[Scummvm-git-logs] scummvm master -> 40b8acad47b4c58f336b1bc97283bc9491117e71
Die4Ever
30947252+Die4Ever at users.noreply.github.com
Sun Nov 14 01:37:49 UTC 2021
This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
40b8acad47 GROOVIE: Othello Cursed Coins puzzle for Clandestiny
Commit: 40b8acad47b4c58f336b1bc97283bc9491117e71
https://github.com/scummvm/scummvm/commit/40b8acad47b4c58f336b1bc97283bc9491117e71
Author: Die4Ever (die4ever2005 at gmail.com)
Date: 2021-11-13T19:37:19-06:00
Commit Message:
GROOVIE: Othello Cursed Coins puzzle for Clandestiny
Changed paths:
engines/groovie/logic/othello.cpp
engines/groovie/logic/othello.h
diff --git a/engines/groovie/logic/othello.cpp b/engines/groovie/logic/othello.cpp
index e01b9c67a4..3d75925542 100644
--- a/engines/groovie/logic/othello.cpp
+++ b/engines/groovie/logic/othello.cpp
@@ -26,16 +26,642 @@
*
*/
-#include "groovie/groovie.h"
#include "groovie/logic/othello.h"
+#include "groovie/groovie.h"
namespace Groovie {
-OthelloGame::OthelloGame() : _random("OthelloGame") {
+const int EMPTY_PIECE = 0;
+const int AI_PIECE = 1;
+const int PLAYER_PIECE = 2;
+
+int xyToVar(int x, int y) {
+ return x * 10 + y + 25;
+}
+
+void sortPossibleMoves(Freeboard (&boards)[30], int numPossibleMoves) {
+ if (numPossibleMoves < 2)
+ return;
+
+ Common::sort(&boards[0], &boards[numPossibleMoves]);
+}
+
+int OthelloGame::scoreEdge(byte (&board)[8][8], int x, int y, int slopeX, int slopeY) {
+ const int8 *scores = &_edgesScores[0];
+ const int8 *ptr = &scores[board[x][y]];
+
+ // we don't score either corner in this function
+ x += slopeX;
+ y += slopeY;
+ int endX = x + slopeX * 5;
+ int endY = y + slopeY * 5;
+
+ while (x <= endX && y <= endY) {
+ ptr = &scores[*ptr + board[x][y]];
+ x += slopeX;
+ y += slopeY;
+ }
+ return _cornersScores[*ptr];
+}
+
+int OthelloGame::scoreEarlyGame(Freeboard *freeboard) {
+ // in the early game the AI's search depth can't see far enough, so instead of the score simply being
+ int scores[3];
+ scores[0] = 0;
+ scores[1] = 0;
+ scores[2] = 0;
+
+ byte(&b)[8][8] = freeboard->_boardstate;
+
+ int scoreRightEdge = scoreEdge(b, 7, 0, 0, 1);
+ int scoreBottomEdge = scoreEdge(b, 0, 7, 1, 0);
+ int scoreTopEdge = scoreEdge(b, 0, 0, 1, 0);
+ int scoreLeftEdge = scoreEdge(b, 0, 0, 0, 1);
+ scores[AI_PIECE] = scoreRightEdge + scoreBottomEdge + scoreTopEdge + scoreLeftEdge;
+
+ int topLeft = b[0][0];
+ int bottomLeft = b[0][7];
+ int topRight = b[7][0];
+ int bottomRight = b[7][7];
+
+ //subtract points for bad spots relative to the opponent
+ //diagonal from the corners
+ const int8 *diagFromCorners = &_scores[0][0];
+ scores[b[1][1]] -= diagFromCorners[topLeft];
+ scores[b[1][6]] -= diagFromCorners[bottomLeft];
+ scores[b[6][1]] -= diagFromCorners[topRight];
+ scores[b[6][6]] -= diagFromCorners[bottomRight];
+
+ // 2 away from the edge
+ const int8 *twoAwayFromEdge = &_scores[1][0];
+ scores[b[1][2]] -= twoAwayFromEdge[b[0][2]];
+ scores[b[1][5]] -= twoAwayFromEdge[b[0][5]];
+ scores[b[2][1]] -= twoAwayFromEdge[b[2][0]];
+ scores[b[2][6]] -= twoAwayFromEdge[b[2][7]];
+ scores[b[5][1]] -= twoAwayFromEdge[b[5][0]];
+ scores[b[5][6]] -= twoAwayFromEdge[b[5][7]];
+ scores[b[6][2]] -= twoAwayFromEdge[b[7][2]];
+ scores[b[6][5]] -= twoAwayFromEdge[b[7][5]];
+
+ // 3 away from the edge
+ const int8 *threeAwayFromEdge = &_scores[2][0];
+ scores[b[1][3]] -= threeAwayFromEdge[b[0][3]];
+ scores[b[1][4]] -= threeAwayFromEdge[b[0][4]];
+ scores[b[3][1]] -= threeAwayFromEdge[b[3][0]];
+ scores[b[3][6]] -= threeAwayFromEdge[b[3][7]];
+ scores[b[4][1]] -= threeAwayFromEdge[b[4][0]];
+ scores[b[4][6]] -= threeAwayFromEdge[b[4][7]];
+ scores[b[6][3]] -= threeAwayFromEdge[b[7][3]];
+ scores[b[6][4]] -= threeAwayFromEdge[b[7][4]];
+
+ // corners
+ scores[topLeft] += 0x32;
+ scores[bottomLeft] += 0x32;
+ scores[topRight] += 0x32;
+ scores[bottomRight] += 0x32;
+
+ // left column
+ scores[b[0][1]] += 4;
+ scores[b[0][2]] += 0x10;
+ scores[b[0][3]] += 0xc;
+ scores[b[0][4]] += 0xc;
+ scores[b[0][5]] += 0x10;
+ scores[b[0][6]] += 4;
+
+ // top row
+ scores[b[1][0]] += 4;
+ scores[b[2][0]] += 0x10;
+ scores[b[3][0]] += 0xc;
+ scores[b[4][0]] += 0xc;
+ scores[b[5][0]] += 0x10;
+ scores[b[6][0]] += 4;
+
+ // bottom row
+ scores[b[1][7]] += 4;
+ scores[b[2][7]] += 0x10;
+ scores[b[3][7]] += 0xc;
+ scores[b[4][7]] += 0xc;
+ scores[b[5][7]] += 0x10;
+ scores[b[6][7]] += 4;
+
+ // away from the edges (interesting we don't score the center/starting spots?)
+ scores[b[2][2]] += 1;
+ scores[b[2][5]] += 1;
+ scores[b[5][2]] += 1;
+ scores[b[5][5]] += 1;
+
+ // right column
+ scores[b[7][1]] += 4;
+ scores[b[7][2]] += 0x10;
+ scores[b[7][3]] += 0xc;
+ scores[b[7][4]] += 0xc;
+ scores[b[7][5]] += 0x10;
+ scores[b[7][6]] += 4;
+
+ return scores[AI_PIECE] - scores[PLAYER_PIECE];
+}
+
+int OthelloGame::scoreLateGame(Freeboard *freeboard) {
+ byte *board = &freeboard->_boardstate[0][0];
+ // in the late game, we simply score the same way we determine the winner, because the AI's search depth can see to the end of the game
+ int scores[3];
+ scores[0] = 0;
+ scores[1] = 0;
+ scores[2] = 0;
+ for (int i = 0; i < 64; i++) {
+ scores[board[i]]++;
+ }
+ return (scores[AI_PIECE] - scores[PLAYER_PIECE]) * 4;
+}
+
+int OthelloGame::scoreBoard(Freeboard *board) {
+ if (_isLateGame)
+ return scoreLateGame(board);
+ else
+ return scoreEarlyGame(board);
+}
+
+void OthelloGame::restart(void) {
+ _counter = 0;
+ _isLateGame = false;
+ _board._score = 0;
+
+ // clear the board
+ memset(_board._boardstate, EMPTY_PIECE, sizeof(_board._boardstate));
+ // set the starting pieces
+ _board._boardstate[4][4] = AI_PIECE;
+ _board._boardstate[3][3] = _board._boardstate[4][4];
+ _board._boardstate[4][3] = PLAYER_PIECE;
+ _board._boardstate[3][4] = _board._boardstate[4][3];
+}
+
+void OthelloGame::setClickable(Freeboard *nextBoard, Freeboard *currentBoard, byte *vars) {
+ for (int x = 0; x < 8; x++) {
+ for (int y = 0; y < 8; y++) {
+ byte b = _lookupPlayer[currentBoard->_boardstate[x][y]];
+ vars[xyToVar(x, y)] = b;
+ if (nextBoard->_boardstate[x][y] == b && b != 0) {
+ vars[xyToVar(x, y)] += 32;
+ }
+ }
+ }
+ return;
+}
+
+void OthelloGame::readBoardStateFromVars(byte *vars) {
+ for (int x = 0; x < 8; x++) {
+ for (int y = 0; y < 8; y++) {
+ byte b = vars[xyToVar(x, y)];
+ if (b == _lookupPlayer[0]) {
+ _board._boardstate[x][y] = EMPTY_PIECE;
+ }
+ if (b == _lookupPlayer[1]) {
+ _board._boardstate[x][y] = AI_PIECE;
+ }
+ if (b == _lookupPlayer[2]) {
+ _board._boardstate[x][y] = PLAYER_PIECE;
+ }
+ }
+ }
+}
+
+Freeboard OthelloGame::getPossibleMove(Freeboard *freeboard, int moveSpot) {
+ // we make a new board with the piece placed and captures completed
+ int player = _isAiTurn ? AI_PIECE : PLAYER_PIECE;
+ int opponent = _isAiTurn ? PLAYER_PIECE : AI_PIECE;
+
+ // copy the board
+ Freeboard newboard;
+ memcpy(newboard._boardstate, freeboard->_boardstate, sizeof(newboard._boardstate));
+
+ byte *board = &newboard._boardstate[0][0];
+ int8 **line = _lines[moveSpot];
+
+ // check every line until we hit the null-terminating pointer
+ for (line = _lines[moveSpot]; *line != NULL; line++) {
+ int8 *lineSpot = *line;
+ int piece = board[*lineSpot];
+ int8 *_lineSpot;
+ // we already know the current moveSpot is the player's piece
+ // if these 2 loops were a regex replacement, they would be something like s/(O+)P/(P+)P/
+ for (_lineSpot = lineSpot; piece == opponent; _lineSpot++) {
+ piece = board[*_lineSpot];
+ }
+ // if _lineSpot was advanced (meaning at least 1 opponent piece), and now we're at a player piece
+ if (_lineSpot != lineSpot && piece == player) {
+ // apply the captures
+ piece = board[*lineSpot];
+ while (piece == opponent) {
+ board[*lineSpot] = player;
+ lineSpot++;
+ piece = board[*lineSpot];
+ }
+ }
+ }
+ // add the new piece
+ board[moveSpot] = player;
+ return newboard;
+}
+
+int OthelloGame::getAllPossibleMoves(Freeboard *freeboard, Freeboard (&boards)[30]) {
+ int moveSpot = 0;
+ byte player = _isAiTurn ? AI_PIECE : PLAYER_PIECE;
+ byte opponent = _isAiTurn ? PLAYER_PIECE : AI_PIECE;
+ int numPossibleMoves = 0;
+ int8 ***line = &_lines[0];
+ do {
+ if (freeboard->_boardstate[moveSpot / 8][moveSpot % 8] == 0) {
+ int8 **lineSpot = *line;
+ int8 *testSpot;
+ // loop through a list of slots in line with piece moveSpot, looping away from moveSpot
+ do {
+ do {
+ // skip all spots that aren't the opponent
+ testSpot = *lineSpot;
+ lineSpot++;
+ if (testSpot == NULL) // end of the null terminated line?
+ goto LAB_OUT;
+ } while (freeboard->_boardstate[*testSpot / 8][*testSpot % 8] != opponent);
+
+ // we found the opponent, skip to the first piece that doesn't belong to the opponent
+ for (; freeboard->_boardstate[*testSpot / 8][*testSpot % 8] == opponent; testSpot++) {}
+
+ // start over again if didn't find a piece of our own on the other side
+ } while (freeboard->_boardstate[*testSpot / 8][*testSpot % 8] != player);
+ // so we found (empty space)(opponent+)(our own piece)
+ // add this to the list of possible moves
+ boards[numPossibleMoves] = getPossibleMove(freeboard, moveSpot);
+ boards[numPossibleMoves]._score = scoreBoard(&boards[numPossibleMoves]);
+ numPossibleMoves++;
+ }
+ LAB_OUT:
+ line++;
+ moveSpot++;
+ if (moveSpot > 63) {
+ sortPossibleMoves(boards, numPossibleMoves);
+ return numPossibleMoves;
+ }
+ } while (true);
+}
+
+int OthelloGame::aiRecurse(Freeboard *board, int depth, int parentScore, int opponentBestScore) {
+ Freeboard possibleMoves[30];
+ int numPossibleMoves = getAllPossibleMoves(board, possibleMoves);
+ if (numPossibleMoves == 0) {
+ _isAiTurn = !_isAiTurn;
+ numPossibleMoves = getAllPossibleMoves(board, possibleMoves);
+ if (numPossibleMoves == 0) {
+ return scoreLateGame(board);
+ }
+ }
+
+ int _depth = depth - 1;
+ bool isPlayerTurn = !_isAiTurn;
+ int bestScore = isPlayerTurn ? 100 : -100;
+ Freeboard *boardsIter = &possibleMoves[0];
+ for (int i = 0; i < numPossibleMoves; i++, boardsIter++) {
+ Freeboard *tBoard = boardsIter;
+ _isAiTurn = isPlayerTurn; // reset and flip the global for whose turn it is before recursing
+ int score;
+ if (_depth == 0) {
+ score = (int)tBoard->_score;
+ } else {
+ if (isPlayerTurn) {
+ score = aiRecurse(tBoard, _depth, parentScore, bestScore);
+ } else {
+ score = aiRecurse(tBoard, _depth, bestScore, opponentBestScore);
+ }
+ }
+ if ((bestScore < score) != isPlayerTurn) {
+ bool done = true;
+ if (isPlayerTurn) {
+ if (parentScore < score)
+ done = false;
+ } else {
+ if (score < opponentBestScore)
+ done = false;
+ }
+ bestScore = score;
+ if (done) {
+ return score;
+ }
+ }
+ }
+
+ return bestScore;
+}
+
+byte OthelloGame::aiDoBestMove(Freeboard *pBoard) {
+ Freeboard possibleMoves[30];
+ int bestScore = -101;
+ int bestMove = 0;
+ int parentScore = -100;
+ if (_flag1 == 0) {
+ _isAiTurn = 1;
+ }
+
+ Freeboard *board = pBoard;
+ int numPossibleMoves = getAllPossibleMoves(board, possibleMoves);
+ if (numPossibleMoves == 0) {
+ return 0;
+ }
+
+ for (int move = 0; move < numPossibleMoves; move++) {
+ _isAiTurn = !_isAiTurn; // flip before recursing
+ int score = aiRecurse(&possibleMoves[move], _depths[_counter], parentScore, 100);
+ if (bestScore < score) {
+ parentScore = score;
+ bestMove = move;
+ bestScore = score;
+ }
+ }
+
+ *pBoard = possibleMoves[bestMove];
+ if (_flag1 == 0) {
+ _counter += 1;
+ }
+ return 1;
+}
+
+void OthelloGame::initLines(void) {
+ // allocate an array of strings, the lines are null-terminated
+ int8 **lines = &_linesStorage[0];
+ int8 *line = &_lineStorage[0];
+
+ for (int baseX = 0; baseX < 8; baseX++) {
+ for (int baseY = 0; baseY < 8; baseY++) {
+ // assign the array of strings to the current spot
+ _lines[(baseX * 8 + baseY)] = lines;
+ for (int slopeX = -1; slopeX < 2; slopeX++) {
+ for (int slopeY = -1; slopeY < 2; slopeY++) {
+ // don't include current spot in its own line
+ if (slopeX == 0 && slopeY == 0)
+ continue;
+
+ // assign the current line to the current spot in the lines array, uint saves us from bounds checking for below 0
+ *lines = line;
+ uint x = baseX + slopeX;
+ uint y;
+ for (y = baseY + slopeY; x < 8 && y < 8; y += slopeY) {
+ *line = x * 8 + y;
+ line++;
+ x += slopeX;
+ }
+ if (baseX + slopeX != (int)x || baseY + slopeY != (int)y) {
+ *line = baseX * 8 + baseY;
+ line++;
+ lines++;
+ }
+ }
+ }
+ // append a 0 to the lines array to terminate that set of lines
+ *lines = NULL;
+ lines++;
+ }
+ }
+}
+
+uint OthelloGame::makeMove(Freeboard *freeboard, uint8 x, uint8 y) {
+ Freeboard possibleMoves[30];
+ Freeboard *board = freeboard;
+ _isAiTurn = 0;
+ uint numPossibleMoves = getAllPossibleMoves(board, possibleMoves);
+ if (numPossibleMoves == 0)
+ return 0;
+
+ if (x == '*') {
+ _flag1 = 1;
+ aiDoBestMove(freeboard);
+ _flag1 = 0;
+ _counter += 1;
+ return 1;
+ }
+
+ // uint saves us from bounds checking below 0, not yet sure why this function uses y, x instead of x, y but it works
+ if (y < 8 && x < 8 && board->_boardstate[y][x] == 0) {
+ // find the pre-made board the represents this move
+ uint newBoardSlot = 0;
+ for (; newBoardSlot < numPossibleMoves && possibleMoves[newBoardSlot]._boardstate[y][x] == 0; newBoardSlot++) {
+ }
+ if (newBoardSlot == numPossibleMoves)
+ return 0;
+
+ *freeboard = possibleMoves[newBoardSlot];
+ _counter += 1;
+ return 1;
+ }
+
+ return 0;
+}
+
+byte OthelloGame::getLeader(Freeboard *f) {
+ byte counters[3] = {};
+
+ for (int x = 0; x < 8; x++) {
+ for (int y = 0; y < 8; y++) {
+ byte t = f->_boardstate[x][y];
+ counters[t]++;
+ }
+ }
+
+ if (counters[2] < counters[1])
+ return 1;
+ if (counters[2] > counters[1])
+ return 2;
+ return 3;
+}
+
+void OthelloGame::opInit(byte *vars) {
+ vars[0] = 0;
+ restart();
+
+ for (int x = 0; x < 8; x++) {
+ for (int y = 0; y < 8; y++) {
+ vars[xyToVar(x, y)] = _lookupPlayer[_board._boardstate[x][y]];
+ }
+ }
+
+ vars[4] = 1;
+}
+
+void OthelloGame::tickBoard() {
+ if (_counter < 60) {
+ if (_movesLateGame < _counter) {
+ _isLateGame = true;
+ }
+ }
+}
+
+void OthelloGame::opPlayerMove(byte *vars) {
+ tickBoard();
+
+ if (_counter < 60) {
+ _flag2 = 0;
+ byte x = vars[3];
+ byte y = vars[2];
+ // top left spot is 0, 0
+ debugC(1, kDebugLogic, "OthelloGame player moved to %d, %d", (int)x, (int)y);
+ vars[4] = makeMove(&_board, x, y);
+ } else {
+ vars[0] = getLeader(&_board);
+ vars[4] = 1;
+ }
+ setClickable(&_board, &_board, vars);
}
-void OthelloGame::run(byte *scriptVariables) {
- // TODO
+// this might be for a hint move? maybe on easy mode?
+void OthelloGame::op3(byte *vars) {
+ tickBoard();
+
+ if (_counter < 60) {
+ vars[3] = '*';
+ uint move = makeMove(&_board, '*', vars[2]);
+ vars[4] = move;
+ if (move == 0) {
+ _flag2 = 1;
+ } else {
+ _flag2 = 0;
+ }
+ } else {
+ vars[0] = getLeader(&_board);
+ vars[4] = 1;
+ }
+ setClickable(&_board, &_board, vars);
+}
+
+void OthelloGame::opAiMove(byte *vars) {
+ tickBoard();
+
+ if (_counter < 60) {
+ uint move = aiDoBestMove(&_board);
+ vars[4] = move;
+ if (move == 0 && _flag2 != 0) {
+ vars[0] = getLeader(&_board);
+ }
+ } else {
+ vars[0] = getLeader(&_board);
+ vars[4] = 0;
+ }
+ setClickable(&_board, &_board, vars);
+}
+
+void OthelloGame::op5(byte *vars) {
+ _counter = vars[2];
+ readBoardStateFromVars(vars);
+ initLines();
+ vars[4] = 1;
+}
+
+OthelloGame::OthelloGame()
+ : _random("OthelloGame"),
+ _depths{1, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 7, 6, 5, 4, 3, 2, 1, 1},
+ _lookupPlayer{21, 40, 31},
+ _scores{30, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0},
+ _edgesScores{0, 3, 6, 9, 3, 15, 12, 18, 6, 0, 45, 6, 0, 3, 27, 12, 60, 15, 9, 18, 36, 21, 24, 27, 30, 24, 36, 33, 39, 27, 21, 3, 27, 21, 24, 69, 33, 18, 36, 30, 39, 78, 42, 45, 48, 51, 45, 57, 54, 60, 48, 42, 87, 48, 42, 45, 6, 54, 102, 57, 51, 60, 15, 63, 66, 69, 72, 66, 78, 75, 81, 69, 63, 24, 69, 63, 66, 69, 75, 39, 78, 72, 81, 78, 84, 87, 90, 93, 87, 99, 96, 102, 90, 84, 87, 90, 84, 87, 48, 96, 102, 99, 93, 102, 57, 0, 0, 0, 0, 0, 0, 0},
+ _cornersScores{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -20, 0, 0, 0, 20, 0, -20, 0, 0, 0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 0, 20, 20, 20, 40, 20, 0, 20, 20, 20, 40, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -40, -20, -20, -20, 0, -20, -40, -20, -20, -20, 0, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 20, 40, 40, 40, 40, 40, 20, 40, 40, 40, 40, -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, -20, -40, -40, -40, -40, -40, -20},
+ _movesLateGame(52)
+{
+ _isLateGame = false;
+ _counter = 0;
+ _isAiTurn = 0;
+ _flag1 = 0;
+ _flag2 = 0;
+ initLines();
+
+#if 0
+ test();
+#endif
+}
+
+void OthelloGame::run(byte *vars) {
+ byte op = vars[1];
+ debugC(1, kDebugLogic, "OthelloGame op %d", (int)op);
+
+ switch (op) {
+ case 0: // init/restart
+ opInit(vars);
+ break;
+ case 1: // win/lose?
+ _flag2 = 1;
+ break;
+ case 2: // player move
+ opPlayerMove(vars);
+ break;
+ case 3: // ???
+ op3(vars);
+ break;
+ case 4: // ai move
+ opAiMove(vars);
+ break;
+ case 5: // ???
+ op5(vars);
+ break;
+ }
+}
+
+void OthelloGame::test() {
+ warning("OthelloGame::test() starting");
+ // pairs of x, y, 3 moves per line
+ testMatch({
+ // x1,y1,x2,y2,x3,y3
+ 5, 4, 5, 2, 3, 2,
+ 6, 6, 1, 2, 1, 0
+ }, true);
+
+ testMatch({
+ // x1,y1,x2,y2,x3,y3
+ 5, 4, 6, 2, 4, 2,
+ 5, 1, 5, 5, 3, 5,
+ 1, 5, 2, 4, 6, 1,
+ 6, 4, 6, 3, 7, 4,
+ 7, 1, 6, 0, 1, 4,
+ 2, 2, 1, 3, 6, 6,
+ 6, 7, 0, 6, 2, 6,
+ 4, 6, 3, 6, 5, 6,
+ 1, 6, 1, 1, 2, 1,
+ 3, 1, 3, 0, 0, 2,
+ 2, 7
+ // x1,y1,x2,y2,x3,y3
+ }, false);
+
+ warning("OthelloGame::test() finished");
+}
+
+void OthelloGame::testMatch(Common::Array<int> moves, bool playerWin) {
+ byte vars[1024];
+ memset(vars, 0, sizeof(vars));
+ byte &op = vars[1];
+ byte &x = vars[3];
+ byte &y = vars[2];
+ byte &winner = vars[4];
+ byte &winner2 = vars[0];
+
+ warning("OthelloGame::testMatch(%u, %d) starting", moves.size(), (int)playerWin);
+ op = 0;
+ run(vars);
+
+ for (uint i = 0; i < moves.size(); i += 2) {
+ if (winner2 != 0)
+ error("early winner? %d, %d", (int)winner, (int)winner2);
+
+ x = moves[i];
+ y = moves[i + 1];
+ op = 2;
+ run(vars);
+
+ if (winner != 1)
+ error("early winner? %d, %d", (int)winner, (int)winner2);
+
+ op = 4;
+ run(vars);
+ }
+
+ if (playerWin && winner2 != 0)
+ error("player didn't win, %d", (int)winner2);
+ else if (playerWin == false && winner2 != 1)
+ error("ai didn't win? %d", (int)winner2);
+
+ warning("OthelloGame::testMatch(%u, %d) finished", moves.size(), (int)playerWin);
}
-} // End of Groovie namespace
+} // namespace Groovie
diff --git a/engines/groovie/logic/othello.h b/engines/groovie/logic/othello.h
index c151a1e8d6..0ceb08b520 100644
--- a/engines/groovie/logic/othello.h
+++ b/engines/groovie/logic/othello.h
@@ -35,15 +35,64 @@
namespace Groovie {
/*
- * Othello/Reversi puzzle in Clandestiny and UHP.
+ * Othello/Reversi Cursed Coins puzzle in Clandestiny and UHP.
*/
+struct Freeboard {
+ int _score;
+ byte _boardstate[8][8]; // 0 is empty, 1 is player, 2 is AI
+
+ // for sorting an array of pointers
+ friend bool operator<(const Freeboard &a, const Freeboard &b) {
+ return a._score > b._score;
+ }
+};
+
class OthelloGame {
public:
OthelloGame();
void run(byte *scriptVariables);
private:
+ int scoreEdge(byte (&board)[8][8], int x, int y, int slopeX, int slopeY);
+ int scoreEarlyGame(Freeboard *freeboard);
+ int scoreLateGame(Freeboard *freeboard);
+ int scoreBoard(Freeboard *board);
+ void restart(void);
+ void setClickable(Freeboard *nextBoard, Freeboard *currentBoard, byte *vars);
+ void readBoardStateFromVars(byte *vars);
+ Freeboard getPossibleMove(Freeboard *freeboard, int moveSpot);
+ int getAllPossibleMoves(Freeboard *freeboard, Freeboard (&boards)[30]);
+ int aiRecurse(Freeboard *board, int depth, int parentScore, int opponentBestScore);
+ byte aiDoBestMove(Freeboard *pBoard);
+ void initLines(void);
+ uint makeMove(Freeboard *freeboard, uint8 x, uint8 y);
+ byte getLeader(Freeboard *f);
+ void opInit(byte *vars);
+ void tickBoard();
+ void opPlayerMove(byte *vars);
+ void op3(byte *vars);
+ void opAiMove(byte *vars);
+ void op5(byte *vars);
+
+ void test();
+ void testMatch(Common::Array<int> moves, bool playerWin);
+
Common::RandomSource _random;
+ byte _flag1;
+ int8 _flag2;
+ const int _depths[60];
+ int _counter;
+ const int _movesLateGame; // this is 52, seems to be a marker of when to change the function pointer to an aleternate scoring algorithm for the late game
+ bool _isLateGame; // used to choose the scoring function, true means scoreLateGame
+ const int8 _lookupPlayer[3]; // used to convert from internal values that represent piece colors to what the script uses in vars, {21, 40, 31}
+ const int8 _scores[3][4];
+ const int8 _edgesScores[112];
+ const int _cornersScores[105];
+ int _isAiTurn;
+ int8 **_lines[64];
+ int8 *_linesStorage[484];
+ int8 _lineStorage[2016];
+ Freeboard _board;
};
} // End of Groovie namespace
More information about the Scummvm-git-logs
mailing list