mirror of
https://github.com/libretro/scummvm.git
synced 2025-02-12 06:00:48 +00:00
GROOVIE: Othello Cursed Coins puzzle for Clandestiny
This commit is contained in:
parent
e078df4125
commit
40b8acad47
@ -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<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);
|
||||
}
|
||||
|
||||
} // namespace Groovie
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user