892 lines
22 KiB
C++
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
|