From 40b8acad47b4c58f336b1bc97283bc9491117e71 Mon Sep 17 00:00:00 2001 From: Die4Ever Date: Sat, 13 Nov 2021 19:37:19 -0600 Subject: [PATCH] GROOVIE: Othello Cursed Coins puzzle for Clandestiny --- engines/groovie/logic/othello.cpp | 636 +++++++++++++++++++++++++++++- engines/groovie/logic/othello.h | 51 ++- 2 files changed, 681 insertions(+), 6 deletions(-) diff --git a/engines/groovie/logic/othello.cpp b/engines/groovie/logic/othello.cpp index e01b9c67a49..3d759255421 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 OthelloGame::run(byte *scriptVariables) { - // TODO +void sortPossibleMoves(Freeboard (&boards)[30], int numPossibleMoves) { + if (numPossibleMoves < 2) + return; + + Common::sort(&boards[0], &boards[numPossibleMoves]); } -} // End of Groovie namespace +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); +} + +// 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 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); +} + +} // namespace Groovie diff --git a/engines/groovie/logic/othello.h b/engines/groovie/logic/othello.h index c151a1e8d64..0ceb08b5203 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 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