Files
2026-02-02 04:50:13 +01:00

892 lines
22 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
* This file is dual-licensed.
* In addition to the GPLv3 license mentioned above, MojoTouch has exclusively licensed
* this code on November 10th, 2021, to be use in closed-source products.
* Therefore, any contributions (commits) to it will also be dual-licensed.
*
*/
#include "groovie/logic/pente.h"
#include "common/stack.h"
#include "common/algorithm.h"
#include "groovie/groovie.h"
namespace Groovie {
#ifdef UINT_MAX
#undef UINT_MAX
#endif
const uint UINT_MAX = (uint)-1;
const int WIN_SCORE = 100000000;
const int CAPTURE_SCORE = 1000000;
const uint PLAYER = 79;
const uint STAUF = 88;
struct pentePlayerTable {
Common::FixedStack<int, 813> lines;
};
struct penteTable {
pentePlayerTable player;
pentePlayerTable stauf;
int playerScore;
int staufScore;
byte playerLines;
byte staufLines;
byte width;
byte height;
uint16 boardSize;
byte lineLength;
uint16 moveCounter;
byte boardState[20][15];
uint16 linesCounter;
uint16 linesTable[20][15][21];
byte numAdjacentPieces[20][15];
byte calcTouchingPieces; // the deepest level of AI recursion sets this to 0, and then sets it back to 1 when returning
};
void PenteGame::addLine(int x, int y, int linesCounter) {
int i = ++_table->linesTable[x][y][0];
_table->linesTable[x][y][i] = linesCounter;
}
void PenteGame::buildLookupTable() {
int width = _table->width;
int height = _table->height;
uint16 linesCounter = 0;
int lineLength = _table->lineLength;
// slope of (1,0)
for (int x = 0; x <= width - lineLength; x++) {
for (int y = 0; y < height; y++) {
for (int z = 0; z < lineLength; z++) {
addLine(x + z, y, linesCounter);
}
linesCounter++;
}
}
// slope of (0,1)
for (int x = 0; x < width; x++) {
for (int y = 0; y <= height - lineLength; y++) {
for (int z = 0; z < lineLength; z++) {
addLine(x, y + z, linesCounter);
}
linesCounter++;
}
}
// slope of (1,1)
for (int x = 0; x <= width - lineLength; x++) {
for (int y = 0; y <= height - lineLength; y++) {
for (int z = 0; z < lineLength; z++) {
addLine(x + z, y + z, linesCounter);
}
linesCounter++;
}
}
// slope of (1,-1)
for (int x = 0; x <= width - lineLength; x++) {
for (int y = lineLength - 1; y < height; y++) {
for (int z = 0; z < lineLength; z++) {
addLine(x + z, y - z, linesCounter);
}
linesCounter++;
}
}
_table->linesCounter = linesCounter;
}
void PenteGame::penteDeInit() {
delete _table;
_table = nullptr;
}
void PenteGame::penteInit(uint width, uint height, uint length) {
_table = new penteTable();
_table->width = width;
_table->height = height;
_table->boardSize = height * width;
_table->lineLength = length;
memset(_table->boardState, 0, sizeof(_table->boardState));
buildLookupTable();
assert(_table->linesCounter == 812);
_table->staufScore = (uint)_table->linesCounter;
_table->playerScore = (uint)_table->linesCounter;
memset(_table->numAdjacentPieces, 0, sizeof(_table->numAdjacentPieces));
_table->calcTouchingPieces = 1;
_nextCapturedSpot = -1;
_animateCapturesBitMask = 0;
_previousMove = 0;
}
int &PenteGame::getPlayerTable(bool staufTurn, pentePlayerTable *&pt) {
pt = staufTurn ? &_table->stauf : &_table->player;
return staufTurn ? _table->staufScore : _table->playerScore;
}
void PenteGame::scoreLine(uint16 lineIndex, bool isStaufTurn, bool revert) {
pentePlayerTable *playerTable;
int &score = getPlayerTable(isStaufTurn, playerTable);
int lineLength, mult;
if (revert) {
lineLength = --playerTable->lines[lineIndex];
mult = -1;
} else {
lineLength = playerTable->lines[lineIndex]++;
mult = 1;
}
if (_table->lineLength - lineLength == 1) {
score = (int)score + (int)WIN_SCORE * mult;
} else {
pentePlayerTable *opponentTable;
int &opponentScore = getPlayerTable(!isStaufTurn, opponentTable);
int opponentLineLength = opponentTable->lines[lineIndex];
if (lineLength == 0) {
opponentScore += (-(1 << ((byte)opponentLineLength & 0x1f))) * mult;
if (_table->lineLength - opponentLineLength == 1) {
if (isStaufTurn)
_table->playerLines -= mult;
else
_table->staufLines -= mult;
}
}
if (opponentLineLength == 0) {
score += (1 << ((byte)lineLength & 0x1f)) * mult;
if (_table->lineLength - lineLength == 2) {
byte b;
if (isStaufTurn)
b = (_table->staufLines += mult);
else
b = (_table->playerLines += mult);
if (revert)
b -= mult;
if (1 < b) {
score = (int)score + (int)CAPTURE_SCORE * mult;
}
}
}
}
}
void PenteGame::calcTouchingPieces(byte moveX, byte moveY, bool revert) {
byte endX, endY;
if (moveX + 1 < _table->width) {
endX = moveX + 1;
} else {
endX = _table->width - 1;
}
if (moveY + 1 < _table->height) {
endY = moveY + 1;
} else {
endY = _table->height - 1;
}
byte x = 0;
if (1 < moveX) {
x = moveX - 1;
}
for (; x <= endX; x++) {
byte y = 0;
if (1 < moveY) {
y = moveY - 1;
}
for (; y <= endY; y++) {
if (revert)
_table->numAdjacentPieces[x][y]--;
else
_table->numAdjacentPieces[x][y]++;
}
}
}
void PenteGame::updateScore(byte x, byte y, bool isStauf) {
assert(_table->boardState[x][y] == 0);
_table->boardState[x][y] = isStauf ? STAUF : PLAYER;
uint16 lines = _table->linesTable[x][y][0];
for (int i = 1; i <= lines; i++) {
uint16 lineIndex = _table->linesTable[x][y][i];
scoreLine(lineIndex, isStauf, false);
}
if (_table->calcTouchingPieces != 0) {
calcTouchingPieces(x, y, false);
}
_table->moveCounter++;
}
void PenteGame::revertScore(byte x, byte y) {
assert(_table->boardState[x][y] != 0);
bool stauf_turn = _table->boardState[x][y] == STAUF;
_table->boardState[x][y] = 0;
_table->moveCounter--;
uint lines = _table->linesTable[x][y][0];
for (uint i = 1; i <= lines; i++) {
uint16 lineIndex = _table->linesTable[x][y][i];
scoreLine(lineIndex, stauf_turn, true);
}
if (_table->calcTouchingPieces != 0) {
calcTouchingPieces(x, y, true);
}
}
byte PenteGame::scoreCaptureSingle(byte x, byte y, int slopeX, int slopeY) {
byte x1 = x + slopeX;
byte y1 = y + slopeY;
byte x2 = x + slopeX * 2;
byte y2 = y + slopeY * 2;
byte endX = x + slopeX * 3;
byte endY = y + slopeY * 3;
// we don't need to check for below 0 when we have unsigned types
if (x >= _table->width || y >= _table->height)
return 0;
if (endX >= _table->width || endY >= _table->height)
return 0;
auto &boardState = _table->boardState;
byte captor = boardState[x][y];
byte captive = captor == STAUF ? PLAYER : STAUF;
// make sure the captor is at the start and end of the line
if (boardState[endX][endY] != captor)
return 0;
// check that the captive is both of the middle pieces
if (boardState[x1][y1] != captive || boardState[x2][y2] != captive)
return 0;
// now we take away the points the captor had for these pieces
revertScore(x1, y1);
revertScore(x2, y2);
return 1;
}
struct Slope {
int x, y;
};
// the order of these is important because we return the bitMask
Slope slopes[] = {{1, 0},
{1, 1},
{0, 1},
{-1, 1},
{-1, 0},
{-1, -1},
{0, -1},
{1, -1}};
uint PenteGame::scoreCapture(byte x, byte y) {
byte bitMask = 0;
bool isStauf = _table->boardState[x][y] == STAUF;
for (const Slope &slope : slopes) {
bitMask <<= 1;
bitMask |= scoreCaptureSingle(x, y, slope.x, slope.y);
}
for (int i = bitMask; i; i >>= 1) {
if ((i & 1) == 0)
continue;
pentePlayerTable *playerTable;
int &score = getPlayerTable(isStauf, playerTable);
int lineLength = ++playerTable->lines[_table->linesCounter];
if (_table->lineLength == lineLength) {
score += WIN_SCORE;
} else {
score += 1 << ((lineLength - 1U) & 0x1f);
}
}
return bitMask;
}
void PenteGame::animateCapture(int16 move, byte *bitMaskG, int16 *outCapture1, int16 *outCapture2) {
int x = move / 15;
int y = 0xe - (move % 15);
byte &bitMask = *bitMaskG;
byte bit = 0;
for (bit = 0; bit < 8; bit++) {
if (bitMask >> bit & 1) {
bitMask = 1 << bit ^ bitMask;
break;
}
}
int16 baseSpot;
switch (bit) {
case 0:
*outCapture1 = (x + 2) * 15 - y;
*outCapture2 = x * 15 - y + 46;
return;
case 1:
*outCapture1 = (x + 1) * 15 - y;
*outCapture2 = x * 15 - y + 16;
return;
case 2:
baseSpot = x * 15 - y;
*outCapture1 = baseSpot;
*outCapture2 = baseSpot - 14;
return;
case 3:
baseSpot = x * 15 - y;
*outCapture1 = baseSpot - 1;
*outCapture2 = baseSpot - 16;
return;
case 4:
baseSpot = x * 15 - y;
*outCapture1 = baseSpot - 2;
*outCapture2 = baseSpot - 18;
return;
case 5:
baseSpot = x * 15 - y;
*outCapture1 = baseSpot + 13;
*outCapture2 = baseSpot + 12;
return;
case 6:
baseSpot = x * 15 - y;
*outCapture1 = baseSpot + 28;
*outCapture2 = baseSpot + 42;
return;
case 7:
baseSpot = x * 15 - y;
*outCapture1 = baseSpot + 29;
*outCapture2 = baseSpot + 44;
}
return;
}
void PenteGame::revertCapture(byte x, byte y, byte bitMask) {
bool isPlayer = _table->boardState[x][y] == PLAYER;
for (int i = bitMask; i; i >>= 1) {
if ((i & 1) == 0)
continue;
pentePlayerTable *playerTable;
int &score = getPlayerTable(!isPlayer, playerTable);
int linesCounter = --playerTable->lines[_table->linesCounter];
if (_table->lineLength - linesCounter == 1) {
score -= WIN_SCORE;
} else {
score -= 1 << linesCounter;
}
}
for (int i = 0; i < 8; i++) {
if ((bitMask >> i & 1) == 0)
continue;
Slope &slope = slopes[7 - i];
updateScore(x + slope.x * 2, y + slope.y * 2, isPlayer);
updateScore(x + slope.x, y + slope.y, isPlayer);
}
}
int PenteGame::scoreMoveAndRevert(byte x, byte y, char depth, int parentScore, bool &gameOver) {
updateScore(x, y, _table->moveCounter % 2);
uint capturesMask = scoreCapture(x, y);
if (_table->playerScore >= WIN_SCORE || _table->staufScore >= WIN_SCORE)
gameOver = true;
else
gameOver = false;
int scoreDiff;
if (depth > 0 && gameOver==false && _table->boardSize != _table->moveCounter) {
scoreDiff = aiRecurse(depth, parentScore);
} else {
if (_table->moveCounter % 2 == 0) {
scoreDiff = _table->playerScore - _table->staufScore;
} else {
scoreDiff = _table->staufScore - _table->playerScore;
}
}
if (capturesMask != 0) {
revertCapture(x, y, capturesMask);
}
revertScore(x, y);
return scoreDiff;
}
int PenteGame::scoreMoveAndRevert(byte x, byte y, char depth, int parentScore) {
// same thing, but don't need to provide the reference to the gameOverBool
bool gameOver;
return scoreMoveAndRevert(x, y, depth, parentScore, gameOver);
}
int PenteGame::aiRecurseTail(int parentScore) {
int bestScore = 0x7fffffff;
_table->calcTouchingPieces = 0;
for (byte x = 0; x < _table->width; x++) {
for (byte y = 0; y < _table->height; y++) {
if ((_table->boardState[x][y] != 0) ||
(_table->numAdjacentPieces[x][y] == 0)) {
continue;
}
int scoreDiff = scoreMoveAndRevert(x, y, 0, 0);
if (scoreDiff < bestScore) {
bestScore = scoreDiff;
}
if (-parentScore != bestScore && parentScore <= -bestScore) {
_table->calcTouchingPieces = 1;
return -bestScore;
}
}
}
_table->calcTouchingPieces = 1;
return -bestScore;
}
int PenteGame::aiRecurse(char depth, int parentScore) {
if (depth == 1) {
// don't do more recursion
return aiRecurseTail(parentScore);
}
// do more recursion after finding some good moves
struct GoodMove {
int scoreDiff;
byte x, y;
bool operator()(GoodMove a, GoodMove b) {
return a.scoreDiff < b.scoreDiff;
}
};
Common::FixedStack<GoodMove, 300> goodMoves; // 300 slots because the board is 20x15, but we rarely need many since the search excludes spots with no adjacenet pieces
int bestScore = 0x7fffffff;
for (byte x = 0; x < _table->width; x++) {
for (byte y = 0; y < _table->height; y++) {
if ((_table->boardState[x][y] != 0) ||
(_table->numAdjacentPieces[x][y] == 0)) {
continue;
}
int scoreDiff = scoreMoveAndRevert(x, y, 0, 0);
goodMoves.push({scoreDiff, x, y});
}
}
// sort ascending by scoreDiff, most of the time you'll see scores like -40 at the top and -34 at the end
Common::sort(&goodMoves[0], &goodMoves.top(), goodMoves[0]);
for (uint i = 0; i < goodMoves.size(); i++) {
byte x = goodMoves[i].x;
byte y = goodMoves[i].y;
int scoreDiff = scoreMoveAndRevert(x, y, depth - 1, bestScore);
if (scoreDiff < bestScore) {
bestScore = scoreDiff;
}
if (-parentScore != bestScore && parentScore <= -bestScore)
break;
}
return -bestScore;
}
uint16 PenteGame::aiGetBestMove(byte depth) {
for (int x = 0; x < _table->width; x++) {
for (int y = 0; y < _table->height; y++) {
if (_table->boardState[x][y] != 0 || _table->numAdjacentPieces[x][y] == 0)
continue;
bool gameOver;
scoreMoveAndRevert(x, y, 0, 0, gameOver);
if (gameOver) {
return y + x * 100;
}
}
}
byte counter = 1;
int bestScore = 0x7fffffff;
uint16 bestMove = 0xffff;
for (; bestScore > 99999999 && depth > 1; depth--) {
for (int x = 0; x < _table->width; x++) {
for (int y = 0; y < _table->height; y++) {
if (_table->boardState[x][y] != 0 || _table->numAdjacentPieces[x][y] == 0)
continue;
int scoreRecurse = scoreMoveAndRevert(x, y, depth - 1, bestScore);
if (scoreRecurse < bestScore) {
counter = 1;
bestMove = x * 100 + y;
bestScore = scoreRecurse;
} else {
if (scoreRecurse == bestScore) {
counter += 1;
uint rng = _random.getRandomNumber(UINT_MAX);
if ((rng % CAPTURE_SCORE) * counter < CAPTURE_SCORE) {
bestMove = x * 100 + y;
}
}
}
}
}
}
return bestMove;
}
int varsMoveToXY(byte hundreds, byte tens, byte ones, byte &x, byte &y) {
int move = hundreds * 100 + tens * 10 + ones;
x = move / 15;
y = 14 - move % 15;
return move;
}
void aiMoveToXY(int move, byte &x, byte &y) {
x = move / 100;
y = move % 100;
}
void moveToVars(int move, byte &hundreds, byte &tens, byte &ones) {
hundreds = move / 100;
tens = move % 100 / 10;
ones = move % 10;
}
int xyToMove(uint x, uint y) {
return x * 15 - y + 14;
}
void moveXYToVars(uint x, uint y, byte &hundreds, byte &tens, byte &ones) {
int move = xyToMove(x, y);
moveToVars(move, hundreds, tens, ones);
}
void PenteGame::animateCapturesCheckWinner(byte *vars) {
if (_animateCapturesBitMask != 0 && _nextCapturedSpot < 0) {
int16 capturedSpot;
animateCapture(_previousMove, &_animateCapturesBitMask, &capturedSpot, &_nextCapturedSpot);
vars[5] = 1;
moveToVars(capturedSpot, vars[0], vars[1], vars[2]);
return;
}
if (_animateCapturesBitMask != 0 || _nextCapturedSpot > -1) {
moveToVars(_nextCapturedSpot, vars[0], vars[1], vars[2]);
_nextCapturedSpot = -1;
vars[5] = 1;
return;
}
if (_table->playerScore >= WIN_SCORE || _table->moveCounter >= _table->boardSize)
vars[5] = 3; // player wins
else if (_table->staufScore >= WIN_SCORE)
vars[5] = 2; // Stauf wins
else {
// the match continues
vars[5] = 0;
return;
}
penteDeInit();
}
void PenteGame::opQueryPiece(byte *vars) {
// this runs multiple times to check if pieces belong to stauf or the player?
// this happens when you close the gamebook
byte x, y;
varsMoveToXY(vars[0], vars[1], vars[2], x, y);
byte piece = _table->boardState[x][y];
if (piece == 0) {
vars[3] = 0;
return;
}
if (piece == 0x4f) {
vars[3] = 2;
return;
}
if (piece != 0x58) {
return;
}
vars[3] = 1;
}
void PenteGame::run(byte *vars) {
byte op = vars[4];
if (_table == nullptr && op != 0) {
debugC(kDebugLogic, "pente Init, seed %u", _random.getSeed());
penteInit(20, 15, 5);
}
debugC(kDebugLogic, "penteOp vars[4]: %d", (int)op);
switch (op) {
case 0:
penteDeInit();
return;
case 1:
byte x, y;
_previousMove = varsMoveToXY(vars[0], vars[1], vars[2], x, y);
debugC(kDebugLogic, "player moved to %d, %d", (int)x, (int)y);
updateScore(x, y, _table->moveCounter % 2);
_animateCapturesBitMask = scoreCapture(x, y);
return;
case 2:
case 4:
animateCapturesCheckWinner(vars);
return;
case 3:
break;
case 5:
opQueryPiece(vars);
default:
return;
}
byte aiDepth = vars[6];
if (aiDepth == 0) {
aiDepth = 3;
} else if (aiDepth == 1) {
aiDepth = 4;
} else {
aiDepth = 5;
}
if (aiDepth != 2) {
if (_easierAi && aiDepth > 2)
aiDepth = 2;
_previousMove = aiGetBestMove(aiDepth);
}
else
warning("pente unknown else");
byte x, y;
aiMoveToXY(_previousMove, x, y);
debugC(kDebugLogic, "Stauf moved to %d, %d", (int)x, (int)y);
updateScore(x, y, _table->moveCounter % 2);
_animateCapturesBitMask = scoreCapture(x, y);
_previousMove = xyToMove(x, y);
moveXYToVars(x, y, vars[0], vars[1], vars[2]);
}
PenteGame::PenteGame(bool easierAi) : _random("PenteGame") {
_table = nullptr;
_nextCapturedSpot = -1;
_animateCapturesBitMask = 0;
_previousMove = 0;
#if 0
_easierAi = false;
test();
#endif
_easierAi = easierAi;
}
void PenteGame::test() {
warning("starting PenteGame::test()");
uint32 oldSeed = _random.getSeed();
// 6 moves per line
testGame(3,
{
/*x=*/10, /*y=*/6, /*x=*/9, /*y=*/6, /*x=*/10, /*y=*/7, /*x=*/10, /*y=*/5, /*x=*/10, /*y=*/8, /*x=*/9, /*y=*/9,
/*x=*/10, /*y=*/9, /*x=*/10, /*y=*/10, /*x=*/9, /*y=*/8, /*x=*/8, /*y=*/7, /*x=*/8, /*y=*/8, /*x=*/7, /*y=*/8,
/*x=*/6, /*y=*/9, /*x=*/11, /*y=*/4,
}, false);
testGame(10,
{
/*x=*/10, /*y=*/6, /*x=*/11, /*y=*/7, /*x=*/10, /*y=*/5, /*x=*/10, /*y=*/7, /*x=*/9, /*y=*/7, /*x=*/12, /*y=*/7,
/*x=*/10, /*y=*/4, /*x=*/8, /*y=*/8, /*x=*/10, /*y=*/3, /*x=*/11, /*y=*/5, /*x=*/10, /*y=*/2, /*x=*/9, /*y=*/7,
/*x=*/10, /*y=*/6,
}, true);
// test bottom left corner
testGame(1993,
{
/*x=*/0, /*y=*/0, /*x=*/1, /*y=*/1, /*x=*/1, /*y=*/0, /*x=*/2, /*y=*/0, /*x=*/0, /*y=*/1, /*x=*/0, /*y=*/2,
/*x=*/2, /*y=*/1, /*x=*/3, /*y=*/2, /*x=*/1, /*y=*/2, /*x=*/2, /*y=*/3, /*x=*/4, /*y=*/1, /*x=*/1, /*y=*/4,
/*x=*/5, /*y=*/1, /*x=*/6, /*y=*/1, /*x=*/3, /*y=*/0, /*x=*/5, /*y=*/2, /*x=*/4, /*y=*/3, /*x=*/3, /*y=*/1,
/*x=*/3, /*y=*/3, /*x=*/5, /*y=*/3, /*x=*/4, /*y=*/1, /*x=*/4, /*y=*/3, /*x=*/3, /*y=*/3, /*x=*/3, /*y=*/4,
/*x=*/2, /*y=*/5, /*x=*/7, /*y=*/0
}, false);
// test bottom right corner
testGame(1995,
{
/*x=*/19, /*y=*/0, /*x=*/18, /*y=*/1, /*x=*/19, /*y=*/1, /*x=*/18, /*y=*/2, /*x=*/18, /*y=*/0, /*x=*/18, /*y=*/3,
/*x=*/18, /*y=*/4, /*x=*/17, /*y=*/5, /*x=*/17, /*y=*/0, /*x=*/16, /*y=*/5, /*x=*/17, /*y=*/4, /*x=*/16, /*y=*/4,
/*x=*/18, /*y=*/5, /*x=*/18, /*y=*/6, /*x=*/18, /*y=*/5, /*x=*/15, /*y=*/3, /*x=*/18, /*y=*/4, /*x=*/14, /*y=*/2,
}, false);
// test top left corner
testGame(1996,
{
/*x=*/0, /*y=*/14, /*x=*/1, /*y=*/13, /*x=*/1, /*y=*/14, /*x=*/2, /*y=*/14, /*x=*/0, /*y=*/13, /*x=*/0, /*y=*/12,
/*x=*/1, /*y=*/12, /*x=*/2, /*y=*/11, /*x=*/2, /*y=*/12, /*x=*/3, /*y=*/12, /*x=*/4, /*y=*/13, /*x=*/1, /*y=*/10,
/*x=*/0, /*y=*/9, /*x=*/3, /*y=*/10, /*x=*/1, /*y=*/12, /*x=*/4, /*y=*/9, /*x=*/5, /*y=*/8, /*x=*/6, /*y=*/9,
/*x=*/3, /*y=*/11, /*x=*/6, /*y=*/10, /*x=*/6, /*y=*/11, /*x=*/4, /*y=*/8, /*x=*/3, /*y=*/9, /*x=*/4, /*y=*/10,
/*x=*/4, /*y=*/11, /*x=*/2, /*y=*/10, /*x=*/0, /*y=*/10, /*x=*/5, /*y=*/10
}, false);
// test top right corner
testGame(2019,
{
/*x=*/19, /*y=*/14, /*x=*/18, /*y=*/13, /*x=*/19, /*y=*/12, /*x=*/18, /*y=*/12, /*x=*/18, /*y=*/11, /*x=*/17, /*y=*/10,
/*x=*/18, /*y=*/14, /*x=*/16, /*y=*/11, /*x=*/18, /*y=*/9, /*x=*/15, /*y=*/12, /*x=*/14, /*y=*/13, /*x=*/15, /*y=*/10,
/*x=*/15, /*y=*/11, /*x=*/14, /*y=*/10, /*x=*/17, /*y=*/12, /*x=*/16, /*y=*/10, /*x=*/13, /*y=*/10, /*x=*/18, /*y=*/10
}, false);
for (uint32 i = 0; i < 10; i++)
testRandomGame(i);
_easierAi = true;
for (uint32 i = 10; i < 20; i++)
testRandomGame(i);
_random.setSeed(oldSeed);
warning("finished PenteGame::test()");
}
bool PenteGame::testGame(uint32 seed, Common::Array<int> moves, bool playerWin) {
byte vars[1024];
byte &winner = vars[5];
byte &op = vars[4];
warning("starting PenteGame::testGame(%u, %u, %d)", seed, moves.size(), (int)playerWin);
memset(vars, 0, sizeof(vars));
_random.setSeed(seed);
op = 0;
run(vars);
for (uint i = 0; i < moves.size(); i += 2) {
if (winner)
error("%u: early winner: %d", i, (int)winner);
int x = moves[i];
int y = moves[i + 1];
if (i % 4) {
// check Stauf's move
op = 3;
run(vars);
byte sX, sY;
varsMoveToXY(vars[0], vars[1], vars[2], sX, sY);
if (sX != x || sY != y)
error("%u: Stauf, expected (%d, %d), got (%d, %d)", i, (int)x, (int)y, (int)sX, (int)sY);
do {
op = 4;
run(vars);
} while (winner == 1);
continue;
}
moveXYToVars(x, y, vars[0], vars[1], vars[2]);
op = 1;
run(vars);
do {
op = 2;
run(vars);
} while (winner == 1);
}
if (playerWin && winner != 3)
error("player didn't win, winner: %d", (int)winner);
else if (playerWin == false && winner != 2)
error("Stauf didn't win, winner: %d", (int)winner);
warning("finished PenteGame::testGame(%u, %u, %d)", seed, moves.size(), (int)playerWin);
return true;
}
void PenteGame::testRandomGame(uint32 seed) {
byte vars[1024];
byte &winner = vars[5];
byte &op = vars[4];
warning("starting PenteGame::testRandomGame(%u)", seed);
memset(vars, 0, sizeof(vars));
_random.setSeed(seed);
op = 0;
run(vars);
while (1) {
// Player makes a random move
int x, y;
do {
x = _random.getRandomNumber(19);
y = _random.getRandomNumber(14);
} while (_table != nullptr && _table->boardState[x][y]);
moveXYToVars(x, y, vars[0], vars[1], vars[2]);
op = 1;
run(vars);
do {
op = 2;
run(vars);
} while (winner == 1);
if (winner)
break;
// Stauf's move
op = 3;
run(vars);
do {
op = 4;
run(vars);
} while (winner == 1);
if (winner)
break;
}
if (winner != 2)
error("Stauf didn't win, winner: %d", (int)winner);
warning("finished PenteGame::testRandomGame(%u)", seed);
}
} // namespace Groovie