Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,840 @@
/* 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/groovie.h"
#include "groovie/logic/beehive.h"
namespace Groovie {
namespace {
extern const int8 beehiveLogicTable1[368];
extern const int8 beehiveLogicTable2[800];
}
void BeehiveGame::overrideClick(byte *vars) {
if (overrideIndex >= overrideMoves.size())
return;
int move = overrideMoves[overrideIndex];
vars[0] = move / 10;
vars[1] = move % 10;
}
void BeehiveGame::overrideMove(byte *vars) {
if (overrideIndex >= overrideMoves.size())
return;
int from = overrideMoves[overrideIndex++];
int to = overrideMoves[overrideIndex++];
vars[0] = from / 10;
vars[1] = from % 10;
vars[2] = to / 10;
vars[3] = to % 10;
}
void BeehiveGame::run(byte *scriptVariables) {
int8 *hexagons = (int8 *)scriptVariables + 25;
int8 *hexDifference = (int8 *)scriptVariables + 13;
byte op = scriptVariables[14] - 1;
enum kBeehiveColor {
kBeehiveColorYellow = -1,
kBeehiveColorRed = 1
};
debugC(1, kDebugLogic, "Beehive subop %d", op);
int8 v21, v22, v24;
int8 tempState[64];
// init hexDifference on every iteration
*hexDifference = 4;
switch (op) {
case 0: // Init board's hexagons
_maxDepth = 4;
memset(_beehiveState, 0, HEXCOUNT);
_beehiveState[0] = kBeehiveColorYellow;
_beehiveState[4] = kBeehiveColorRed;
_beehiveState[34] = kBeehiveColorYellow;
_beehiveState[60] = kBeehiveColorRed;
_beehiveState[56] = kBeehiveColorYellow;
_beehiveState[26] = kBeehiveColorRed;
return;
case 1:
memset(hexagons, 0, HEXCOUNT);
scriptVariables[85] = 0;
sub02(&v22, tempState);
if (v22) {
for (int i = 0; i < v22; i++)
scriptVariables[tempState[i] + 25] = kBeehiveColorRed;
} else {
*hexDifference = getHexDifference();
}
return;
case 2: // Player clicks on a honey-filled (source) hexagon
memset(hexagons, 0, HEXCOUNT);
scriptVariables[85] = 0;
//overrideClick(scriptVariables);
v24 = 10 * scriptVariables[0] + scriptVariables[1];
debugC(2, kDebugLogic, "Beehive player clicked %d", (int)v24);
selectSourceHexagon(v24, &v22, tempState);
for (int j = 0; j < v22; j++)
scriptVariables[tempState[j] + 25] = kBeehiveColorRed;
scriptVariables[v24 + 25] = kBeehiveColorRed;
return;
case 3: // Player moves into an empty (destination) hexagon
scriptVariables[24] = 1;
scriptVariables[4] = 2;
overrideMove(scriptVariables);
v24 = 10 * scriptVariables[0] + scriptVariables[1];
v22 = 10 * scriptVariables[2] + scriptVariables[3];
debugC(1, kDebugLogic, "Beehive player moved from %d, to %d", (int)v24, (int)v22);
sub16(v24, v22, hexDifference, (int8 *)scriptVariables + 16, (int8 *)scriptVariables + 17);
scriptVariables[15] = scriptVariables[16];
sub04(v24, v22, (int8 *)scriptVariables);
return;
case 4: // Stauf plays
scriptVariables[24] = 1;
scriptVariables[4] = 1;
calcStaufMove(&v24, &v22, hexDifference, &v21, (int8 *)scriptVariables + 16, (int8 *)scriptVariables + 17);
// Execute method tail
break;
case 5: // Calculate board state after every move
if (scriptVariables[24] == 1) {
scriptVariables[0] = scriptVariables[2];
scriptVariables[1] = scriptVariables[3];
scriptVariables[24] = 0;
}
if (scriptVariables[16]) {
int8 v16 = scriptVariables[16] - 1;
*hexDifference = 1;
scriptVariables[16] = v16;
v24 = 10 * scriptVariables[0] + scriptVariables[1];
int8 v23 = scriptVariables[v16 + 17];
scriptVariables[2] = v23 / 10;
scriptVariables[3] = v23 % 10;
sub04(v24, v23, (int8 *)scriptVariables);
} else {
*hexDifference = 4 - (scriptVariables[4] == 2 ? 1 : 0);
}
return;
case 6:
scriptVariables[24] = 1;
scriptVariables[4] = 2;
calcSamanthaMove(&v24, &v22, hexDifference, &v21, (int8 *)scriptVariables + 16, (int8 *)scriptVariables + 17);
// Execute method tail
break;
default:
return;
}
if (v24 == -1) {
*hexDifference = getHexDifference();
} else {
scriptVariables[0] = v24 / 10;
scriptVariables[1] = v24 % 10;
scriptVariables[2] = v22 / 10;
scriptVariables[3] = v22 % 10;
sub04(v24, v22, (int8 *)scriptVariables);
}
}
void BeehiveGame::sub02(int8 *a1, int8 *a2) {
int8 v9 = -1;
*a1 = 0;
while (findCell(_beehiveState, &v9, -1)) {
bool v3 = false;
for (int i = 0; i < 6; i++) {
if (v3)
break;
int8 move = beehiveLogicTable1[6 * v9 + i];
if (move != -1 && !_beehiveState[move]) {
a2[*a1] = v9;
v3 = true;
++*a1;
}
}
for (int i = 0; i < 12; i++) {
if (v3)
break;
int8 move = beehiveLogicTable2[12 * v9 + i];
if (move != -1 && !_beehiveState[move]) {
a2[*a1] = v9;
v3 = true;
++*a1;
}
}
}
if (!*a1) {
for (int i = 0; i < HEXCOUNT; ++i)
if (!_beehiveState[i])
_beehiveState[i] = 1;
}
}
void BeehiveGame::sub04(int8 a1, int8 a2, int8 *scriptVariables) {
int v3 = 0;
if (scriptVariables[13] == 1) {
if (beehiveLogicTable1[6 * a1] != a2) {
for (; v3 < 5; v3++) {
if (beehiveLogicTable1[6 * a1 + v3] == a2)
break;
}
}
int v7 = v3 + 12;
scriptVariables[5] = v7 / 10;
scriptVariables[6] = v7 % 10;
return;
}
scriptVariables[10] = 0;
scriptVariables[7] = 0;
if (beehiveLogicTable2[12 * a1] != a2) {
for (; v3 < 11; v3++) {
if (beehiveLogicTable2[12 * a1 + v3] == a2)
break;
}
}
scriptVariables[5] = v3 / 10;
int8 v5 = -1;
int8 v6 = -1;
scriptVariables[6] = v3 % 10;
switch (v3) {
case 0:
v6 = beehiveLogicTable1[6 * a1];
break;
case 1:
v5 = beehiveLogicTable1[6 * a1];
// fall through
case 2:
v6 = beehiveLogicTable1[6 * a1 + 1];
break;
case 3:
v5 = beehiveLogicTable1[6 * a1 + 1];
// fall through
case 4:
v6 = beehiveLogicTable1[6 * a1 + 2];
break;
case 5:
v5 = beehiveLogicTable1[6 * a1 + 2];
// fall through
case 6:
v6 = beehiveLogicTable1[6 * a1 + 3];
break;
case 7:
v5 = beehiveLogicTable1[6 * a1 + 3];
// fall through
case 8:
v6 = beehiveLogicTable1[6 * a1 + 4];
break;
case 9:
v5 = beehiveLogicTable1[6 * a1 + 4];
// fall through
case 10:
v6 = beehiveLogicTable1[6 * a1 + 5];
break;
case 11:
v6 = beehiveLogicTable1[6 * a1 + 5];
v5 = beehiveLogicTable1[6 * a1];
break;
default:
v6 = 0;
break;
}
int8 v4 = 0;
if (v5 != -1)
v4 = _beehiveState[v5];
if (_beehiveState[v6]) {
scriptVariables[8] = v6 / 10;
scriptVariables[9] = v6 % 10;
scriptVariables[7] = 2 - (_beehiveState[v6] == 1 ? 1 : 0);
}
if (v4) {
scriptVariables[11] = v5 / 10;
scriptVariables[12] = v5 % 10;
scriptVariables[10] = 2 - (v4 == 1 ? 1 : 0);
}
}
void BeehiveGame::calcSamanthaMove(int8 *a1, int8 *a2, int8 *a3, int8 *a4, int8 *a5, int8 *a6) {
int8 params[4];
*a4 = 0;
_maxDepth = 5;// in the original game Samantha did 4 like Stauf
if (calcMove(_beehiveState, -125, -1, _maxDepth, 0, params) == 125
&& (*a4 = 1, calcMove(_beehiveState, -125, -1, _maxDepth, 1, params) == 125)) {
*a1 = -1;
*a2 = -1;
for (int i = 0; i < HEXCOUNT; ++i) {
if (!_beehiveState[i])
_beehiveState[i] = 1;
}
} else {
*a1 = params[1];
*a2 = params[2];
*a3 = params[0];
sub17(_beehiveState, -1, params, a5, a6);
}
}
void BeehiveGame::calcStaufMove(int8 *a1, int8 *a2, int8 *a3, int8 *a4, int8 *a5, int8 *a6) {
int8 params[4];
*a4 = 0;
_maxDepth = 4;
if (_easierAi) {
int numPieces = 0;
for (int i = 0; i < HEXCOUNT; ++i)
numPieces += _beehiveState[i] != 0;
if (numPieces < 16)
_maxDepth = 3;
else
_maxDepth = 1;
}
if (calcMove(_beehiveState, 125, 1, _maxDepth, 0, params) == -125
&& (*a4 = 1, calcMove(_beehiveState, 125, 1, _maxDepth, 1, params) == -125)) {
*a1 = -1;
*a2 = -1;
for (int i = 0; i < HEXCOUNT; ++i) {
if (!_beehiveState[i])
_beehiveState[i] = -1;
}
} else {
*a1 = params[1];
*a2 = params[2];
*a3 = params[0];
sub17(_beehiveState, 1, params, a5, a6);
}
}
int8 BeehiveGame::sub11(int8 *beehiveState, int8 *a2, int8 *a3, int8 *a4, int8 a5, int8 a6, int8 *a7) {
if (*a2 == -1) {
if (!findCell(beehiveState, a2, a5))
return 0;
}
int8 v16 = 0;
while (1) {
while (1) {
if (v16)
return 1;
for (; *a3 < 6; (*a3)++) {
if (v16)
break;
int8 v9 = beehiveLogicTable1[6 * *a2 + *a3];
if (v9 != -1 && !beehiveState[v9] && *a2 < sub12(beehiveState, a5, v9, *a2)) {
v16 = 1;
*a7 = 1;
a7[1] = *a2;
a7[2] = beehiveLogicTable1[6 * *a2 + *a3];
}
}
if (*a4 >= 12)
break;
while (!v16) {
int8 v11 = beehiveLogicTable2[12 * *a2 + *a4];
if (v11 != -1
&& !beehiveState[v11]
&& !sub13(beehiveState, v11, a5)
&& sub13(beehiveState, beehiveLogicTable2[12 * *a2 + *a4], -a5)) {
int8 v12 = sub13(beehiveState, *a2, -a5);
int8 v13 = *a4 >> 1;
int8 v14 = ~(1 << v13) & v12;
if ((*a4 & 1) != 0) {
if (v13 == 5)
v14 &= ~1u;
else
v14 &= ~(1 << (v13 + 1));
}
if (!v14 || !sub13(beehiveState, *a2, a5) || a6) {
v16 = 1;
*a7 = 2;
a7[1] = *a2;
a7[2] = beehiveLogicTable2[12 * *a2 + *a4];
}
}
(*a4)++;
if (*a4 >= 12)
break;
}
if (*a4 >= 12)
break;
}
if (v16)
return 1;
if (!findCell(beehiveState, a2, a5))
return 0;
*a3 = 0;
*a4 = 0;
}
}
int8 BeehiveGame::sub12(int8 *beehiveState, int8 a2, int8 a3, int8 a4) {
int8 result = 125;
for (int i = 0; i < 6; i++) {
int8 v7 = beehiveLogicTable1[i + 6 * a3];
if (v7 != -1 && beehiveState[v7] == a2 && a4 != v7 && result > v7)
result = beehiveLogicTable1[i + 6 * a3];
}
return result;
}
int8 BeehiveGame::sub13(int8 *beehiveState, int8 a2, int8 a3) {
int result = 0;
for (int i = 0; i < 6; i++) {
int8 v5 = beehiveLogicTable1[6 * a2 + i];
if (v5 != -1 && beehiveState[v5] == a3)
result |= 1 << i;
}
return result;
}
void BeehiveGame::sub15(int8 *beehiveState, int8 a2, int8 *a3) {
beehiveState[a3[2]] = a2;
if (*a3 == 2)
beehiveState[a3[1]] = 0;
for (int i = 0; i < 6; ++i) {
int8 v4 = beehiveLogicTable1[6 * a3[2] + i];
if (v4 != -1) {
if (!(a2 + beehiveState[v4]))
beehiveState[v4] = a2;
}
}
}
void BeehiveGame::sub16(int8 a1, int8 a2, int8 *a3, int8 *a4, int8 *a5) {
int8 params[4];
params[0] = sub19(a1, a2);
params[1] = a1;
params[2] = a2;
*a3 = params[0];
sub17(_beehiveState, -1, params, a4, a5);
}
void BeehiveGame::sub17(int8 *beehiveState, int8 a2, int8 *a3, int8 *a4, int8 *a5) {
beehiveState[a3[2]] = a2;
if (*a3 == 2)
beehiveState[a3[1]] = 0;
*a4 = 0;
for (int i = 0; i < 6; i++) {
int8 v6 = beehiveLogicTable1[6 * a3[2] + i];
if (v6 != -1) {
if (!(a2 + beehiveState[v6])) {
beehiveState[v6] = a2;
a5[(*a4)++] = beehiveLogicTable1[6 * a3[2] + i];
}
}
}
}
void BeehiveGame::selectSourceHexagon(int8 a1, int8 *a2, int8 *a3) {
*a2 = 0;
for (int i = 0; i < 6; i++) {
int8 val = beehiveLogicTable1[6 * a1 + i];
if (val != -1 && !_beehiveState[val])
a3[(*a2)++] = val;
}
for (int i = 0; i < 12; i++) {
int val = beehiveLogicTable2[12 * a1 + i];
if (val != -1 && !_beehiveState[val])
a3[(*a2)++] = val;
}
}
int8 BeehiveGame::sub19(int8 a1, int8 a2) {
for (int i = 0; i < 6; i++)
if (beehiveLogicTable1[6 * a1 + i] == a2)
return 1;
return 2;
}
int8 BeehiveGame::calcMove(int8 *beehiveState, int8 a2, int8 a3, int8 depth, int a5, int8 *params) {
int8 paramsloc[4];
int8 params2[3];
int8 state[64];
if (!depth)
return getTotal(beehiveState);
int8 v7 = -125 * a3;
int8 v14 = 0;
int8 v13 = 0;
int8 v15 = -1;
if (sub11(beehiveState, &v15, &v14, &v13, a3, a5, params2)) {
do {
for (int i = 0; i < HEXCOUNT; i++)
state[i] = beehiveState[i];
sub15(state, a3, params2);
int8 v8 = calcMove(state, v7, -a3, depth - 1, a5, paramsloc);
if (a3 <= 0) {
if (v8 < v7) {
params[0] = params2[0];
params[1] = params2[1];
params[2] = params2[2];
v7 = v8;
}
if (a2 >= v7)
return v7;
} else {
if (v8 > v7) {
params[0] = params2[0];
params[1] = params2[1];
params[2] = params2[2];
v7 = v8;
}
if (a2 <= v7)
return v7;
}
} while (sub11(beehiveState, &v15, &v14, &v13, a3, a5, params2));
}
if (depth < _maxDepth && -125 * a3 == v7)
return getTotal(beehiveState);
else
return v7;
}
int8 BeehiveGame::getHexDifference() {
return (getTotal(_beehiveState) >= 0 ? 1 : 0) + 5;
}
int8 BeehiveGame::getTotal(int8 *hexagons) {
int8 result = 0;
for (int i = 0; i < HEXCOUNT; i++)
result += hexagons[i];
return result;
}
int8 BeehiveGame::findCell(int8 *beehiveState, int8 *pos, int8 key) {
for (int i = *pos + 1; i < HEXCOUNT; i++) {
if (beehiveState[i] == key) {
*pos = i;
return 1;
}
}
return 0;
}
namespace {
const int8 beehiveLogicTable1[368] = {
-1, 5, 6, 1, -1, -1,
0, 6, 7, 2, -1, -1,
1, 7, 8, 3, -1, -1,
2, 8, 9, 4, -1, -1,
3, 9, 10, -1, -1, -1,
-1, 11, 12, 6, 0, -1,
5, 12, 13, 7, 1, 0,
6, 13, 14, 8, 2, 1,
7, 14, 15, 9, 3, 2,
8, 15, 16, 10, 4, 3,
9, 16, 17, -1, -1, 4,
-1, 18, 19, 12, 5, -1,
11, 19, 20, 13, 6, 5,
12, 20, 21, 14, 7, 6,
13, 21, 22, 15, 8, 7,
14, 22, 23, 16, 9, 8,
15, 23, 24, 17, 10, 9,
16, 24, 25, -1, -1, 10,
-1, 26, 27, 19, 11, -1,
18, 27, 28, 20, 12, 11,
19, 28, 29, 21, 13, 12,
20, 29, 30, 22, 14, 13,
21, 30, 31, 23, 15, 14,
22, 31, 32, 24, 16, 15,
23, 32, 33, 25, 17, 16,
24, 33, 34, -1, -1, 17,
-1, -1, 35, 27, 18, -1,
26, 35, 36, 28, 19, 18,
27, 36, 37, 29, 20, 19,
28, 37, 38, 30, 21, 20,
29, 38, 39, 31, 22, 21,
30, 39, 40, 32, 23, 22,
31, 40, 41, 33, 24, 23,
32, 41, 42, 34, 25, 24,
33, 42, -1, -1, -1, 25,
-1, -1, 43, 36, 27, 26,
35, 43, 44, 37, 28, 27,
36, 44, 45, 38, 29, 28,
37, 45, 46, 39, 30, 29,
38, 46, 47, 40, 31, 30,
39, 47, 48, 41, 32, 31,
40, 48, 49, 42, 33, 32,
41, 49, -1, -1, 34, 33,
-1, -1, 50, 44, 36, 35,
43, 50, 51, 45, 37, 36,
44, 51, 52, 46, 38, 37,
45, 52, 53, 47, 39, 38,
46, 53, 54, 48, 40, 39,
47, 54, 55, 49, 41, 40,
48, 55, -1, -1, 42, 41,
-1, -1, 56, 51, 44, 43,
50, 56, 57, 52, 45, 44,
51, 57, 58, 53, 46, 45,
52, 58, 59, 54, 47, 46,
53, 59, 60, 55, 48, 47,
54, 60, -1, -1, 49, 48,
-1, -1, -1, 57, 51, 50,
56, -1, -1, 58, 52, 51,
57, -1, -1, 59, 53, 52,
58, -1, -1, 60, 54, 53,
59, -1, -1, -1, 55, 54,
0, 0
};
const int8 beehiveLogicTable2[800] = {
-1, -1, 11, 12, 13, 7, 2, -1, -1, -1, -1, -1,
-1, 5, 12, 13, 14, 8, 3, -1, -1, -1, -1, -1,
0, 6, 13, 14, 15, 9, 4, -1, -1, -1, -1, -1,
1, 7, 14, 15, 16, 10, -1, -1, -1, -1, -1, -1,
2, 8, 15, 16, 17, -1, -1, -1, -1, -1, -1, -1,
-1, -1, 18, 19, 20, 13, 7, 1, -1, -1, -1, -1,
-1, 11, 19, 20, 21, 14, 8, 2, -1, -1, -1, -1,
5, 12, 20, 21, 22, 15, 9, 3, -1, -1, -1, 0,
6, 13, 21, 22, 23, 16, 10, 4, -1, -1, -1, 1,
7, 14, 22, 23, 24, 17, -1, -1, -1, -1, -1, 2,
8, 15, 23, 24, 25, -1, -1, -1, -1, -1, -1, 3,
-1, -1, 26, 27, 28, 20, 13, 6, 0, -1, -1, -1,
-1, 18, 27, 28, 29, 21, 14, 7, 1, 0, -1, -1,
11, 19, 28, 29, 30, 22, 15, 8, 2, 1, 0, 5,
12, 20, 29, 30, 31, 23, 16, 9, 3, 2, 1, 6,
13, 21, 30, 31, 32, 24, 17, 10, 4, 3, 2, 7,
14, 22, 31, 32, 33, 25, -1, -1, -1, 4, 3, 8,
15, 23, 32, 33, 34, -1, -1, -1, -1, -1, 4, 9,
-1, -1, -1, 35, 36, 28, 20, 12, 5, -1, -1, -1,
-1, 26, 35, 36, 37, 29, 21, 13, 6, 5, -1, -1,
18, 27, 36, 37, 38, 30, 22, 14, 7, 6, 5, 11,
19, 28, 37, 38, 39, 31, 23, 15, 8, 7, 6, 12,
20, 29, 38, 39, 40, 32, 24, 16, 9, 8, 7, 13,
21, 30, 39, 40, 41, 33, 25, 17, 10, 9, 8, 14,
22, 31, 40, 41, 42, 34, -1, -1, -1, 10, 9, 15,
23, 32, 41, 42, -1, -1, -1, -1, -1, -1, 10, 16,
-1, -1, -1, -1, 43, 36, 28, 19, 11, -1, -1, -1,
-1, -1, -1, 43, 44, 37, 29, 20, 12, 11, -1, -1,
26, 35, 43, 44, 45, 38, 30, 21, 13, 12, 11, 18,
27, 36, 44, 45, 46, 39, 31, 22, 14, 13, 12, 19,
28, 37, 45, 46, 47, 40, 32, 23, 15, 14, 13, 20,
29, 38, 46, 47, 48, 41, 33, 24, 16, 15, 14, 21,
30, 39, 47, 48, 49, 42, 34, 25, 17, 16, 15, 22,
31, 40, 48, 49, -1, -1, -1, -1, -1, 17, 16, 23,
32, 41, 49, -1, -1, -1, -1, -1, -1, -1, 17, 24,
-1, -1, -1, -1, 50, 44, 37, 28, 19, 18, -1, -1,
-1, -1, -1, 50, 51, 45, 38, 29, 20, 19, 18, 26,
35, 43, 50, 51, 52, 46, 39, 30, 21, 20, 19, 27,
36, 44, 51, 52, 53, 47, 40, 31, 22, 21, 20, 28,
37, 45, 52, 53, 54, 48, 41, 32, 23, 22, 21, 29,
38, 46, 53, 54, 55, 49, 42, 33, 24, 23, 22, 30,
39, 47, 54, 55, -1, -1, -1, 34, 25, 24, 23, 31,
40, 48, 55, -1, -1, -1, -1, -1, -1, 25, 24, 32,
-1, -1, -1, -1, 56, 51, 45, 37, 28, 27, 26, -1,
-1, -1, -1, 56, 57, 52, 46, 38, 29, 28, 27, 35,
43, 50, 56, 57, 58, 53, 47, 39, 30, 29, 28, 36,
44, 51, 57, 58, 59, 54, 48, 40, 31, 30, 29, 37,
45, 52, 58, 59, 60, 55, 49, 41, 32, 31, 30, 38,
46, 53, 59, 60, -1, -1, -1, 42, 33, 32, 31, 39,
47, 54, 60, -1, -1, -1, -1, -1, 34, 33, 32, 40,
-1, -1, -1, -1, -1, 57, 52, 45, 37, 36, 35, -1,
-1, -1, -1, -1, -1, 58, 53, 46, 38, 37, 36, 43,
50, 56, -1, -1, -1, 59, 54, 47, 39, 38, 37, 44,
51, 57, -1, -1, -1, 60, 55, 48, 40, 39, 38, 45,
52, 58, -1, -1, -1, -1, -1, 49, 41, 40, 39, 46,
53, 59, -1, -1, -1, -1, -1, -1, 42, 41, 40, 47,
-1, -1, -1, -1, -1, -1, 58, 52, 45, 44, 43, -1,
-1, -1, -1, -1, -1, -1, 59, 53, 46, 45, 44, 50,
56, -1, -1, -1, -1, -1, 60, 54, 47, 46, 45, 51,
57, -1, -1, -1, -1, -1, -1, 55, 48, 47, 46, 52,
58, -1, -1, -1, -1, -1, -1, -1, 49, 48, 47, 53,
0, 0, 0, 0, 26, 18, 35, 11, 27, 43, 5, 19,
36, 50, 0, 12, 28, 44, 56, 6, 20, 37, 51, 1,
13, 29, 45, 57, 7, 21, 38, 52, 2, 14, 30, 46,
58, 8, 22, 39, 53, 3, 15, 31, 47, 59, 9, 23,
40, 54, 4, 16, 32, 48, 60, 10, 24, 41, 55, 17,
33, 49, 25, 42, 34, 0, 0, 0
};
} // End of anonymous namespace
void BeehiveGame::testGame(Common::Array<int> moves, bool playerWin) {
byte vars[1024];
memset(vars, 0, sizeof(vars));
int8 &hexDifference = ((int8 *)vars)[13];
byte &op = vars[14];// can't do the -1 with a reference
byte &counter = vars[16];
op = 1;
run(vars);
op = 2;
run(vars);
for (uint i = 0; i < moves.size(); i += 2) {
int from = moves[i];
int to = moves[i + 1];
op = 3;
vars[0] = from / 10;
vars[1] = from % 10;
run(vars);
op = 4;
vars[0] = from / 10;
vars[1] = from % 10;
vars[2] = to / 10;
vars[3] = to % 10;
run(vars);
while (counter) {
op = 6;
run(vars);
}
op = 6;
run(vars);
if (i + 2 < moves.size() && hexDifference == 6) {
error("early Stauf win");
} else if (i + 2 < moves.size() && hexDifference == 5) {
error("early player win");
}
op = 5;
run(vars);
while (counter) {
op = 6;
run(vars);
}
op = 6;
run(vars);
op = 2;
run(vars);
if (i + 2 < moves.size() && hexDifference == 6) {
error("early Stauf win");
} else if (i + 2 < moves.size() && hexDifference == 5) {
error("early player win");
}
}
if (playerWin && hexDifference != 5)
error("player didn't win");
if (playerWin == false && hexDifference != 6)
error("Stauf didn't win");
}
void BeehiveGame::tests() {
warning("starting BeehiveGame::tests()");
// 8 moves per line, in from and to pairs
// speedrun strat
testGame({
/**/ 34, 42, /**/ 56, 50, /**/ 50, 35, /**/ 42, 55, /**/ 34, 42, /**/ 42, 49, /**/ 35, 43, /**/ 43, 50,
/**/ 50, 51, /**/ 51, 52, /**/ 52, 53, /**/ 53, 54, /**/ 52, 57, /**/ 52, 46, /**/ 34, 25, /**/ 34, 24,
/**/ 25, 23, /**/ 46, 31, /**/ 31, 30, /**/ 52, 38, /**/ 29, 12, /**/ 31, 39, /**/ 35, 28, /**/ 49, 32,
/**/ 31, 40, /**/ 39, 47, /**/ 20, 19, /**/ 29, 37, /**/ 57, 58, /**/ 53, 46, /**/ 53, 52
}, true);
// losing game
testGame({
/**/ 34, 25, /**/ 25, 10, /**/ 34, 17, /**/ 0, 2, /**/ 56, 57, /**/ 57, 51, /**/ 51, 50, /**/ 51, 52,
/**/ 51, 44, /**/ 50, 43, /**/ 50, 35, /**/ 36, 38, /**/ 35, 37, /**/ 38, 39, /**/ 38, 29, /**/ 45, 58,
/**/ 58, 59, /**/ 57, 45, /**/ 44, 35, /**/ 35, 26, /**/ 46, 54, /**/ 59, 60, /**/ 59, 55, /**/ 55, 40,
/**/ 39, 23
}, false);
// copy the moveset from one of the tests to play it out yourself
overrideMoves = {};
overrideIndex = 0;
warning("finished BeehiveGame::tests()");
}
} // End of Groovie namespace

View File

@@ -0,0 +1,90 @@
/* 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.
*
*/
#ifndef GROOVIE_LOGIC_BEEHIVE_H
#define GROOVIE_LOGIC_BEEHIVE_H
#include "common/system.h"
namespace Groovie {
/*
* Beehive (Blood and Honey) puzzle (hs.grv)
*
* An infection-style game in which the player must cover more
* territory than the computer. It's similar to the microscope puzzle
* in the 7th Guest. The playfield is a honeycomb made of 61
* hexagons. The hexagons are numbered starting from the top-left
* corner, with a direction from bottom left to top right.
*/
class BeehiveGame {
public:
BeehiveGame(bool easierAi) {
#if 0
_easierAi = false;
tests();
#endif
_easierAi = easierAi;
}
~BeehiveGame() {}
void run(byte *scriptVariables);
private:
void sub02(int8 *a1, int8 *a2);
void sub04(int8 a1, int8 a2, int8 *scriptVariables);
void calcSamanthaMove(int8 *a1, int8 *a2, int8 *a3, int8 *a4, int8 *a5, int8 *a6);
void calcStaufMove(int8 *a1, int8 *a2, int8 *a3, int8 *a4, int8 *a5, int8 *a6);
int8 sub11(int8 *beehiveState, int8 *a2, int8 *a3, int8 *a4, int8 a5, int8 a6, int8 *a7);
int8 sub12(int8 *beehiveState, int8 a2, int8 a3, int8 a4);
int8 sub13(int8 *beehiveState, int8 a2, int8 a3);
void sub15(int8 *beehiveState, int8 a2, int8 *a3);
void sub16(int8 a1, int8 a2, int8 *a3, int8 *a4, int8 *a5);
void sub17(int8 *beehiveState, int8 a2, int8 *a3, int8 *a4, int8 *a5);
void selectSourceHexagon(int8 a1, int8 *a2, int8 *a3);
int8 sub19(int8 a1, int8 a2);
int8 getHexDifference();
int8 getTotal(int8 *hexagons);
int8 calcMove(int8 *beehiveState, int8 a2, int8 a3, int8 depth, int a5, int8 *a6);
int8 findCell(int8 *beehiveState, int8 *pos, int8 key);
void testGame(Common::Array<int> moves, bool playerWin);
void tests();
void overrideClick(byte *vars);
void overrideMove(byte *vars);
#define HEXCOUNT 61
int8 _beehiveState[HEXCOUNT];
Common::Array<int> overrideMoves;
uint overrideIndex;
bool _easierAi;
int8 _maxDepth;
};
} // End of Groovie namespace
#endif // GROOVIE_LOGIC_BEEHIVE_H

View File

@@ -0,0 +1,427 @@
/* 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/groovie.h"
#include "groovie/logic/cake.h"
namespace Groovie {
/*
* T11hCake() constructor
* - Each spot on the board is part of multiple potential victory lines
* - The first x and y dimensions of the loops select the origin point of the line
* - The z is for the distance along that line
* - Then we push_back the id number of the line into the array at _map.indecies[x][y]
* - This is used in UpdateScores()
* .
* @see UpdateScores()
*/
CakeGame::CakeGame(bool easierAi) : _random("CakeGame") {
restart();
_map = {};
int numLines = 0;
// map all the lines with slope of (1, 0)
for (int y = 0; y < HEIGHT; y++) {
for (int x = 0; x <= WIDTH - GOAL_LEN; x++) {
for (int z = 0; z < GOAL_LEN; z++) {
setLineNum(x + z, y, numLines);
}
numLines++;
}
}
// map all the lines with slope of (0, 1)
for (int x = 0; x < WIDTH; x++) {
for (int y = 0; y <= HEIGHT - GOAL_LEN; y++) {
for (int z = 0; z < GOAL_LEN; z++) {
setLineNum(x, y + z, numLines);
}
numLines++;
}
}
// map all the lines with slope of (1,1)
for (int y = 0; y <= HEIGHT - GOAL_LEN; y++) {
for (int x = 0; x <= WIDTH - GOAL_LEN; x++) {
for (int z = 0; z < GOAL_LEN; z++) {
setLineNum(x + z, y + z, numLines);
}
numLines++;
}
}
// map all the lines with slope of (1,-1)
for (int y = GOAL_LEN - 1; y < HEIGHT; y++) {
for (int x = 0; x <= WIDTH - GOAL_LEN; x++) {
for (int z = 0; z < GOAL_LEN; z++) {
setLineNum(x + z, y - z, numLines);
}
numLines++;
}
}
#if 0
_easierAi = false;
testCake();
#endif
_easierAi = easierAi;
}
void CakeGame::run(byte *scriptVariables) {
byte &lastMove = scriptVariables[1];
byte &winner = scriptVariables[3];
winner = 0;
if (lastMove == 8) {
restart();
return;
}
if (lastMove == 9) {
// samantha makes a move
lastMove = aiGetBestMove(6);
_hasCheated = true;
return;
}
if (isColumnFull(lastMove)) {
warning("player tried to place a bon bon in a full column, last_move: %d", (int)lastMove);
lastMove = 10;
return;
}
placeBonBon(lastMove);
winner = getWinner();
if (winner) {
return;
}
int depth = 4 + (_hasCheated == false);
if (_easierAi && _moveCount > 8)
depth = 3;
else if (_easierAi)
depth = 2;
lastMove = aiGetBestMove(depth);
placeBonBon(lastMove);
if (gameEnded()) {
winner = STAUF;
}
}
void CakeGame::restart() {
_playerProgress = {};
_staufProgress = {};
memset(_boardState, 0, sizeof(_boardState));
memset(_columnHeights, 0, sizeof(_columnHeights));
_moveCount = 0;
_hasCheated = false;
_playerProgress._score = NUM_LINES;
_staufProgress._score = NUM_LINES;
}
void CakeGame::setLineNum(uint x, uint y, uint index) {
assert(x < WIDTH);
assert(y < HEIGHT);
byte slot = _map.lengths[x][y]++;
assert(slot < GOAL_LEN * GOAL_LEN);
assert(index < NUM_LINES);
_map.indecies[x][y][slot] = index;
}
bool CakeGame::isColumnFull(byte column) {
return _columnHeights[column] >= HEIGHT;
}
CakeGame::PlayerProgress &CakeGame::getPlayerProgress(bool stauf) {
if (stauf)
return _staufProgress;
else
return _playerProgress;
}
/*
* UpdateScores()
* - Each PlayerProgress has an array of ints, _linesCounters[], where each entry maps to the ID of a line
* - When a bon bon is added to the board, we look up _map.lengths[x][y] and then loop through all the indecies for that point
* - Increment the PlayerProgress._linesCounters[id]
* - Calculate the scores proportional to the PlayerProgress._linesCounters[id]
* .
* .
*/
void CakeGame::updateScores(byte x, bool revert) {
bool stauf = _moveCount % 2;
PlayerProgress &pp = getPlayerProgress(stauf);
byte y = _columnHeights[x] - 1;
// get the number of potential victory lines that this spot exists in
int num_lines = _map.lengths[x][y];
for (int line = 0; line < num_lines; line++) {
// get the ID for this potential victory line
int index = _map.indecies[x][y][line];
int len = pp._linesCounters[index];
// add this new bon bon to the progress of this potential victory line, or remove in the case of revert
int mult = 1;// mult is used for multiplying the score gains, depends on revert
if (!revert)
pp._linesCounters[index]++;
else {
len = --pp._linesCounters[index];
mult = -1;
}
if (GOAL_LEN == len + 1) {
// that's a bingo
pp._score += WIN_SCORE * mult;
}
else {
PlayerProgress &pp2 = getPlayerProgress(!stauf);
int len2 = pp2._linesCounters[index];
if (len == 0) {
// we started a new line, take away the points the opponent had from this line since we ruined it for them
pp2._score -= (1 << (len2 & 31)) * mult;
}
if (len2 == 0) {
// the opponent doesn't have any spots in this line, so we get points for it
pp._score += (1 << (len & 31)) * mult;
}
}
}
}
void CakeGame::placeBonBon(byte x) {
byte y = _columnHeights[x]++;
if (_moveCount % 2)
_boardState[x][y] = STAUF;
else
_boardState[x][y] = PLAYER;
updateScores(x);
_moveCount++;
}
void CakeGame::revertMove(byte x) {
// PlaceBonBon in reverse, this is used for the AI's recursion rollback
_moveCount--;
updateScores(x, true);
byte y = --_columnHeights[x];
_boardState[x][y] = 0;
}
byte CakeGame::getWinner() {
if (_playerProgress._score >= WIN_SCORE)
return PLAYER;
if (_staufProgress._score >= WIN_SCORE)
return STAUF;
return 0;
}
bool CakeGame::gameEnded() {
if (getWinner())
return true;
if (_moveCount >= WIDTH * HEIGHT)
return true;
return false;
}
int CakeGame::getScoreDiff() {
if (_moveCount % 2)
return _staufProgress._score - _playerProgress._score;
else
return _playerProgress._score - _staufProgress._score;
}
int CakeGame::aiRecurse(int search_depth, int parent_score) {
int best_score = 0x7fffffff;
for (byte move = 0; move < WIDTH; move++) {
if (isColumnFull(move))
continue;
placeBonBon(move);
int score = getScoreDiff();
if (search_depth > 1 && !gameEnded())
score = aiRecurse(search_depth - 1, best_score);
revertMove(move);
if (score < best_score)
best_score = score;
if (-parent_score != best_score && parent_score <= -best_score)
break;
}
// we negate the score because from the perspective of our parent caller, this is his opponent's score
return -best_score;
}
byte CakeGame::aiGetBestMove(int search_depth) {
int best_move = 0xffff;
uint counter = 1;
for (int best_score = 0x7fffffff; best_score > 999999 && search_depth > 1; search_depth--) {
for (byte move = 0; move < WIDTH; move++) {
if (isColumnFull(move))
continue;
placeBonBon(move);
if (getWinner()) {
revertMove(move);
return move;
}
int score = aiRecurse(search_depth - 1, best_score);
revertMove(move);
if (score < best_score) {
counter = 1;
best_move = move;
best_score = score;
} else if (best_score == score) {
// rng is only used on moves with equal scores
counter++;
uint r = _random.getRandomNumber(1000000 - 1);
if (r * counter < 1000000) {
best_move = move;
}
}
}
}
return best_move;
}
void CakeGame::testCake() {
warning("starting CakeGame::testCake()");
uint32 oldSeed = _random.getSeed();
// test the draw condition, grouped by column
runCakeTestNoAi(/*move 1*/ "7777777" /*8*/ "6666666" /*15*/ "5555555" /*22*/ "34444444" /*30*/ "333333" /*36*/ "2222222" /*43*/ "01111111" /*51*/ "000000", false, true);
runCakeTest(9, "24223233041", true);
runCakeTest(1, "232232432445", false);
runCakeTest(123, "4453766355133466", false);
_random.setSeed(oldSeed);
warning("finished CakeGame::testCake()");
}
void CakeGame::runCakeTestNoAi(const char *moves, bool playerWin, bool draw = false) {
warning("starting runCakeTestNoAi(%s, %d)", moves, (int)playerWin);
restart();
for (int i = 0; moves[i]; i++) {
byte win = getWinner();
if (win) {
error("early win at %d, winner: %d", i, (int)win);
}
if (gameEnded()) {
error("early draw at %d", i);
}
byte move = moves[i] - '0';
placeBonBon(move);
}
byte winner = getWinner();
if (draw) {
if (winner != 0 || !gameEnded())
error("wasn't a draw! winner: %d, gameover: %d", (int)winner, (int)gameEnded());
} else if (playerWin && winner != PLAYER) {
error("player didn't win! winner: %d", (int)winner);
} else if (playerWin == false && winner != STAUF) {
error("Stauf didn't win! winner: %d", (int)winner);
}
warning("finished runCakeTestNoAi(%s, %d), winner: %d", moves, (int)playerWin, (int)winner);
}
void CakeGame::runCakeTest(uint seed, const char *moves, bool playerWin) {
warning("starting runCakeTest(%u, %s, %d)", seed, moves, (int)playerWin);
// first fill the board with the expected moves and test the win-detection function by itself without AI
runCakeTestNoAi(moves, playerWin);
restart();
byte vars[1024];
memset(vars, 0, sizeof(vars));
byte &lastMove = vars[1];
byte &winner = vars[3];
winner = 0;
lastMove = 8;
run(vars);
uint old_seed = _random.getSeed();
_random.setSeed(seed);
for (int i = 0; moves[i]; i += 2) {
if (winner != 0) {
error("early win at %d, winner: %d", i, (int)winner);
}
lastMove = moves[i] - '0';
byte stauf_move = moves[i + 1] - '0';
run(vars);
if (stauf_move < 8) {
if (winner == 2) {
error("early player win at %d", i);
}
if (stauf_move != lastMove) {
error("incorrect Stauf move, expected: %d, got: %d", (int)stauf_move, (int)lastMove);
}
} else if (winner != 2) {
error("missing Stauf move, last_move: %d", (int)lastMove);
} else
break;
}
if (playerWin && winner != 2) {
error("player didn't win! winner: %d", (int)winner);
} else if (playerWin == false && winner != 1) {
error("Stauf didn't win! winner: %d", (int)winner);
}
_random.setSeed(old_seed);
warning("finished runCakeTest(%u, %s, %d)", seed, moves, (int)playerWin);
}
} // End of Groovie namespace

View File

@@ -0,0 +1,98 @@
/* 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.
*
*/
#ifndef GROOVIE_LOGIC_CAKE_H
#define GROOVIE_LOGIC_CAKE_H
#include "common/random.h"
#include "common/system.h"
namespace Groovie {
/*
* Cake (Connect Four) puzzle in the dining room (tb.grv)
*/
class CakeGame {
public:
CakeGame(bool easierAi);
void run(byte *scriptVariables);
private:
static const int WIDTH = 8;
static const int HEIGHT = 7;
static const int GOAL_LEN = 4;
static const int WIN_SCORE = 1000000;//!< the number of points added for a connect four
static const byte STAUF = 1;
static const byte PLAYER = 2;
static const int NUM_LINES = 107;//!< how many potential victory lines there are
Common::RandomSource _random;
//! ID numbers for all of the potential victory lines for each spot on the board
struct LinesMappings {
byte lengths[WIDTH][HEIGHT];
byte indecies[WIDTH][HEIGHT][GOAL_LEN * GOAL_LEN];
};
//! how many points a player has, and their progress on potential victory lines
struct PlayerProgress {
int _score;
int _linesCounters[NUM_LINES];//!< how many pieces are claimed in each potential victory, links to LineMappings, an entry of 4 means that's a victory
};
PlayerProgress _playerProgress;
PlayerProgress _staufProgress;
byte _boardState[WIDTH][HEIGHT];//!< (0, 0) is the bottom left of the board
byte _columnHeights[WIDTH];
int _moveCount;
bool _hasCheated;
LinesMappings _map;//!< ID numbers for all of the potential victory lines for each spot on the board
bool _easierAi;
void restart();
void setLineNum(uint x, uint y, uint index);
bool isColumnFull(byte column);
PlayerProgress &getPlayerProgress(bool stauf);
void updateScores(byte x, bool revert = false);
void placeBonBon(byte x);
void revertMove(byte x);
byte getWinner();
bool gameEnded();
int getScoreDiff();
int aiRecurse(int search_depth, int parent_score);
byte aiGetBestMove(int search_depth);
void testCake();
void runCakeTest(uint seed, const char *moves, bool player_win);
void runCakeTestNoAi(const char *moves, bool player_win, bool draw);
};
} // End of Groovie namespace
#endif // GROOVIE_LOGIC_CAKE_H

View File

@@ -0,0 +1,800 @@
/* 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/cell.h"
#include "common/config-manager.h"
namespace Groovie {
CellGame::CellGame(bool easierAi) {
_startX = _startY = _endX = _endY = 255;
_stack_index = _boardStackPtr = 0;
_flag4 = false;
_flag2 = false;
_flag1 = false;
_coeff3 = 0;
_moveCount = 0;
_easierAi = easierAi;
}
byte CellGame::getStartX() {
if (_startX > BOARDSIZE) {
warning ("CellGame::getStartX: not calculated yet (%d)!", _startX);
return 0;
} else {
return _startX;
}
}
byte CellGame::getStartY() {
if (_startY > BOARDSIZE) {
warning ("CellGame::getStartY: not calculated yet (%d)!", _startY);
return 6;
} else {
return _startY;
}
}
byte CellGame::getEndX() {
if (_endX > BOARDSIZE) {
warning ("CellGame::getEndX: not calculated yet (%d)!", _endX);
return 1;
} else {
return _endX;
}
}
byte CellGame::getEndY() {
if (_endY > BOARDSIZE) {
warning ("CellGame::getEndY: not calculated yet (%d)!", _endY);
return 6;
} else {
return _endY;
}
}
CellGame::~CellGame() {
}
const int8 possibleMoves[][9] = {
{ 1, 7, 8, -1 },
{ 0, 2, 7, 8, 9, -1 },
{ 1, 3, 8, 9, 10, -1 },
{ 2, 4, 9, 10, 11, -1 },
{ 3, 5, 10, 11, 12, -1 },
{ 4, 6, 11, 12, 13, -1 }, // 5
{ 5, 12, 13, -1 },
{ 0, 1, 8, 14, 15, -1 },
{ 0, 1, 2, 7, 9, 14, 15, 16, -1 },
{ 1, 2, 3, 8, 10, 15, 16, 17, -1 },
{ 2, 3, 4, 9, 11, 16, 17, 18, -1 }, // 10
{ 3, 4, 5, 10, 12, 17, 18, 19, -1 },
{ 4, 5, 6, 11, 13, 18, 19, 20, -1 },
{ 5, 6, 12, 19, 20, -1 },
{ 7, 8, 15, 21, 22, -1 },
{ 7, 8, 9, 14, 16, 21, 22, 23, -1 }, // 15
{ 8, 9, 10, 15, 17, 22, 23, 24, -1 },
{ 9, 10, 11, 16, 18, 23, 24, 25, -1 },
{ 10, 11, 12, 17, 19, 24, 25, 26, -1 },
{ 11, 12, 13, 18, 20, 25, 26, 27, -1 },
{ 12, 13, 19, 26, 27, -1 }, // 20
{ 14, 15, 22, 28, 29, -1 },
{ 14, 15, 16, 21, 23, 28, 29, 30, -1 },
{ 15, 16, 17, 22, 24, 29, 30, 31, -1 },
{ 16, 17, 18, 23, 25, 30, 31, 32, -1 },
{ 17, 18, 19, 24, 26, 31, 32, 33, -1 }, // 25
{ 18, 19, 20, 25, 27, 32, 33, 34, -1 },
{ 19, 20, 26, 33, 34, -1 },
{ 21, 22, 29, 35, 36, -1 },
{ 21, 22, 23, 28, 30, 35, 36, 37, -1 },
{ 22, 23, 24, 29, 31, 36, 37, 38, -1 }, // 30
{ 23, 24, 25, 30, 32, 37, 38, 39, -1 },
{ 24, 25, 26, 31, 33, 38, 39, 40, -1 },
{ 25, 26, 27, 32, 34, 39, 40, 41, -1 },
{ 26, 27, 33, 40, 41, -1 },
{ 28, 29, 36, 42, 43, -1 }, // 35
{ 28, 29, 30, 35, 37, 42, 43, 44, -1 },
{ 29, 30, 31, 36, 38, 43, 44, 45, -1 },
{ 30, 31, 32, 37, 39, 44, 45, 46, -1 },
{ 31, 32, 33, 38, 40, 45, 46, 47, -1 },
{ 32, 33, 34, 39, 41, 46, 47, 48, -1 }, // 40
{ 33, 34, 40, 47, 48, -1 },
{ 35, 36, 43, -1 },
{ 35, 36, 37, 42, 44, -1 },
{ 36, 37, 38, 43, 45, -1 },
{ 37, 38, 39, 44, 46, -1 }, // 45
{ 38, 39, 40, 45, 47, -1 },
{ 39, 40, 41, 46, 48, -1 },
{ 40, 41, 47, -1 }
};
const int8 strategy2[][17] = {
{ 2, 9, 14, 15, 16, -1 },
{ 3, 10, 14, 15, 16, 17, -1 },
{ 0, 4, 7, 11, 14, 15, 16, 17, 18, -1 },
{ 1, 5, 8, 12, 15, 16, 17, 18, 19, -1 },
{ 2, 6, 9, 13, 16, 17, 18, 19, 20, -1 },
{ 3, 10, 17, 18, 19, 20, -1 }, // 5
{ 4, 11, 18, 19, 20, -1 },
{ 2, 9, 16, 21, 22, 23, -1 },
{ 3, 10, 17, 21, 22, 23, 24, -1 },
{ 0, 4, 7, 11, 14, 18, 21, 22, 23, 24, 25, -1 },
{ 1, 5, 8, 12, 15, 19, 22, 23, 24, 25, 26, -1 }, // 10
{ 2, 6, 9, 13, 16, 20, 23, 24, 25, 26, 27, -1 },
{ 3, 10, 17, 24, 25, 26, 27, -1 },
{ 4, 11, 18, 25, 26, 27, -1 },
{ 0, 1, 2, 9, 16, 23, 28, 29, 30, -1 },
{ 0, 1, 2, 3, 10, 17, 24, 28, 29, 30, 31, -1 }, // 15
{ 0, 1, 2, 3, 4, 7, 11, 14, 18, 21, 25, 28, 29, 30, 31, 32, -1 },
{ 1, 2, 3, 4, 5, 8, 12, 15, 19, 22, 26, 29, 30, 31, 32, 33, -1 },
{ 2, 3, 4, 5, 6, 9, 13, 16, 20, 23, 27, 30, 31, 32, 33, 34, -1 },
{ 3, 4, 5, 6, 10, 17, 24, 31, 32, 33, 34, -1 },
{ 4, 5, 6, 11, 18, 25, 32, 33, 34, -1 }, // 20
{ 7, 8, 9, 16, 23, 30, 35, 36, 37, -1 },
{ 7, 8, 9, 10, 17, 24, 31, 35, 36, 37, 38, -1 },
{ 7, 8, 9, 10, 11, 14, 18, 21, 25, 28, 32, 35, 36, 37, 38, 39, -1 },
{ 8, 9, 10, 11, 12, 15, 19, 22, 26, 29, 33, 36, 37, 38, 39, 40, -1 },
{ 9, 10, 11, 12, 13, 16, 20, 23, 27, 30, 34, 37, 38, 39, 40, 41, -1 }, // 25
{ 10, 11, 12, 13, 17, 24, 31, 38, 39, 40, 41, -1 },
{ 11, 12, 13, 18, 25, 32, 39, 40, 41, -1 },
{ 14, 15, 16, 23, 30, 37, 42, 43, 44, -1 },
{ 14, 15, 16, 17, 24, 31, 38, 42, 43, 44, 45, -1 },
{ 14, 15, 16, 17, 18, 21, 25, 28, 32, 35, 39, 42, 43, 44, 45, 46, -1 }, // 30
{ 15, 16, 17, 18, 19, 22, 26, 29, 33, 36, 40, 43, 44, 45, 46, 47, -1 },
{ 16, 17, 18, 19, 20, 23, 27, 30, 34, 37, 41, 44, 45, 46, 47, 48, -1 },
{ 17, 18, 19, 20, 24, 31, 38, 45, 46, 47, 48, -1 },
{ 18, 19, 20, 25, 32, 39, 46, 47, 48, -1 },
{ 21, 22, 23, 30, 37, 44, -1 }, // 35
{ 21, 22, 23, 24, 31, 38, 45, -1 },
{ 21, 22, 23, 24, 25, 28, 32, 35, 39, 42, 46, -1 },
{ 22, 23, 24, 25, 26, 29, 33, 36, 40, 43, 47, -1 },
{ 23, 24, 25, 26, 27, 30, 34, 37, 41, 44, 48, -1 },
{ 24, 25, 26, 27, 31, 38, 45, -1 }, // 40
{ 25, 26, 27, 32, 39, 46, -1 },
{ 28, 29, 30, 37, 44, -1 },
{ 28, 29, 30, 31, 38, 45, -1 },
{ 28, 29, 30, 31, 32, 35, 39, 42, 46, -1 },
{ 29, 30, 31, 32, 33, 36, 40, 43, 47, -1 }, // 45
{ 30, 31, 32, 33, 34, 37, 41, 44, 48, -1 },
{ 31, 32, 33, 34, 38, 45, -1 },
{ 32, 33, 34, 39, 46, -1 }
};
void CellGame::copyToTempBoard() {
for (int i = 0; i < 53; ++i) {
_tempBoard[i] = _board[i];
}
}
void CellGame::copyFromTempBoard() {
for (int i = 0; i < 53; ++i) {
_board[i] = _tempBoard[i];
}
}
void CellGame::copyToShadowBoard() {
_board[53] = 0;
_board[55] = 1;
_board[56] = 0;
for (int i = 0; i < 49; ++i) {
_shadowBoard[i] = _board[i];
}
}
void CellGame::pushBoard() {
assert(_boardStackPtr < 57 * 9);
for (int i = 0; i < 57; ++i)
_boardStack[_boardStackPtr + i] = _board[i];
_boardStackPtr += 57;
}
void CellGame::popBoard() {
assert(_boardStackPtr > 0);
_boardStackPtr -= 57;
for (int i = 0; i < 57; ++i) {
_board[i] = _boardStack[_boardStackPtr + i];
}
}
void CellGame::pushShadowBoard() {
assert(_boardStackPtr < 57 * 9);
for (int i = 0; i < 57; ++i)
_boardStack[_boardStackPtr + i] = _shadowBoard[i];
_boardStackPtr += 57;
}
void CellGame::popShadowBoard() {
assert(_boardStackPtr > 0);
_boardStackPtr -= 57;
for (int i = 0; i < 57; ++i) {
_shadowBoard[i] = _boardStack[_boardStackPtr + i];
}
}
void CellGame::clearMoves() {
_stack_startXY[0] = _board[53];
_stack_endXY[0] = _board[54];
_stack_pass[0] = _board[55];
_stack_index = 1;
}
void CellGame::pushMove() {
_stack_startXY[_stack_index] = _board[53];
_stack_endXY[_stack_index] = _board[54];
_stack_pass[_stack_index] = _board[55];
_stack_index++;
}
void CellGame::resetMove() {
_board[53] = 0;
_board[54] = 0;
_board[55] = 0;
}
void CellGame::takeCells(uint16 whereTo, int8 color) {
int cellN;
const int8 *str;
str = possibleMoves[whereTo];
while (1) {
cellN = *str++;
if (cellN < 0)
break;
if (_tempBoard[cellN] > 0) {
--_tempBoard[_tempBoard[cellN] + 48];
_tempBoard[cellN] = color;
++_tempBoard[color + 48];
}
}
}
void CellGame::countAllCells() {
_board[49] = 0;
_board[50] = 0;
_board[51] = 0;
_board[52] = 0;
for (int i = 0; i < 49; i++) {
switch (_board[i]) {
case 1: // CELL_BLUE
_board[49]++;
break;
case 2: // CELL_GREEN
_board[50]++;
break;
case 3:
_board[51]++;
break;
case 4:
_board[52]++;
break;
default:
break;
}
}
}
int CellGame::countCellsOnTempBoard(int8 color) {
const int8 *str;
int res = 0;
int i;
for (i = 0; i < 49; i++)
_boardSum[i] = 0;
for (i = 0; i < 49; i++) {
if (_tempBoard[i] == color) {
for (str = possibleMoves[i]; *str > 0; str++) {
if (!_tempBoard[*str])
++_boardSum[*str];
}
}
}
for (i = 0; i < 49; i++)
res += _boardSum[i];
return res;
}
bool CellGame::canMoveFunc1(int8 color) {
const int8 *str;
if (_board[55] == 1) {
for (; _board[53] < 49; _board[53]++) {
if (_shadowBoard[_board[53]] == color) {
str = &possibleMoves[_board[53]][_board[56]];
for (;_board[56] < 8; _board[56]++) {
_board[54] = *str++;
if (_board[54] < 0)
break;
if (!_shadowBoard[_board[54]]) {
_shadowBoard[_board[54]] = -1;
++_board[56];
return true;
}
}
_board[56] = 0;
}
}
_board[53] = 0;
_board[55] = 2;
_board[56] = 0;
}
if (_board[55] == 2) {
for (; _board[53] < 49; _board[53]++) {
if (_shadowBoard[_board[53]] == color) {
str = &strategy2[_board[53]][_board[56]];
for (;_board[56] < 16; _board[56]++) {
_board[54] = *str++;
if (_board[54] < 0)
break;
if (!_board[_board[54]]) {
++_board[56];
return true;
}
}
_board[56] = 0;
}
}
}
return false;
}
bool CellGame::canMoveFunc3(int8 color) {
const int8 *str;
if (_board[55] == 1) {
for (; _board[53] < 49; _board[53]++) {
if (_shadowBoard[_board[53]] == color) {
str = &possibleMoves[_board[53]][_board[56]];
for (;_board[56] < 8; _board[56]++) {
_board[54] = *str++;
if (_board[54] < 0)
break;
if (!_shadowBoard[_board[54]]) {
_shadowBoard[_board[54]] = -1;
++_board[56];
return true;
}
}
_board[56] = 0;
}
}
_board[53] = 0;
_board[55] = 2;
_board[56] = 0;
for (int i = 0; i < 49; ++i)
_shadowBoard[i] = _board[i];
}
if (_board[55] == 2) {
for (; _board[53] < 49; _board[53]++) {
if (_shadowBoard[_board[53]] == color) {
str = &strategy2[_board[53]][_board[56]];
for (;_board[56] < 16; _board[56]++) {
_board[54] = *str++;
if (_board[54] < 0)
break;
if (!_shadowBoard[_board[54]]) {
_shadowBoard[_board[54]] = -1;
++_board[56];
return true;
}
}
_board[56] = 0;
}
}
}
return false;
}
bool CellGame::canMoveFunc2(int8 color) {
const int8 *str;
while (1) {
while (_board[_board[54]]) {
++_board[54];
if (_board[54] >= 49)
return false;
}
if (!_board[55]) {
str = possibleMoves[_board[54]];
while (1) {
_board[53] = *str++;
if (_board[53] < 0)
break;
if (_board[_board[53]] == color) {
_board[55] = 1;
return true;
}
}
_board[55] = 1;
}
if (_board[55] == 1) {
_board[55] = 2;
_board[56] = 0;
}
if (_board[55] == 2) {
str = &strategy2[_board[54]][_board[56]];
for (; _board[56] < 16; _board[56]++) {
_board[53] = *str++;
if (_board[53] < 0)
break;
if (_board[_board[53]] == color) {
++_board[56];
return true;
}
}
++_board[54];
_board[55] = 0;
if (_board[54] >= 49)
break;
}
}
return false;
}
void CellGame::makeMove(int8 color) {
copyToTempBoard();
_tempBoard[_board[54]] = color;
++_tempBoard[color + 48];
if (_board[55] == 2) {
_tempBoard[_board[53]] = 0;
--_tempBoard[color + 48];
}
takeCells(_board[54], color);
}
int CellGame::getBoardWeight(int8 color1, int8 color2) {
int8 celln;
const int8 *str;
byte cellCnt[8];
str = possibleMoves[_board[54]];
cellCnt[1] = _board[49];
cellCnt[2] = _board[50];
cellCnt[3] = _board[51];
cellCnt[4] = _board[52];
if (_board[55] != 2)
++cellCnt[color2];
celln = *str++;
celln = _board[celln];
if (celln > 0) {
--cellCnt[celln];
++cellCnt[color2];
}
celln = *str++;
celln = _board[celln];
if (celln > 0) {
--cellCnt[celln];
++cellCnt[color2];
}
celln = *str++;
celln = _board[celln];
if (celln > 0) {
--cellCnt[celln];
++cellCnt[color2];
}
while (1) {
celln = *str++;
if (celln < 0)
break;
celln = _board[celln];
if (celln > 0) {
--cellCnt[celln];
++cellCnt[color2];
}
}
return _coeff3 + 2 * (2 * cellCnt[color1] - cellCnt[1] - cellCnt[2] - cellCnt[3] - cellCnt[4]);
}
void CellGame::chooseBestMove(int8 color) {
int moveIndex = 0;
if (_flag2) {
int bestWeight = 32767;
for (int i = 0; i < _stack_index; ++i) {
_board[53] = _stack_startXY[i];
_board[54] = _stack_endXY[i];
_board[55] = _stack_pass[i];
makeMove(color);
int curWeight = countCellsOnTempBoard(color);
if (curWeight <= bestWeight) {
if (curWeight < bestWeight)
moveIndex = 0;
bestWeight = curWeight;
_stack_startXY[moveIndex] = _board[53];
_stack_endXY[moveIndex] = _board[54];
_stack_pass[moveIndex++] = _board[55];
}
}
_stack_index = moveIndex;
}
_startX = _stack_startXY[0] % 7;
_startY = _stack_startXY[0] / 7;
_endX = _stack_endXY[0] % 7;
_endY = _stack_endXY[0] / 7;
}
int8 CellGame::calcBestWeight(int8 color1, int8 color2, uint16 depth, int bestWeight) {
int8 res;
int8 curColor;
bool canMove;
int type;
uint16 i;
int8 currBoardWeight;
int8 weight;
pushBoard();
copyFromTempBoard();
curColor = color2;
for (i = 0;; ++i) {
if (i >= 4) {
res = _coeff3 + 2 * (2 * _board[color1 + 48] - _board[49] - _board[50] - _board[51] - _board[52]);
popBoard();
return res;
}
++curColor;
if (curColor > 4)
curColor = 1;
if (_board[curColor + 48]) {
if (_board[curColor + 48] >= 49 - _board[49] - _board[50] - _board[51] - _board[52]) {
resetMove();
canMove = canMoveFunc2(curColor);
type = 1;
} else {
copyToShadowBoard();
if (depth == 1) {
canMove = canMoveFunc3(curColor);
type = 3;
} else {
canMove = canMoveFunc1(curColor);
type = 2;
}
}
if (canMove)
break;
}
}
if (_flag1) {
popBoard();
return bestWeight + 1;
}
depth -= 1;
if (depth) {
makeMove(curColor);
if (type == 1) {
res = calcBestWeight(color1, curColor, depth, bestWeight);
} else {
pushShadowBoard();
res = calcBestWeight(color1, curColor, depth, bestWeight);
popShadowBoard();
}
} else {
res = getBoardWeight(color1, curColor);
}
if ((res < bestWeight && color1 != curColor) || _flag4) {
popBoard();
return res;
}
currBoardWeight = _coeff3 + 2 * (2 * _board[color1 + 48] - _board[49] - _board[50] - _board[51] - _board[52]);
while (1) {
if (type == 1) {
canMove = canMoveFunc2(curColor);
} else if (type == 2) {
canMove = canMoveFunc1(curColor);
} else {
canMove = canMoveFunc3(curColor);
}
if (!canMove)
break;
if (_flag1) {
popBoard();
return bestWeight + 1;
}
if (_board[55] == 2) {
if (getBoardWeight(color1, curColor) == currBoardWeight)
continue;
}
if (!depth) {
weight = getBoardWeight(color1, curColor);
if (type == 1) {
if (_board[55] == 2)
_board[56] = 16;
}
} else {
makeMove(curColor);
if (type != 1) {
pushShadowBoard();
weight = calcBestWeight(color1, curColor, depth, bestWeight);
popShadowBoard();
} else {
weight = calcBestWeight(color1, curColor, depth, bestWeight);
}
}
if ((weight < res && color1 != curColor) || (weight > res && color1 == curColor))
res = weight;
if ((res < bestWeight && color1 != curColor) || _flag4)
break;
}
popBoard();
return res;
}
void CellGame::doGame(int8 color, int depth) {
bool canMove;
int type;
countAllCells();
if (_board[color + 48] >= 49 - _board[49] - _board[50] - _board[51] - _board[52]) {
resetMove();
canMove = canMoveFunc2(color);
type = true;
} else {
copyToShadowBoard();
canMove = canMoveFunc1(color);
type = false;
}
if (canMove) {
int8 w1, w2;
if (_board[color + 48] - _board[49] - _board[50] - _board[51] - _board[52] == 0)
depth = 0;
_coeff3 = 0;
if (_board[55] == 1)
_coeff3 = 1;
clearMoves();
if (depth) {
makeMove(color);
_flag4 = false;
if (type) {
w2 = calcBestWeight(color, color, depth, -127);
} else {
pushShadowBoard();
w2 = calcBestWeight(color, color, depth, -127);
popShadowBoard();
}
} else {
w2 = getBoardWeight(color, color);
}
int8 currBoardWeight = 2 * (2 * _board[color + 48] - _board[49] - _board[50] - _board[51] - _board[52]);
while (1) {
if (type)
canMove = canMoveFunc2(color);
else
canMove = canMoveFunc1(color);
if (!canMove)
break;
if (_flag1)
break;
_coeff3 = 0;
if (_board[55] == 2) {
if (getBoardWeight(color, color) == currBoardWeight)
continue;
}
if (_board[55] == 1)
_coeff3 = 1;
if (depth) {
makeMove(color);
_flag4 = false;
if (type) {
w1 = calcBestWeight(color, color, depth, w2);
} else {
pushShadowBoard();
w1 = calcBestWeight(color, color, depth, w2);
popShadowBoard();
}
} else {
w1 = getBoardWeight(color, color);
}
if (w1 == w2)
pushMove();
if (w1 > w2) {
clearMoves();
w2 = w1;
}
}
chooseBestMove(color);
}
}
const int8 depths[] = { 1, 1, 1, 2, 1, 1, 2, 2, 1, 2, 2, 2, 3, 2, 2, 3, 3, 2, 3, 3, 3 };
void CellGame::calcMove(int8 color, uint16 depth) {
_flag1 = false;
++_moveCount;
if (depth) {
if (depth == 1) {
_flag2 = true;
doGame(color, 0);
} else {
int newDepth;
newDepth = depths[3 * (depth - 2) + _moveCount % 3];
if (_easierAi && _moveCount > 7)
newDepth = 1;
_flag2 = true;
if (newDepth >= 20) {
assert(0); // This branch is not implemented
} else {
doGame(color, newDepth);
}
}
} else {
_flag2 = false;
doGame(color, depth);
}
}
void CellGame::run(uint16 depth, byte *scriptBoard) {
const byte color = 2;
int i;
for (i = 0; i < 49; i++, scriptBoard++) {
_board[i] = 0;
if (*scriptBoard == 50)
_board[i] = 1;
if (*scriptBoard == 66)
_board[i] = 2;
}
for (i = 49; i < 57; i++)
_board[i] = 0;
calcMove(color, depth);
}
} // End of Groovie namespace

View File

@@ -0,0 +1,101 @@
/* 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.
*
*/
#ifndef GROOVIE_LOGIC_CELL_H
#define GROOVIE_LOGIC_CELL_H
#include "common/textconsole.h"
#define BOARDSIZE 7
#define CELL_CLEAR 0
#define CELL_BLUE 1
#define CELL_GREEN 2
namespace Groovie {
class CellGame {
public:
CellGame(bool easierAi);
~CellGame();
void run(uint16 depth, byte *scriptBoard);
byte getStartX();
byte getStartY();
byte getEndX();
byte getEndY();
private:
void copyToTempBoard();
void copyFromTempBoard();
void copyToShadowBoard();
void pushBoard();
void popBoard();
void pushShadowBoard();
void popShadowBoard();
void clearMoves();
void pushMove();
void resetMove();
bool canMoveFunc1(int8 color);
bool canMoveFunc2(int8 color);
bool canMoveFunc3(int8 color);
void takeCells(uint16 whereTo, int8 color);
void countAllCells();
int countCellsOnTempBoard(int8 color);
void makeMove(int8 color);
int getBoardWeight(int8 color1, int8 color2);
void chooseBestMove(int8 color);
int8 calcBestWeight(int8 color1, int8 color2, uint16 depth, int bestWeight);
void doGame(int8 color, int depth);
void calcMove(int8 color, uint16 depth);
byte _startX;
byte _startY;
byte _endX;
byte _endY;
int8 _board[57];
int8 _tempBoard[58];
int8 _shadowBoard[64];
int8 _boardStack[570];
int _boardStackPtr;
int8 _boardSum[58];
int8 _stack_startXY[128];
int8 _stack_endXY[128];
int8 _stack_pass[128];
int _stack_index;
int _coeff3;
bool _flag1, _flag2, _flag4;
int _moveCount;
bool _easierAi;
};
} // End of Groovie namespace
#endif // GROOVIE_LOGIC_CELL_H

View File

@@ -0,0 +1,250 @@
/* 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/groovie.h"
#include "groovie/logic/gallery.h"
namespace Groovie {
// Links between the pieces in the Gallery challenge
// For example, the first row signifies that piece 1
// is connected to pieces 2, 4 and 5
const byte GalleryGame::kGalleryLinks[21][10] = {
{ 2, 4, 5, 0, 0, 0, 0, 0, 0, 0 }, // 1
{ 1, 5, 3, 0, 0, 0, 0, 0, 0, 0 }, // 2
{ 2, 5, 9, 12, 0, 0, 0, 0, 0, 0 }, // 3
{ 1, 5, 6, 7, 8, 0, 0, 0, 0, 0 }, // 4
{ 1, 2, 3, 4, 7, 8, 9, 0, 0, 0 }, // 5
{ 4, 7, 10, 11, 13, 14, 15, 16, 18, 0 }, // 6
{ 4, 5, 6, 8, 9, 10, 0, 0, 0, 0 }, // 7
{ 4, 5, 7, 0, 0, 0, 0, 0, 0, 0 }, // 8
{ 3, 5, 7, 10, 11, 12, 18, 0, 0, 0 }, // 9
{ 6, 7, 9, 11, 0, 0, 0, 0, 0, 0 }, // 10
{ 6, 9, 10, 18, 0, 0, 0, 0, 0, 0 }, // 11
{ 3, 9, 18, 21, 0, 0, 0, 0, 0, 0 }, // 12
{ 6, 14, 17, 19, 0, 0, 0, 0, 0, 0 }, // 13
{ 6, 13, 15, 17, 19, 20, 21, 0, 0, 0 }, // 14
{ 6, 14, 16, 18, 21, 0, 0, 0, 0, 0 }, // 15
{ 6, 15, 0, 0, 0, 0, 0, 0, 0, 0 }, // 16
{13, 14, 19, 0, 0, 0, 0, 0, 0, 0 }, // 17
{ 6, 9, 11, 12, 15, 21, 0, 0, 0, 0 }, // 18
{13, 14, 17, 20, 0, 0, 0, 0, 0, 0 }, // 19
{14, 19, 21, 0, 0, 0, 0, 0, 0, 0 }, // 20
{12, 14, 15, 18, 20, 0, 0, 0, 0, 0 } // 21
};
enum kGalleryPieceStatus {
kPieceUnselected = 0,
kPieceSelected = 1
};
void GalleryGame::run(byte *scriptVariables) {
byte pieceStatus[kPieceCount];
byte status1[kPieceCount];
memcpy(pieceStatus, scriptVariables + 26, kPieceCount);
int selectedPieces = 0;
for (int i = 0; i < kPieceCount; i++) {
status1[i] = 0;
// in this context it seems like kPieceSelected means it's available for selection
if (pieceStatus[i] == kPieceSelected) {
byte status2[kPieceCount];
for (int j = 0; j < kPieceCount; j++)
status2[j] = pieceStatus[j];
status2[i] = 0;
byte curLink = kGalleryLinks[i][0];
int linkedPiece = 1;
while (curLink != 0) {
linkedPiece++;
status2[curLink - 1] = kPieceUnselected;
curLink = kGalleryLinks[i][linkedPiece - 1];
}
status1[i] = galleryAI(status2, 1);
// in this context, kPieceSelected means we think it's an optimal move
if (status1[i] == kPieceSelected) {
selectedPieces++;
}
}
}
if (selectedPieces == 0) {
// optimal move not found, choose a move with a high score?
int highestScore = 0;
for (int i = 0; i < kPieceCount; i++) {
if (highestScore < status1[i]) {
highestScore = status1[i];
}
}
if (highestScore == 2) {
highestScore = 1;
} else {
if (highestScore < kPieceCount) {
highestScore = 2;
} else {
highestScore -= 12;
}
}
for (int i = 0; i < kPieceCount; i++) {
if (highestScore < status1[i]) {
status1[i] = kPieceSelected;
selectedPieces++;
}
}
}
int selectedPiece = 0;
// var 49 is set by the script calling o_random
// choose one of our good moves
byte v12 = scriptVariables[49] % selectedPieces;
for (int i = 0; i < kPieceCount; i++) {
if (status1[selectedPiece] == 1 && !v12--)
break;
selectedPiece++;
}
scriptVariables[47] = (selectedPiece + 1) / 10;
scriptVariables[48] = (selectedPiece + 1) % 10;
}
byte GalleryGame::galleryAI(byte *pieceStatus, int depth) {
byte status1[kPieceCount];
byte status2[kPieceCount];
int selectedPieces = 0;
for (int i = 0; i < kPieceCount; i++) {
status1[i] = 0;
if (pieceStatus[i] == kPieceSelected) {
for (int j = 0; j < kPieceCount; j++)
status2[j] = pieceStatus[j];
status2[i] = 0;
selectedPieces = 1;
byte curLink = kGalleryLinks[i][0];
int linkedPiece = 1;
while (curLink != 0) {
linkedPiece++;
status2[curLink - 1] = kPieceUnselected;
curLink = kGalleryLinks[i][linkedPiece - 1];
}
status1[i] = galleryAI(status2, depth == 0 ? 1 : 0);
if (!depth && status1[i] == kPieceSelected) {
return 1;
}
}
}
if (selectedPieces) {
byte v8 = 0;
byte v9 = 0;
byte v10 = 0;
for (int j = 0; j < 21; ++j) {
byte v12 = status1[j];
if (v12) {
++v10;
if (v12 == 1)
++v9;
else
v8 += v12;
}
}
if (v9 == v10)
return 1; // I believe 1 means this is an optimal move
else if (_easierAi && (v9 + 1 == v10 || v9 - 1 == v10))
return 1; // close enough to an optimal move?
else
return (v8 + 102 * v9) / v10;// otherwise, higher numbers are better
}
return depth == 0 ? 2 : 1;
}
void GalleryGame::testsWriteMove(int move, byte pieceStatus[kPieceCount]) {
if (pieceStatus[move] != kPieceSelected)
error("illegal move to %d", move + 1);
pieceStatus[move] = kPieceUnselected;
for (int i = 0; i < 10; i++) {
byte curLink = kGalleryLinks[move][i];
if (!curLink)
break;
pieceStatus[curLink - 1] = kPieceUnselected;
}
}
void GalleryGame::ensureSamanthaWins(int seed) {
byte scriptVariables[1024];
byte goalPieceStatus[kPieceCount];
memset(goalPieceStatus, 0, sizeof(goalPieceStatus));
Common::RandomSource rng("ensureSamanthaWins");
rng.setSeed(seed);
warning("starting ensureSamanthaWins with seed %u", seed);
memset(scriptVariables, 1, sizeof(scriptVariables));
for (int i = 0; i < 100; i++) {
bool isStauf = i % 2;
scriptVariables[49] = rng.getRandomNumber(14);
run(scriptVariables);
int selectedMove = scriptVariables[47] * 10 + scriptVariables[48] - 1;
warning("Move %d: %s moved to %d", i, (isStauf ? "Stauf" : "Samantha"), selectedMove + 1);
testsWriteMove(selectedMove, scriptVariables + 26);
if (memcmp(scriptVariables + 26, goalPieceStatus, sizeof(goalPieceStatus)) == 0) {
if (isStauf)
error("Stauf won");
else
warning("Samantha won");
return;
}
}
error("game took too long");
}
void GalleryGame::test() {
warning("running gallery tests");
for (int i = 0; i < 20; i++) {
ensureSamanthaWins(i);
}
warning("finished running gallery tests");
}
} // End of Groovie namespace

View File

@@ -0,0 +1,105 @@
/* 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.
*
*/
#ifndef GROOVIE_LOGIC_GALLERY_H
#define GROOVIE_LOGIC_GALLERY_H
#include "common/random.h"
#include "common/system.h"
namespace Groovie {
/*
* Puzzle in the Gallery (bs.grv).
* The aim is to select the last piece of the image.
* There are 18 pieces in total.
* When selecting a piece, all surrounding pieces are also selected
*
* +--------------------+--------------------------------+--------+
* | 1/1A | 2/1B | |
* | +--------------+--+--------------------------+-----+ |
* | | | | |
* +--+ 4/1D | 5/1E | 3/1C |
* | | | |
* +-----+--------+--+--------+-----------------+--+--------+ |
* | | | | | | |
* | | | | | | |
* | | | 8/21 | | | |
* | | | | +-----------+ | |
* | | | | | | | |
* | | +-----------+ | 10/23 | 9/22 | |
* | | | | | |
* | | 7/20 +-----+-----+ +-----+
* | | | | | | |
* | +--------------------------+ | | | |
* | 6/1F | | | |
* +-----------+-----------+-----+--+ | 11 | | 12 |
* | 13/26 | | | | | / | | / |
* | +-----+-----+ | | | | 24 +-----------+ 25 |
* | | | | | | | | | |
* +-----+ 17/2A | | |16| | | | |
* | | | | |/ | | | | |
* | +-----+-----+ | |29| | | +-----+
* | | | | | | | | |
* | | | | | +-----+ 18/2B | |
* | 19/2C | 14/27 | | | | | |
* | | | | +-----------+ | |
* | | | | | | | |
* | | | +--+ 15/28 | | |
* | | | | | |
* | +--------+--+--------------------+-----------+ |
* | | 20/2D | 21/2E |
* +-----------+--------+-----------------------------------------+
*/
const int kPieceCount = 21;
class GalleryGame {
public:
GalleryGame(bool easierAi) {
#if 0
_easierAi = false;
test();
#endif
_easierAi = easierAi;
}
void run(byte *scriptVariables);
private:
byte galleryAI(byte *pieceStatus, int depth);
static const byte kGalleryLinks[21][10];
bool _easierAi;
void test();
void ensureSamanthaWins(int seed);
void testsWriteMove(int move, byte pieceStatus[kPieceCount]);
};
} // End of Groovie namespace
#endif // GROOVIE_LOGIC_GALLERY_H

View File

@@ -0,0 +1,677 @@
/* 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/groovie.h"
#include "groovie/logic/mousetrap.h"
namespace Groovie {
MouseTrapGame::MouseTrapGame(bool easierAi) : _random("MouseTrapGame") {
_mouseTrapCounter = _mouseTrapCounter1 = 0;
_mouseTrapX = _mouseTrapY = 0;
memset(_mouseTrapRoute, 0, 75);
memset(_mouseTrapRouteCopy, 0, 76);
_mouseTrapPosX = _mouseTrapPosY = 0;
memset(_mouseTrapCells, 0, 31);
_mouseTrapNumSteps = 0;
_easierAi = easierAi;
}
void MouseTrapGame::run(byte *scriptVariables) {
byte op = scriptVariables[2];
// variable 24 is the mouse?
//scriptVariables[24] = 2;
// player wins: scriptVariables[22] = 1;
// stauf wins: scriptVariables[22] = 2;
// allows the player to click to place the mouse somewhere? scriptVariables[5] = 0;
switch (op) {
case 0:
sub01(scriptVariables);
break;
case 1: // init board
// value of 0 is V, 1 is <, 2 is ^, 3 is >
// variable 23 is the outside piece
//scriptVariables[23] = _random.getRandomNumber(3);
// variable slot is the space number + 25, the left corner
// (Stauf's goal) is space 1, above that is space 2, the
// center is 13, and the right corner (goal) is space 25
init();
sub03(scriptVariables);
break;
case 2: // before player chooses the floor to move, set the banned move
{
int clicked = xyToPos(_mouseTrapX, _mouseTrapY);
scriptVariables[clicked + 50] = 0;
break;
}
case 3: // after player moving floor
// a bunch of hardcoded conditionals to copy variables and
// set the banned move
// this probably also sets a variable to allow the player to
// move the mouse, and checks for win/lose
sub05(scriptVariables);
break;
case 5: // maybe player moving mouse
sub06(scriptVariables);
break;
case 6: // Stauf moving floor?
sub07(scriptVariables);
break;
case 7: // maybe Stauf moving mouse
sub08(scriptVariables);
break;
case 8: // Samantha making a move
sub09(scriptVariables);
break;
default:
warning("Unknown mousetrap op %d", op);
break;
}
}
static const int8 mouseTrapStates[] = {
6, 12, 9, 3
};
static const int8 mouseTrapLookup[] = {
1, 0, 3, 0, 0, 1, 0, 3, 1, 4, 3, 4, 4, 1, 4, 3
};
void MouseTrapGame::init() {
int8 initState[8], initX[8], initY[8];
initState[0] = 0;
initState[1] = 1;
initState[3] = 3;
initState[2] = 2;
initState[4] = 4;
initState[5] = 5;
initState[6] = 6;
initState[7] = 7;
initY[0] = 1;
initY[1] = 3;
initY[2] = 0;
initY[3] = 4;
initY[4] = 0;
initY[5] = 4;
initY[6] = 1;
initY[7] = 3;
initX[0] = 0;
initX[1] = 0;
initX[2] = 1;
initX[3] = 1;
initX[4] = 3;
initX[5] = 3;
initX[6] = 4;
initX[7] = 4;
// easier AI gives a fixed board state, because the random configurations can give you some bad ones
if(_easierAi)
_random.setSeed(711);
for (int i = 7; i >= 0; i--) {
int8 j = _random.getRandomNumber(i);
_mouseTrapCells[5 * initY[i] + 5 + initX[i]] = mouseTrapStates[initState[j] >> 1];
for (; j < i; j++) {
initState[j] = initState[j + 1];
}
}
_mouseTrapCells[11] = mouseTrapStates[3];
_mouseTrapCells[16] = mouseTrapStates[0];
_mouseTrapCells[5] = 12;
_mouseTrapCells[21] = mouseTrapStates[0];
_mouseTrapCells[12] = mouseTrapStates[3];
_mouseTrapCells[15] = 13;
_mouseTrapCells[25] = 9;
_mouseTrapCells[22] = mouseTrapStates[1];
_mouseTrapCells[13] = mouseTrapStates[2];
_mouseTrapCells[18] = mouseTrapStates[2];
_mouseTrapCells[23] = mouseTrapStates[1];
_mouseTrapCells[7] = 14;
_mouseTrapCells[17] = 15;
_mouseTrapCells[27] = 11;
_mouseTrapCells[9] = 6;
_mouseTrapCells[19] = 7;
_mouseTrapCells[29] = 3;
_mouseTrapCells[30] = mouseTrapStates[_random.getRandomNumber(3)];
_mouseTrapPosY = 2;
_mouseTrapPosX = 2;
_mouseTrapY = 0;
_mouseTrapX = 0;
_mouseTrapNumSteps = 0;
_mouseTrapCounter = 0;
}
void MouseTrapGame::sub01(byte *scriptVariables) {
int8 x, y;
findMaxPointInRoute(&x, &y);
scriptVariables[5] = (_mouseTrapPosX == x && _mouseTrapPosY == y) ? 1 : 0;
if (havePosInRoute(4, 4)) {
copyRoute(4, 4);
scriptVariables[22] = 1;
} else if (havePosInRoute(0, 0)) {
copyRoute(0, 0);
scriptVariables[22] = 2;
} else {
scriptVariables[22] = 0;
if (!scriptVariables[5])
copyRoute(x, y);
}
}
void MouseTrapGame::sub03(byte *scriptVariables) {
int cnt = 1;
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
scriptVariables[cnt + 25] = findState(_mouseTrapCells[5 * j + 5 + i]);
cnt++;
}
}
scriptVariables[23] = findState(_mouseTrapCells[30]);
}
void MouseTrapGame::sub05(byte *scriptVariables) {
int8 x, y;
posToXY(scriptVariables[1] + 10 * scriptVariables[0], &x, &y);
flipField(x, y);
if (calcSolution()) {
scriptVariables[5] = 0;
updateRoute();
if (havePosInRoute(4, 4)) {
copyRoute(4, 4);
scriptVariables[22] = 1;
} else if (havePosInRoute(0, 0)) {
copyRoute(0, 0);
scriptVariables[22] = 2;
} else {
copyStateToVars(scriptVariables);
scriptVariables[22] = 0;
}
} else {
scriptVariables[5] = 1;
scriptVariables[22] = 0;
}
}
void MouseTrapGame::sub06(byte *scriptVariables) {
int8 x, y;
posToXY(10 * scriptVariables[0] + scriptVariables[1], &x, &y);
copyRoute(x, y);
}
void MouseTrapGame::sub07(byte *scriptVariables) {
int8 x1, y1, x2, y2;
goFarthest(&x1, &y1);
flipField(x1, y1);
if (!calcSolution()) {
scriptVariables[5] = 1;
scriptVariables[22] = 0;
} else {
scriptVariables[5] = 0;
updateRoute();
if (!havePosInRoute(0, 0)) {
if (havePosInRoute(4, 4)) {
copyRoute(4, 4);
scriptVariables[22] = 1;
} else {
findMinPointInRoute(&x2, &y2);
if (_mouseTrapPosX != x2 || _mouseTrapPosY != y2) {
copyRoute(x2, y2);
scriptVariables[22] = 0;
} else {
scriptVariables[5] = 1;
scriptVariables[22] = 0;
}
}
} else {
copyRoute(0, 0);
scriptVariables[22] = 2;
}
}
int8 pos = xyToPos(x1, y1);
scriptVariables[0] = pos / 10;
scriptVariables[1] = pos % 10;
}
void MouseTrapGame::sub08(byte *scriptVariables) {
int8 x1, y1, x, y;
popLastStep(&x1, &y1);
int8 pos = xyToPos(x1, y1);
_mouseTrapPosX = x1;
_mouseTrapPosY = y1;
scriptVariables[0] = scriptVariables[11];
scriptVariables[1] = scriptVariables[12];
scriptVariables[11] = pos / 10;
scriptVariables[12] = pos % 10;
posToXY(scriptVariables[1] + 10 * scriptVariables[0], &x, &y);
if (y > y1) {
scriptVariables[15] = 0;
} else if (y < y1) {
scriptVariables[15] = 2;
} else if (x > x1) {
scriptVariables[15] = 3;
} else if (x < x1) {
scriptVariables[15] = 1;
}
if (!_mouseTrapCounter1)
scriptVariables[2] = 0;
}
void MouseTrapGame::sub09(byte *scriptVariables) {
int8 x1, y1, x2, y2;
getBestDirection(&x1, &y1);
flipField(x1, y1);
if (!calcSolution()) {
scriptVariables[5] = 1;
scriptVariables[22] = 0;
} else {
scriptVariables[5] = 0;
updateRoute();
if (!havePosInRoute(4, 4)) {
if (havePosInRoute(0, 0)) {
copyRoute(0, 0);
scriptVariables[22] = 2;
} else {
findMaxPointInRoute(&x2, &y2);
copyRoute(x2, y2);
scriptVariables[22] = 0;
}
} else {
copyRoute(4, 4);
scriptVariables[22] = 1;
}
}
int8 pos = xyToPos(x1, y1);
scriptVariables[0] = pos / 10;
scriptVariables[1] = pos % 10;
}
void MouseTrapGame::copyRoute(int8 x, int8 y) {
int i;
for (i = 0; i < _mouseTrapCounter; i++) {
if (_mouseTrapRoute[3 * i] == x && _mouseTrapRoute[3 * i + 1] == y)
break;
}
_mouseTrapCounter1 = 0;
do {
_mouseTrapRouteCopy[3 * _mouseTrapCounter1 + 0] = _mouseTrapRoute[3 * i + 0];
_mouseTrapRouteCopy[3 * _mouseTrapCounter1 + 1] = _mouseTrapRoute[3 * i + 1];
_mouseTrapRouteCopy[3 * _mouseTrapCounter1 + 2] = _mouseTrapRoute[3 * i + 2];
_mouseTrapCounter1++;
i = _mouseTrapRoute[3 * i + 2];
} while (i);
}
int8 MouseTrapGame::xyToPos(int8 x, int8 y) {
return 5 * y + x + 1;
}
void MouseTrapGame::posToXY(int8 pos, int8 *x, int8 *y) {
*y = (pos - 1) / 5;
*x = (pos - 1) % 5;
}
void MouseTrapGame::copyStateToVars(byte *scriptVariables) {
memset(scriptVariables + 51, 0, 24);
scriptVariables[75] = 0;
for (int i = 0; i < _mouseTrapCounter; i++)
scriptVariables[xyToPos(_mouseTrapRoute[3 * i], _mouseTrapRoute[3 * i + 1]) + 50] = 1;
}
int8 MouseTrapGame::findState(int8 val) {
int8 result = 0;
while (mouseTrapStates[result] != val) {
if (++result >= 4)
return -1;
}
return result;
}
void MouseTrapGame::flipField(int8 x, int8 y) {
int8 tmp;
if (y) {
if (y == 4) {
if (x == 1) {
tmp = _mouseTrapCells[10];
_mouseTrapCells[10] = _mouseTrapCells[11];
_mouseTrapCells[11] = _mouseTrapCells[12];
_mouseTrapCells[12] = _mouseTrapCells[13];
_mouseTrapCells[13] = _mouseTrapCells[14];
_mouseTrapCells[14] = _mouseTrapCells[30];
_mouseTrapCells[30] = tmp;
_mouseTrapX = 1;
_mouseTrapY = 0;
} else if (x == 3) {
tmp = _mouseTrapCells[20];
_mouseTrapCells[20] = _mouseTrapCells[21];
_mouseTrapCells[21] = _mouseTrapCells[22];
_mouseTrapCells[22] = _mouseTrapCells[23];
_mouseTrapCells[23] = _mouseTrapCells[24];
_mouseTrapCells[24] = _mouseTrapCells[30];
_mouseTrapCells[30] = tmp;
_mouseTrapX = 3;
_mouseTrapY = 0;
}
} else if (x) {
if (x == 4) {
if (y == 1) {
tmp = _mouseTrapCells[6];
_mouseTrapCells[6] = _mouseTrapCells[11];
_mouseTrapCells[11] = _mouseTrapCells[16];
_mouseTrapCells[16] = _mouseTrapCells[21];
_mouseTrapCells[21] = _mouseTrapCells[26];
_mouseTrapCells[26] = _mouseTrapCells[30];
_mouseTrapCells[30] = tmp;
_mouseTrapX = 0;
_mouseTrapY = 1;
} else if (y == 3) {
tmp = _mouseTrapCells[8];
_mouseTrapCells[8] = _mouseTrapCells[13];
_mouseTrapCells[13] = _mouseTrapCells[18];
_mouseTrapCells[18] = _mouseTrapCells[23];
_mouseTrapCells[23] = _mouseTrapCells[28];
_mouseTrapCells[28] = _mouseTrapCells[30];
_mouseTrapCells[30] = tmp;
_mouseTrapX = 0;
_mouseTrapY = 3;
}
}
} else if (y == 1) {
tmp = _mouseTrapCells[26];
_mouseTrapCells[26] = _mouseTrapCells[21];
_mouseTrapCells[21] = _mouseTrapCells[16];
_mouseTrapCells[16] = _mouseTrapCells[11];
_mouseTrapCells[11] = _mouseTrapCells[6];
_mouseTrapCells[6] = _mouseTrapCells[30];
_mouseTrapCells[30] = tmp;
_mouseTrapX = 4;
_mouseTrapY = 1;
} else if (y == 3) {
tmp = _mouseTrapCells[28];
_mouseTrapCells[28] = _mouseTrapCells[23];
_mouseTrapCells[23] = _mouseTrapCells[18];
_mouseTrapCells[18] = _mouseTrapCells[13];
_mouseTrapCells[13] = _mouseTrapCells[8];
_mouseTrapCells[8] = _mouseTrapCells[30];
_mouseTrapCells[30] = tmp;
_mouseTrapX = 4;
_mouseTrapY = 3;
}
} else if (x == 1) {
tmp = _mouseTrapCells[14];
_mouseTrapCells[14] = _mouseTrapCells[13];
_mouseTrapCells[13] = _mouseTrapCells[12];
_mouseTrapCells[12] = _mouseTrapCells[11];
_mouseTrapCells[11] = _mouseTrapCells[10];
_mouseTrapCells[10] = _mouseTrapCells[30];
_mouseTrapCells[30] = tmp;
_mouseTrapX = 1;
_mouseTrapY = 4;
} else if (x == 3) {
tmp = _mouseTrapCells[24];
_mouseTrapCells[24] = _mouseTrapCells[23];
_mouseTrapCells[23] = _mouseTrapCells[22];
_mouseTrapCells[22] = _mouseTrapCells[21];
_mouseTrapCells[21] = _mouseTrapCells[20];
_mouseTrapCells[20] = _mouseTrapCells[30];
_mouseTrapCells[30] = tmp;
_mouseTrapX = 3;
_mouseTrapY = 4;
}
}
bool MouseTrapGame::calcSolution() {
int8 pos = _mouseTrapPosY + 5 * _mouseTrapPosX; // coordinates swapped?
int8 val = _mouseTrapCells[pos + 5];
return ((val & 1) != 0 && _mouseTrapPosX && (_mouseTrapCells[pos] & 4) != 0)
|| ((val & 4) != 0 && _mouseTrapPosX < 4 && (_mouseTrapCells[pos + 10] & 1) != 0)
|| ((val & 8) != 0 && _mouseTrapPosY < 4 && (_mouseTrapCells[pos + 6] & 2) != 0)
|| ((val & 2) != 0 && _mouseTrapPosY && (_mouseTrapCells[pos + 4] & 8) != 0);
}
bool MouseTrapGame::havePosInRoute(int8 x, int8 y) {
for (int i = 0; i < _mouseTrapCounter; i++) {
if (_mouseTrapRoute[3 * i] == x && _mouseTrapRoute[3 * i + 1] == y)
return true;
}
return false;
}
void MouseTrapGame::addToRoute(int8 x, int8 y, int8 num) {
if (!havePosInRoute(x, y)) {
_mouseTrapRoute[3 * _mouseTrapCounter] = x;
_mouseTrapRoute[3 * _mouseTrapCounter + 1] = y;
_mouseTrapRoute[3 * _mouseTrapCounter + 2] = num;
_mouseTrapCounter++;
}
}
void MouseTrapGame::updateRoute() {
_mouseTrapCounter = 0;
addToRoute(_mouseTrapPosX, _mouseTrapPosY, 0);
int prevCounter = 0;
do {
for (int i = prevCounter; i < _mouseTrapCounter; i++) {
int8 y1 = _mouseTrapRoute[3 * i + 1];
int8 x1 = _mouseTrapRoute[3 * i];
int8 pos = 5 * x1 + y1;
int8 mask = _mouseTrapCells[pos + 5];
if ((mask & 1) != 0 && x1 && (_mouseTrapCells[pos] & 4) != 0)
addToRoute(x1 - 1, y1, i);
if ((mask & 4) != 0 && x1 < 4 && (_mouseTrapCells[pos + 10] & 1) != 0)
addToRoute(x1 + 1, y1, i);
if ((mask & 8) != 0 && y1 < 4 && (_mouseTrapCells[pos + 6] & 2) != 0)
addToRoute(x1, y1 + 1, i);
if ((mask & 2) != 0 && y1 && (_mouseTrapCells[pos + 4] & 8) != 0)
addToRoute(x1, y1 - 1, i);
}
prevCounter = _mouseTrapCounter;
} while (_mouseTrapCounter != prevCounter);
}
void MouseTrapGame::popLastStep(int8 *x, int8 *y) {
_mouseTrapCounter1--;
*x = _mouseTrapRouteCopy[3 * _mouseTrapCounter1];
*y = _mouseTrapRouteCopy[3 * _mouseTrapCounter1 + 1];
}
void MouseTrapGame::goFarthest(int8 *x, int8 *y) {
int8 origX = _mouseTrapX;
int8 origY = _mouseTrapY;
int8 maxVal = 0;
int8 maxX = 0, maxY = 0;
if (_mouseTrapNumSteps)
--_mouseTrapNumSteps;
for (int8 i = 0; i < 8; i++) {
int8 x1 = mouseTrapLookup[2 * i];
int8 y1 = mouseTrapLookup[2 * i + 1];
if (x1 != origX || y1 != origY) {
flipField(x1, y1);
int8 dist = calcDistanceToExit();
if (_easierAi)
dist += _random.getRandomNumber(2);
if (_mouseTrapNumSteps && _random.getRandomNumber(1) != 0)
dist += 3;
if (dist >= maxVal) {
maxVal = dist;
maxX = x1;
maxY = y1;
}
flipField(mouseTrapLookup[2 * ((i + 4) & 7)], mouseTrapLookup[2 * ((i + 4) & 7) + 1]);
}
}
*x = maxX;
*y = maxY;
}
void MouseTrapGame::findMinPointInRoute(int8 *x, int8 *y) {
int8 maxVal = 0;
int8 x1 = _mouseTrapPosX;
int8 y1 = _mouseTrapPosY;
for (int i = 0; i < _mouseTrapCounter; i++) {
if (8 - _mouseTrapRoute[3 * i + 1] - _mouseTrapRoute[3 * i] > maxVal) {
maxVal = 8 - _mouseTrapRoute[3 * i + 1] - _mouseTrapRoute[3 * i];
x1 = _mouseTrapRoute[3 * i];
y1 = _mouseTrapRoute[3 * i + 1];
}
}
*x = x1;
*y = y1;
}
int8 MouseTrapGame::calcDistanceToExit() {
int8 maxDist = 0;
updateRoute();
if (havePosInRoute(4, 4))
return 0;
for (int i = 0; i < _mouseTrapCounter; i++) {
if (8 - _mouseTrapRoute[3 * i + 1] - _mouseTrapRoute[3 * i] > maxDist)
maxDist = 8 - _mouseTrapRoute[3 * i + 1] - _mouseTrapRoute[3 * i];
}
return maxDist;
}
void MouseTrapGame::getBestDirection(int8 *x, int8 *y) {
int8 maxVal = 0;
int8 origX = _mouseTrapX;
int8 origY = _mouseTrapY;
_mouseTrapNumSteps = 8;
int8 maxX = 0, maxY = 0;
for (int i = 0; i < 8; i++) {
int x1 = mouseTrapLookup[2 * i];
int y1 = mouseTrapLookup[2 * i + 1];
if (origX != x1 || origY != y1) {
flipField(x1, y1);
int8 maxInRoute = findMaxInRoute();
if (maxInRoute >= maxVal) {
maxVal = maxInRoute;
maxX = x1;
maxY = y1;
}
flipField(mouseTrapLookup[2 * ((i + 4) & 7)], mouseTrapLookup[2 * ((i + 4) & 7) + 1]);
}
}
*x = maxX;
*y = maxY;
}
void MouseTrapGame::findMaxPointInRoute(int8 *x, int8 *y) {
int8 maxVal = 0;
int8 x1 = _mouseTrapPosX;
int8 y1 = _mouseTrapPosY;
updateRoute();
for (int i = 0; i < _mouseTrapCounter; i++) {
if (_mouseTrapRoute[3 * i] + _mouseTrapRoute[3 * i + 1] > maxVal) {
maxVal = _mouseTrapRoute[3 * i] + _mouseTrapRoute[3 * i + 1];
x1 = _mouseTrapRoute[3 * i];
y1 = _mouseTrapRoute[3 * i + 1];
}
}
*x = x1;
*y = y1;
}
int8 MouseTrapGame::findMaxInRoute() {
updateRoute();
if (havePosInRoute(0, 0))
return 0;
int8 maxCoords = 0;
for (int i = 0; i < _mouseTrapCounter; i++) {
if (_mouseTrapRoute[3 * i] + _mouseTrapRoute[3 * i + 1] > maxCoords)
maxCoords = _mouseTrapRoute[3 * i] + _mouseTrapRoute[3 * i + 1];
}
return maxCoords;
}
} // End of Groovie namespace

View File

@@ -0,0 +1,101 @@
/* 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.
*
*/
#ifndef GROOVIE_LOGIC_MOUSETRAP_H
#define GROOVIE_LOGIC_MOUSETRAP_H
#include "common/random.h"
#include "common/system.h"
namespace Groovie {
/*
* Mouse Trap puzzle in the Lab.
*
* Stauf's Goal is space 1, counting up as you go north east
* towards the north corner which is space 5 and the moveable
* space to the left of that is space 4.
* South east from Stauf's goal is the next line starting with
* space 6, counting up as you go north east where the moveable
* space to the right of the north corner is space 10
*
* Next line is 11 (unmovable) to 15 (unmoveable), this line
* contains the center space which is space 13
* Next line is 16 (moveable) to 20 (moveable)
* Next line is 21 (unmovable) to 25 (unmovable), with 25 being
* the player's goal door
*
* Space -2 is the next piece, outside of the box
*/
class MouseTrapGame {
public:
MouseTrapGame(bool easierAi);
void run(byte *scriptVariables);
private:
void init();
void sub01(byte *scriptVariables);
void sub03(byte *scriptVariables);
void sub05(byte *scriptVariables);
void sub06(byte *scriptVariables);
void sub07(byte *scriptVariables);
void sub08(byte *scriptVariables);
void sub09(byte *scriptVariables);
void copyRoute(int8 x, int8 y);
int8 xyToPos(int8 x, int8 y);
void posToXY(int8 pos, int8 *x, int8 *y);
void copyStateToVars(byte *scriptVariables);
int8 findState(int8 val);
void flipField(int8 x, int8 y);
bool calcSolution();
bool havePosInRoute(int8 y, int8 x);
void addToRoute(int8 y, int8 x, int8 num);
void updateRoute();
void popLastStep(int8 *x, int8 *y);
void goFarthest(int8 *x, int8 *y);
void findMinPointInRoute(int8 *y, int8 *x);
int8 calcDistanceToExit();
void getBestDirection(int8 *x, int8 *y);
void findMaxPointInRoute(int8 *x, int8 *y);
int8 findMaxInRoute();
private:
Common::RandomSource _random;
int8 _mouseTrapX, _mouseTrapY;
int8 _mouseTrapCounter, _mouseTrapCounter1;
int8 _mouseTrapRoute[75];
int8 _mouseTrapRouteCopy[76];
int8 _mouseTrapCells[31];
int8 _mouseTrapPosX, _mouseTrapPosY;
int8 _mouseTrapNumSteps;
bool _easierAi;
};
} // End of Groovie namespace
#endif // GROOVIE_LOGIC_MOUSETRAP_H

View File

@@ -0,0 +1,721 @@
/* 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/othello.h"
#include "groovie/groovie.h"
namespace Groovie {
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 counting the pieces, we use some heuristics
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 || _easierAi)
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::writeBoardToVars(Freeboard *board, byte *vars) {
for (int x = 0; x < 8; x++) {
for (int y = 0; y < 8; y++) {
byte b = _lookupPlayer[board->_boardstate[x][y]];
vars[xyToVar(x, y)] = b;
}
}
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;
}
void OthelloGame::checkPossibleMove(Freeboard *board, Freeboard (&boards)[30], int8 **lineSpot, int &numPossibleMoves, int moveSpot, byte player, byte opponent) {
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?
return;
} while (board->_boardstate[*testSpot / 8][*testSpot % 8] != opponent);
// we found the opponent, skip to the first piece that doesn't belong to the opponent
for (; board->_boardstate[*testSpot / 8][*testSpot % 8] == opponent; testSpot++) {
}
// start over again if didn't find a piece of our own on the other side
} while (board->_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(board, moveSpot);
boards[numPossibleMoves]._score = scoreBoard(&boards[numPossibleMoves]);
numPossibleMoves++;
}
int OthelloGame::getAllPossibleMoves(Freeboard *board, 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 (board->_boardstate[moveSpot / 8][moveSpot % 8] == 0) {
checkPossibleMove(board, boards, *line, numPossibleMoves, moveSpot, player, opponent);
}
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 depth = _depths[_counter];
if (_easierAi)
depth = 1;
int score = aiRecurse(&possibleMoves[move], depth, 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;
}
writeBoardToVars(&_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;
}
writeBoardToVars(&_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;
}
writeBoardToVars(&_board, vars);
}
void OthelloGame::op5(byte *vars) {
_counter = vars[2];
readBoardStateFromVars(vars);
initLines();
vars[4] = 1;
}
OthelloGame::OthelloGame(bool easierAi)
: _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
_easierAi = false;
test();
#endif
_easierAi = easierAi;
}
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

View File

@@ -0,0 +1,101 @@
/* 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.
*
*/
#ifndef GROOVIE_LOGIC_OTHELLO_H
#define GROOVIE_LOGIC_OTHELLO_H
#include "common/random.h"
#include "common/system.h"
namespace Groovie {
/*
* 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(bool easierAi);
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 writeBoardToVars(Freeboard *board, byte *vars);
void readBoardStateFromVars(byte *vars);
Freeboard getPossibleMove(Freeboard *freeboard, int moveSpot);
void checkPossibleMove(Freeboard *board, Freeboard (&boards)[30], int8 **lineSpot, int &numPossibleMoves, int moveSpot, byte player, byte opponent);
int getAllPossibleMoves(Freeboard *board, 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;
bool _easierAi;
};
} // End of Groovie namespace
#endif // GROOVIE_LOGIC_OTHELLO_H

View File

@@ -0,0 +1,891 @@
/* 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

View File

@@ -0,0 +1,85 @@
/* 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.
*
*/
#ifndef GROOVIE_LOGIC_PENTE_H
#define GROOVIE_LOGIC_PENTE_H
#include "common/random.h"
#include "common/system.h"
namespace Groovie {
/*
* Pente puzzle at the end of the game.
*/
struct pentePlayerTable;
struct penteTable;
class PenteGame {
public:
PenteGame(bool easierAi);
void run(byte *vars);
private:
void animateCapturesCheckWinner(byte *vars);
void opQueryPiece(byte *vars);
void addLine(int x, int y, int linesCounter);
void buildLookupTable();
void penteDeInit();
void penteInit(uint width, uint height, uint length);
int &getPlayerTable(bool staufTurn, pentePlayerTable *&pt);
void scoreLine(uint16 lineIndex, bool isStaufTurn, bool revert);
void calcTouchingPieces(byte moveX, byte moveY, bool revert);
void updateScore(byte x, byte y, bool whose_turn);
void revertScore(byte x, byte y);
byte scoreCaptureSingle(byte x, byte y, int slopeX, int slopeY);
uint scoreCapture(byte x, byte y);
void animateCapture(short move, byte *bitMaskG, short *outCapture1, short *outCapture2);
void revertCapture(byte x, byte y, byte y2);
int scoreMoveAndRevert(byte x, byte y, char depth, int parentScore, bool &gameOver);
int scoreMoveAndRevert(byte x, byte y, char depth, int parentScore);
int aiRecurseTail(int parentScore);
int aiRecurse(char depth, int parentScore);
uint16 aiGetBestMove(byte depth);
void test();
bool testGame(uint32 seed, Common::Array<int> moves, bool playerWin);
void testRandomGame(uint32 seed);
Common::RandomSource _random;
byte _animateCapturesBitMask;
short _previousMove;
short _nextCapturedSpot;
penteTable *_table;
bool _easierAi;
};
} // End of Groovie namespace
#endif // GROOVIE_LOGIC_PENTE_H

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,199 @@
/* 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.
*
*/
#ifndef GROOVIE_LOGIC_TLCGAME_H
#define GROOVIE_LOGIC_TLCGAME_H
#include "common/textconsole.h"
#include "common/random.h"
#define GROOVIE_TLC_MAX_EPSIODES (15)
#define GROOVIE_TLC_MAX_QUEST_EP (50)
namespace Groovie {
class GroovieEngine;
// The regions.rle contains 898 entries. Round about 18 kByte in memory.
struct TlcRegionsHeader {
char name[12];
int numAnswers;
uint32 offset;
};
struct TlcRegion {
uint16 left;
uint16 top;
uint16 right;
uint16 bottom;
};
struct TlcEpQuestionData {
bool questionUsed;
uint32 questionScore;
};
struct TlcTatHeader {
uint32 questionsNum;
uint32 questionsOffset;
uint8 binDividends[16];
};
struct TlcTatAnswer {
uint8 binScore[16];
};
struct TlcTatQuestions {
char name[6];
int answerCount;
TlcTatAnswer answerData[8];
};
class TlcGame
{
public:
#ifdef ENABLE_GROOVIE2
TlcGame(byte *scriptVariables);
~TlcGame();
static const char *getTlcMusicFilename(int musicId);
void handleOp(uint8 op);
/**
* Handle region commands. A region describes the coordinates of
* a rectangle as clickable area in the question dialogs. These regions
* are provided by a the extra file.
* screen coordinates.
*/
void opRegions();
/**
* Get the coordiantes of the region for the next answer. There are
* up to 8 answers possible for each question. In the script the
* coordinates are (0,0,0,0) which will be replace by the new ones.
* @param left Left value of the rectangle
* @param top Top value of the rectangle
* @param right Right value of the rectangle
* @param bottom Bottom value of the rectangle
* @return 0 if anwer was found. -1 in case no more answer
* available for this question
*/
int getRegionNext(uint16 &left, uint16 &top, uint16 &right, uint16 &bottom);
/**
* Rewinds the internal answer counter for the function
* getRegionNext()
*/
void getRegionRewind();
/**
* Handles some flags which are used during a TAT. The game seems to
* use this flags to skip some questions during a TAT.
* OpCode_0x42(2)
*/
void opFlags();
/**
* Handles all Exit Poll commands. The exit poll (EP) questions are
* described in detail in the file EPAIDB.RLE.
* OpCode_0x42(1)
*/
void opExitPoll();
/**
* Handles all TAT commands. The TAT questions are described in detail
* in the file TATAIDB.RLE
*/
void opTat();
private:
Common::RandomSource _random;
void inline setScriptVar(uint16 var, byte value);
void inline setScriptVar16(uint16 var, uint16 value);
uint16 inline getScriptVar16(uint16 var);
byte *_scriptVariables;
/**
* Loads the description part of the regions.rle file into memory
* This makes it faster to search for the correct quesion.
*/
void regionsInit();
void regionsLoad();
// Variables for region handling
int _numRegionHeaders;
int _curAnswerIndex;
int _curQuestNumAnswers;
TlcRegion _curQuestRegions[8];
TlcRegionsHeader *_regionHeader;
/**
* Functions for Exit Poll Commands
*/
void epInit();
void epSelectNextQuestion();
void epResultQuestion();
void epResultEpisode();
// Variables for Exit Poll handling
int16 _epScoreBin[6];
int _epEpisodeIdx; // 15 Episodes: 0..14
int _epQuestionIdx; // 1..X (questions in current episode. counted up for every question)
int _epQuestionNumOfPool; // 1..X (question number in the data base. The questions are played in random order)
int _epQuestionsInEpisode;
TlcEpQuestionData *_epQuestionsData;
// Variables for flag handling
byte _tatFlags[0x0E][0x09];
/**
* Functions for TAT Commands
*/
void tatInitRegs();
void tatLoadDB();
void tatLoadDBHeaders();
void tatResultQuest();
void tatResultEpisode();
void tatGetProfile();
void debugTatFlags(int y, int y2);
// Variables for TAT handling
int _tatEpisodes;
int _tatQuestCount;
TlcTatHeader *_tatHeaders;
TlcTatQuestions *_tatQuestions;
// uint8 _tatCoeffs[15][16];
#endif
};
} // End of Groovie namespace
#endif // GROOVIE_LOGIC_TLCGAME_H

View File

@@ -0,0 +1,987 @@
/* 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/groovie.h"
#include "groovie/logic/triangle.h"
namespace Groovie {
namespace {
extern const int8 triangleLookup1[12];
extern const int8 triangleLookup2[12];
extern const int8 triangleLookup3[12];
extern const int8 triangleLogicTable[924];
}
TriangleGame::TriangleGame() : _random("TriangleGame") {
init();
#if 0
test();
#endif
}
void TriangleGame::run(byte *scriptVariables) {
byte op = scriptVariables[3];
uint8 move;
switch (op) {
case 3:
init();
scriptVariables[3] = 0;
return;
case 4:
// Samantha AI
move = sub03(2);
break;
case 5:
// Stauf AI (only called after Samantha)
move = sub03(1);
break;
default:
// Player and then Stauf
debugC(kDebugLogic, "player chose spot %d", (int)(scriptVariables[1]) + (10 * (int)scriptVariables[0]));
setCell(scriptVariables[1] + 10 * scriptVariables[0], 2);
scriptVariables[3] = sub02();
if (scriptVariables[3] == 0) {
move = sub03(1);
} else {
debugC(kDebugLogic, "winner: %d", (int)scriptVariables[3]);
return;
}
}
scriptVariables[0] = move / 10;
scriptVariables[1] = move % 10;
scriptVariables[3] = sub02();
debugC(kDebugLogic, "stauf chose spot %d, winner: %d", (int)move, (int)scriptVariables[3]);
}
void TriangleGame::init() {
debugC(kDebugLogic, "TriangleGame::init(), seed: %u", _random.getSeed());
_triangleCellCount = 0;
memset(_triangleCells, 0, 66);
}
int8 TriangleGame::sub02() {
int8 v6[132];
int8 v7[68];
sub05(_triangleCells, v6, v7);
for (int i = 0; v6[i] != 66; i++) {
bool v1 = false;
bool v2 = false;
bool pl = false;
// There could be several sections, each one
// ends with 66. And the overall list ends with 66 too
// Hence, two loops
for (; v6[i] != 66; i++) {
if (!triangleLogicTable[14 * v6[i] + 6])
pl = true;
if (!triangleLogicTable[14 * v6[i] + 7])
v2 = true;
if (!triangleLogicTable[14 * v6[i] + 8])
v1 = true;
}
if (pl && v2 && v1)
return _triangleCells[v6[i - 1]];
}
return 0;
}
int8 TriangleGame::sub03(int8 player) {
int8 pickedMoves[4];
int8 tempTriangle1[68];
int8 tempTriangle2[68];
int8 a6a[132];
int8 tempTriangle3[68];
int8 tempMoves[132];
int8 pos;
if (_triangleCellCount >= 2) {
sub05(_triangleCells, tempMoves, tempTriangle3);
sub07(tempMoves, _triangleCells, tempTriangle3, tempTriangle2, tempTriangle1, a6a);
// Find move until valid one
(pos = sub09(player, tempTriangle2, tempTriangle1, a6a, _triangleCells)) != 66 ||
(pos = sub10(player, tempTriangle1, _triangleCells)) != 66 ||
(pos = sub12(player, a6a, _triangleCells, tempTriangle1)) != 66 ||
(pos = sub09(3 - player, tempTriangle2, tempTriangle1, a6a, _triangleCells));
if (pos == 66) {
pos = _random.getRandomNumber(65);
int8 oldPos = pos;
while (_triangleCells[pos]) {
if (++pos > 65)
pos = 0;
if (oldPos == pos) {
pos = 66;
break;
}
}
}
} else {
int8 max = 0;
if (!_triangleCells[24]) {
pickedMoves[0] = 24;
max = 1;
}
if (!_triangleCells[31])
pickedMoves[max++] = 31;
if (!_triangleCells[32])
pickedMoves[max++] = 32;
if (max)
pos = pickedMoves[_random.getRandomNumber(max - 1)];
else {
warning("TriangleGame: Undefined behaviour");
pos = 0; // tempMoves is uninitalized in this branch, so just return 0
}
}
if (pos != 66)
setCell(pos, player);
return pos;
}
void TriangleGame::sub05(int8 *triangleCells, int8 *tempMoves, int8 *tempTriangle) {
int8 dest[8];
for (int i = 0; i < 66; i++)
tempTriangle[i] = triangleCells[i];
int v16 = 3;
for (int j = 0; j < 66; ++j) {
if (triangleCells[j]) {
bool flag = false;
copyLogicRow(j, triangleCells[j], dest);
for (int8 *k = dest; *k != 66; k++) {
if (j > *k) {
if (flag) {
if (tempTriangle[j] != tempTriangle[*k])
replaceCells(tempTriangle, j, tempTriangle[j], tempTriangle[*k]);
} else {
flag = true;
tempTriangle[j] = tempTriangle[*k];
}
}
}
if (!flag)
tempTriangle[j] = v16++;
}
}
int v11 = 0;
if (v16 > 3) {
for (int v12 = 3; v12 < v16; v12++) {
int v14 = v11;
for (int m = 0; m < 66; m++) {
if (tempTriangle[m] == v12)
tempMoves[v11++] = m;
}
if (v11 != v14)
tempMoves[v11++] = 66;
}
}
tempMoves[v11] = 66;
}
void TriangleGame::sub07(int8 *tempMoves, int8 *triangleCells, int8 *tempTriangle3, int8 *tempTriangle2, int8 *tempTriangle1, int8 *tempMoves2) {
int8 singleRow[8];
int8 tempTriangle[68];
int8 routes[4356];
for (int i = 0; i < 66; i++) {
tempTriangle2[i] = tempTriangle3[i];
tempTriangle1[i] = 0;
}
if (*tempMoves != 66) {
for (int8 *ptr1 = tempMoves; *ptr1 != 66; ptr1++) {
int8 *v45 = &routes[66 * tempTriangle3[*ptr1]];
*v45 = 66;
while (*ptr1 != 66) {
copyLogicRow(*ptr1++, 0, singleRow);
collapseLoops(v45, singleRow);
}
}
for (int8 *ptr2 = tempMoves; *ptr2 != 66; ptr2++) {
int8 *j;
for (j = ptr2; *j != 66; j++)
;
for (int8 *k = j + 1; *k != 66; k++) {
if (triangleCells[*k] == triangleCells[*ptr2]) {
int8 val1 = tempTriangle2[*k];
int8 val2 = tempTriangle2[*ptr2];
if (val1 != val2) {
int8 lookupLen = copyLookup(&routes[66 * val2], &routes[66 * val1], tempTriangle);
if (lookupLen == 1) {
if (triangleCells[*ptr2] == 1) {
tempTriangle1[tempTriangle[0]] |= 1u;
} else if (triangleCells[*ptr2] == 2) {
tempTriangle1[tempTriangle[0]] |= 0x40u;
}
} else if (lookupLen > 1) {
int8 v16 = lookupLen - 1;
do {
if (triangleCells[*ptr2] == 1) {
tempTriangle1[tempTriangle[v16]] |= 0x10u;
} else if (triangleCells[*ptr2] == 2) {
tempTriangle1[tempTriangle[v16]] |= 0x20u;
}
} while (v16--);
collapseLoops(&routes[66 * tempTriangle2[*k]], &routes[66 * tempTriangle2[*ptr2]]);
int8 from = tempTriangle2[*ptr2];
int8 to = tempTriangle2[*k];
for (int m = 0; m < 66; m++) {
if (tempTriangle2[m] == from)
tempTriangle2[m] = to;
}
}
}
}
for (; *k != 66; k++)
;
}
for (; *ptr2 != 66; ptr2++)
;
}
}
int8 maxVal = 0;
for (int i = 0; i < 66; i++) {
if (tempTriangle2[i] > maxVal)
maxVal = tempTriangle2[i];
}
int8 len2 = 0;
for (int i = 3; i <= maxVal; i++) {
int8 prevLen2 = len2;
for (int j = 0; j < 66; j++) {
if (tempTriangle2[j] == i)
tempMoves2[len2++] = j;
}
if (prevLen2 != len2)
tempMoves2[len2++] = 66;
}
tempMoves2[len2] = 66;
for (int8 *ptr3 = tempMoves2; *ptr3 != 66; ptr3++) {
bool flag1 = false, flag2 = false, flag3 = false;
int8 row = tempTriangle2[*ptr3];
byte mask1 = 0, mask2 = 0;
for (int i = 0; i < 66; i++) {
if (tempTriangle2[i] == row && triangleCells[i]) {
if (triangleCells[i] == 1) {
mask1 = 1;
mask2 = 16;
} else {
mask1 = 64;
mask2 = 32;
}
break;
}
}
while (*ptr3 != 66) {
if (!triangleLogicTable[14 * *ptr3 + 6])
flag1 = 1;
if (!triangleLogicTable[14 * *ptr3 + 7])
flag2 = 1;
if (!triangleLogicTable[14 * *ptr3 + 8])
flag3 = 1;
++ptr3;
}
if (!flag1) {
int8 lookup1 = copyLookup(triangleLookup1, &routes[66 * row], tempTriangle);
if (lookup1 == 1) {
tempTriangle1[tempTriangle[0]] |= mask1;
} else if (lookup1 > 1) {
int k = lookup1 - 1;
do
tempTriangle1[tempTriangle[k]] |= mask2;
while (k--);
flag1 = 1;
}
}
if (!flag2) {
int8 lookup2 = copyLookup(triangleLookup2, &routes[66 * row], tempTriangle);
if (lookup2 == 1) {
tempTriangle1[tempTriangle[0]] |= mask1;
} else if (lookup2 > 1) {
int k = lookup2 - 1;
do
tempTriangle1[tempTriangle[k]] |= mask2;
while (k--);
flag2 = 1;
}
}
if (!flag3) {
int8 lookup3 = copyLookup(triangleLookup3, &routes[66 * row], tempTriangle);
if (lookup3 == 1) {
tempTriangle1[tempTriangle[0]] |= mask1;
} else if (lookup3 > 1) {
int k = lookup3 - 1;
do
tempTriangle1[tempTriangle[k]] |= mask2;
while (k--);
flag3 = 1;
}
}
byte mask3 = 0;
if (flag1)
mask3 = 4;
if (flag2)
mask3 |= 8u;
if (flag3)
mask3 |= 2u;
for (int i = 0; i < 66; i++) {
if (tempTriangle2[i] == row)
tempTriangle1[i] |= mask3;
}
}
}
int8 TriangleGame::sub09(int8 player, int8 *tempTriangle2, int8 *tempTriangle1, int8 *a4, int8 *triangleCells) {
int8 movesTable[280];
int numDir1 = 0;
int numDir2 = 0;
int numDir3 = 0;
int numDir4 = 0;
int row = 0;
for (const int8 *tPtr = &triangleLogicTable[6]; tPtr < &triangleLogicTable[924]; tPtr += 14, row++) {
if (!triangleCells[row] && (tempTriangle1[row] & (player == 1 ? 1 : 64)) != 0) {
int c1 = 0, c2 = 0, c3 = 0;
int mask = 0;
copyLogicRow(row, player, movesTable);
for (int8 *movPtr = movesTable; *movPtr != 66; ++movPtr) {
int row2 = 0;
mask |= tempTriangle1[*movPtr];
for (const int8 *tPtr2 = &triangleLogicTable[6]; tPtr2 < &triangleLogicTable[924]; tPtr2 += 14, row2++) {
if (tempTriangle2[row2] == tempTriangle2[*movPtr]) {
c1 += (tPtr2[0] == 0) ? 1 : 0;
c2 += (tPtr2[1] == 0) ? 1 : 0;
c3 += (tPtr2[2] == 0) ? 1 : 0;
}
}
}
if (c1)
mask &= ~4u;
if (c2)
mask &= ~8u;
if (c3)
mask &= ~2u;
if (tPtr[0] || c1) {
if (tPtr[1] || c2) {
if (tPtr[2] || c3) {
if (mask) {
if (mask == 0xe) {
movesTable[numDir2 + 76] = row;
numDir2++;
} else if (mask == 2 || mask == 8 || mask == 4) {
movesTable[numDir4 + 212] = row;
numDir4++;
} else {
movesTable[numDir3 + 144] = row;
numDir3++;
}
}
} else {
movesTable[numDir1 + 8] = row;
numDir1++;
}
} else {
movesTable[numDir1 + 8] = row;
numDir1++;
}
} else {
movesTable[numDir1 + 8] = row;
numDir1++;
}
}
}
if (numDir1)
return movesTable[_random.getRandomNumber(numDir1 - 1) + 8];
if (numDir2)
return movesTable[_random.getRandomNumber(numDir2 - 1) + 76];
if (numDir3)
return movesTable[_random.getRandomNumber(numDir3 - 1) + 144];
if (numDir4)
return movesTable[_random.getRandomNumber(numDir4 - 1) + 212];
return 66;
}
int8 TriangleGame::sub10(int8 player, int8 *a2, int8 *triangleCells) {
int8 *destPtr;
byte mask;
int counter;
int8 dest1[8];
int8 dest2[68];
mask = 0;
counter = 0;
if (player == 1)
mask = 16;
else if (player == 2)
mask = 32;
for (int i = 0; i < 66; ++i) {
if (!triangleCells[i] && (mask & (byte)a2[i]) != 0) {
copyLogicRow(i, player, dest1);
destPtr = dest1;
while (*destPtr != 66) {
if ((a2[*destPtr] & 0xE) == 0xE) {
dest2[counter] = i;
counter++;
break;
}
destPtr++;
}
}
}
if (counter)
return dest2[_random.getRandomNumber(counter - 1)];
return 66;
}
int8 TriangleGame::sub12(int8 player, int8 *tempMoves, int8 *triangleCells, int8 *tempTriangle) {
int8 moves[8];
int8 tempTriangle1[68];
int8 tempTriangle2[68];
int8 tempTriangle3[68];
int8 tempTriangle4[68];
int8 result = 66;
int maxlen = -1;
int8 *startPtr = triangleCells;
for (int8 *ptr = tempMoves; *ptr != 66; ptr++) {
int len = 0;
int8 *beg = ptr;
int8 p0 = *ptr;
for (; *ptr != 66; ++ptr)
++len;
if (len > maxlen && triangleCells[p0] == player) {
maxlen = len;
startPtr = beg;
}
}
tempTriangle4[0] = 66;
for (int8 *ptr = startPtr; *ptr != 66; ++ptr) {
if (sub13(*ptr, triangleCells, moves))
collapseLoops(tempTriangle4, moves);
}
int len1 = 0, len2 = 0, len3 = 0;
tempTriangle1[0] = 66;
tempTriangle2[0] = 66;
tempTriangle3[0] = 66;
for (int8 *ptr = tempTriangle4; *ptr != 66; ++ptr) {
int8 v13 = 100;
int8 v15 = triangleLogicTable[14 * *ptr + 11];
if (v15 < 100)
v13 = triangleLogicTable[14 * *ptr + 11];
int8 v16 = triangleLogicTable[14 * *ptr + 13];
if (v13 > v16)
v13 = triangleLogicTable[14 * *ptr + 13];
int8 v17 = triangleLogicTable[14 * *ptr + 12];
if (v13 > v17)
v13 = triangleLogicTable[14 * *ptr + 12];
if (v13 == v15) {
tempTriangle1[len1++] = *ptr;
} else if (v13 == v17) {
tempTriangle2[len2++] = *ptr;
} else if (v13 == v16) {
tempTriangle3[len3++] = *ptr;
}
}
bool flag1 = false, flag2 = false, flag3 = false;
tempTriangle3[len3] = 66;
tempTriangle2[len2] = 66;
tempTriangle1[len1] = 66;
int8 startVal = tempTriangle[*startPtr];
switch (startVal) {
case 8:
flag3 = true;
break;
case 4:
flag2 = true;
break;
case 2:
flag1 = true;
break;
case 12:
flag2 = true;
flag3 = flag2;
break;
case 6:
flag1 = true;
flag2 = true;
break;
case 10:
flag1 = true;
flag3 = true;
break;
case 14:
flag2 = false;
flag1 = false;
flag3 = flag2;
break;
default:
flag2 = true;
flag1 = true;
flag3 = flag2;
break;
}
int minLen = 101;
if (flag1) {
for (int8 *ptr = tempTriangle1; *ptr != 66; ++ptr) {
int8 part1 = 0;
if ((startVal & 8) == 0)
part1 = triangleLogicTable[14 * *ptr + 7];
int8 part2 = 0;
if ((startVal & 4) == 0)
part2 = triangleLogicTable[14 * *ptr + 6];
if (minLen > part1 + part2) {
minLen = part1 + part2;
result = *ptr;
}
}
}
if (flag2) {
for (int8 *ptr = tempTriangle2; *ptr != 66; ++ptr) {
int8 part1 = 0;
if ((startVal & 8) == 0)
part1 = triangleLogicTable[14 * *ptr + 7];
int8 part2 = 0;
if ((startVal & 2) == 0)
part2 = triangleLogicTable[14 * *ptr + 8];
if (minLen > part1 + part2) {
minLen = part1 + part2;
result = *ptr;
}
}
}
if (flag3) {
for (int8 *ptr = tempTriangle3; *ptr != 66; ++ptr) {
int8 part1 = 0;
if ((startVal & 2) == 0)
part1 = triangleLogicTable[14 * *ptr + 8];
int8 part2 = 0;
if ((startVal & 4) == 0)
part2 = triangleLogicTable[14 * *ptr + 6];
if (minLen > part1 + part2) {
minLen = part1 + part2;
result = *ptr;
}
}
}
return result;
}
int TriangleGame::sub13(int8 row, int8 *triangleCells, int8 *moves) {
int pos = 0;
for (int i = 0; i < 6; i++) {
int8 v6 = triangleLogicTable[14 * row + i];
if (v6 != -1 && !triangleCells[v6]) {
int v7 = (i + 1) % 6;
int8 v8 = triangleLogicTable[14 * row + v7];
if (v8 != -1 && !triangleCells[v8]) {
int8 v9 = triangleLogicTable[14 * v6 + v7];
if (v9 != -1 && !triangleCells[v9])
moves[pos++] = v9;
}
}
}
moves[pos] = 66;
return pos;
}
void TriangleGame::setCell(int8 cellnum, int8 val) {
assert(cellnum >= 0);
assert(cellnum < 66);
if (cellnum >= 0 && cellnum < 66) {
++_triangleCellCount;
assert(_triangleCells[cellnum] == 0);
_triangleCells[cellnum] = val;
}
}
void TriangleGame::copyLogicRow(int row, int8 key, int8 *dest) {
int pos = 0;
for (int i = 0; i < 6; i++) {
int8 val = triangleLogicTable[14 * row + i];
if (val != -1 && _triangleCells[val] == key)
dest[pos++] = val;
}
dest[pos] = 66;
}
void TriangleGame::replaceCells(int8 *tempTriangle, int limit, int8 from, int8 to) {
for (int i = 0; i <= limit; ++i) {
if (tempTriangle[i] == from)
tempTriangle[i] = to;
}
}
int TriangleGame::copyLookup(const int8 *lookup, int8 *start, int8 *dest){
int counter = 0;
if (*lookup == 66) {
*dest = 66;
return counter;
}
for (; *lookup != 66; lookup++) {
for (int8 *ptr = start; *ptr != 66; ptr++) {
if (*ptr == *lookup)
dest[counter++] = *lookup;
}
}
dest[counter] = 66;
return counter;
}
void TriangleGame::collapseLoops(int8 *route, int8 *singleRow) {
int len = 0;
for (int8 *i = route; *i != 66; i++)
len++;
int origlen = len;
for (int8 *i = singleRow; *i != 66; i++) {
int j;
for (j = 0; j < len; j++) {
if (route[j] == *i)
break;
}
if (j == len)
route[len++] = *i;
}
if (len != origlen)
route[len] = 66;
}
bool TriangleGame::testGame(uint32 seed, const Common::Array<uint8> moves, bool playerWin) {
byte vars[1024];
byte &op = vars[3];
byte &move10s = vars[0];
byte &move1s = vars[1];
byte &winner = vars[3];
memset(vars, 0, sizeof(vars));
op = 3;
run(vars);
warning("starting TriangleGame::testGame(%u, %u, %d)", seed, moves.size(), (int)playerWin);
_random.setSeed(seed);
for (uint i = 0; i < moves.size(); i++) {
if (i % 2) {
// check Stauf's move
uint8 move = ((uint)move10s * 10) + (uint)move1s;
if (move != moves[i]) {
error("%u: bad Stauf move: %d", (int)i, (int)move);
// return false here is useful for finding the right seed to test
return false;
}
continue;
}
// else, input player's move
if (winner != 0)
error("%u: early winner: %d", (int)i, (int)winner);
uint8 move = moves[i];
move10s = move / 10;
move1s = move % 10;
op = 0;
run(vars);
}
if (playerWin && winner != 2)
error("player didn't win, winner: %d", (int)winner);
if (playerWin == false && winner != 1)
error("Stauf didn't win, winner: %d", (int)winner);
warning("finished TriangleGame::testGame(%u, %u, %d)", seed, moves.size(), (int)playerWin);
return true;
}
void TriangleGame::ensureSamanthaWin(uint32 seed) {
byte vars[1024];
byte &op = vars[3];
byte &winner = vars[3];
op = 3;
run(vars);
warning("starting TriangleGame::ensureSamanthaWin(%u)", seed);
_random.setSeed(seed);
for (int i = 0; i < 100; i++) {
// Samantha
op = 4;
run(vars);
if (winner)
break;
// Stauf
op = 5;
run(vars);
if (winner)
break;
}
if (winner != 2)
error("Samantha didn't win, winner: %d", (int)winner);
warning("finished TriangleGame::ensureSamanthaWin(%u)", seed);
}
void TriangleGame::testPlayRandomly(uint32 seed) {
byte vars[1024];
byte &op = vars[3];
byte &move10s = vars[0];
byte &move1s = vars[1];
byte &winner = vars[3];
op = 3;
run(vars);
warning("starting TriangleGame::testPlayRandomly(%u)", seed);
_random.setSeed(seed);
for (int i = 0; i < 100; i++) {
// Player make random move
uint8 move = 0;
do {
move = _random.getRandomNumber(65);
} while (_triangleCells[move]);
move10s = move / 10;
move1s = move % 10;
op = 0;
run(vars);
if (winner)
break;
// Stauf
op = 5;
run(vars);
if (winner)
break;
}
if (winner != 1)
error("Stauf didn't win, winner: %d", (int)winner);
warning("finished TriangleGame::testPlayRandomly(%u)", seed);
}
void TriangleGame::test() {
warning("starting TriangleGame::test");
uint32 oldSeed = _random.getSeed();
// Samantha appears to not always win, but she usually does, and she wins these seeds
// haven't verified if she always wins in the original game
for (uint32 i = 100; i < 105; i++)
ensureSamanthaWin(i);
// Similar thing here, technically there might be a seed where the player wins, but Stauf should win the vast majority of them
for (uint32 i = 200; i < 205; i++)
testPlayRandomly(i);
testGame(1, {24, 32, 30, 42, 37, 53, 45, 39, 19, 47, 20, 56, 55, 59, 36, 49, 29, 46, 23, 38, 18}, true);
testGame(1, {24, 32, 30, 42, 37, 53, 19, 39, 45, 47, 46, 59, 56, 49, 38, 48, 31, 40, 25, 50, 20}, true);
testGame(2, {24, 31, 33, 38, 43, 46, 16, 41, 54, 52, 64, 61, 53, 37, 42, 51, 32, 40, 23, 60, 15}, true);
testGame(2, {24, 31, 33, 38, 43, 46, 16, 41, 53, 52, 64, 61, 54, 37, 34, 50, 25, 36, 17, 0, 10}, true);
testGame(40680, {0, 24, 1, 12, 2, 4, 3, 5, 6, 30, 7, 9, 8, 29, 10, 13, 11, 47, 14, 18, 20, 37, 19, 36, 27, 57, 26, 31}, false);
_random.setSeed(oldSeed);
warning("finished TriangleGame::test");
}
namespace {
const int8 triangleLookup1[12] = {
0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 66
};
const int8 triangleLookup2[12] = {
0, 2, 5, 9, 14, 20, 27, 35, 44, 54, 65, 66
};
const int8 triangleLookup3[12] = {
55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66
};
const int8 triangleLogicTable[924] = {
-1, -1, 2, 1, -1, -1, 0, 0, 10, 10, 6, 0, 10, 10,
0, 2, 4, 3, -1, -1, 0, 1, 9, 9, 5, 1, 9, 9,
-1, -1, 5, 4, 1, 0, 1, 0, 9, 11, 5, 1, 9, 9,
1, 4, 7, 6, -1, -1, 0, 2, 8, 8, 4, 2, 8, 8,
2, 5, 8, 7, 3, 1, 1, 1, 8, 10, 4, 1, 8, 8,
-1, -1, 9, 8, 4, 2, 2, 0, 8, 12, 4, 2, 8, 8,
3, 7, 11, 10, -1, -1, 0, 3, 7, 7, 3, 3, 7, 7,
4, 8, 12, 11, 6, 3, 1, 2, 7, 9, 3, 2, 7, 7,
5, 9, 13, 12, 7, 4, 2, 1, 7, 11, 3, 2, 7, 7,
-1, -1, 14, 13, 8, 5, 3, 0, 7, 13, 3, 3, 7, 7,
6, 11, 16, 15, -1, -1, 0, 4, 6, 6, 3, 4, 6, 6,
7, 12, 17, 16, 10, 6, 1, 3, 6, 8, 2, 3, 6, 6,
8, 13, 18, 17, 11, 7, 2, 2, 6, 10, 2, 2, 6, 6,
9, 14, 19, 18, 12, 8, 3, 1, 6, 12, 2, 3, 6, 6,
-1, -1, 20, 19, 13, 9, 4, 0, 6, 14, 3, 4, 6, 6,
10, 16, 22, 21, -1, -1, 0, 5, 5, 5, 3, 5, 5, 5,
11, 17, 23, 22, 15, 10, 1, 4, 5, 7, 2, 4, 5, 5,
12, 18, 24, 23, 16, 11, 2, 3, 5, 9, 1, 3, 5, 5,
13, 19, 25, 24, 17, 12, 3, 2, 5, 11, 1, 3, 5, 5,
14, 20, 26, 25, 18, 13, 4, 1, 5, 13, 2, 4, 5, 5,
-1, -1, 27, 26, 19, 14, 5, 0, 5, 15, 3, 5, 5, 5,
15, 22, 29, 28, -1, -1, 0, 6, 4, 4, 3, 6, 6, 4,
16, 23, 30, 29, 21, 15, 1, 5, 4, 6, 2, 5, 5, 4,
17, 24, 31, 30, 22, 16, 2, 4, 4, 8, 1, 4, 4, 4,
18, 25, 32, 31, 23, 17, 3, 3, 4, 10, 0, 3, 4, 4,
19, 26, 33, 32, 24, 18, 4, 2, 4, 12, 1, 4, 4, 4,
20, 27, 34, 33, 25, 19, 5, 1, 4, 14, 2, 5, 4, 5,
-1, -1, 35, 34, 26, 20, 6, 0, 4, 16, 3, 6, 4, 6,
21, 29, 37, 36, -1, -1, 0, 7, 3, 3, 3, 7, 7, 3,
22, 30, 38, 37, 28, 21, 1, 6, 3, 5, 2, 6, 6, 3,
23, 31, 39, 38, 29, 22, 2, 5, 3, 7, 1, 5, 5, 3,
24, 32, 40, 39, 30, 23, 3, 4, 3, 9, 0, 4, 4, 3,
25, 33, 41, 40, 31, 24, 4, 3, 3, 11, 0, 4, 3, 4,
26, 34, 42, 41, 32, 25, 5, 2, 3, 13, 1, 5, 3, 5,
27, 35, 43, 42, 33, 26, 6, 1, 3, 15, 2, 6, 3, 6,
-1, -1, 44, 43, 34, 27, 7, 0, 3, 17, 3, 7, 3, 7,
28, 37, 46, 45, -1, -1, 0, 8, 2, 2, 4, 8, 8, 2,
29, 38, 47, 46, 36, 28, 1, 7, 2, 4, 3, 7, 7, 2,
30, 39, 48, 47, 37, 29, 2, 6, 2, 6, 2, 6, 6, 2,
31, 40, 49, 48, 38, 30, 3, 5, 2, 8, 1, 5, 5, 3,
32, 41, 50, 49, 39, 31, 4, 4, 2, 10, 1, 4, 4, 4,
33, 42, 51, 50, 40, 32, 5, 3, 2, 12, 1, 5, 3, 5,
34, 43, 52, 51, 41, 33, 6, 2, 2, 14, 2, 6, 2, 6,
35, 44, 53, 52, 42, 34, 7, 1, 2, 16, 3, 7, 2, 7,
-1, -1, 54, 53, 43, 35, 8, 0, 2, 18, 4, 8, 2, 8,
36, 46, 56, 55, -1, -1, 0, 9, 1, 1, 5, 9, 9, 1,
37, 47, 57, 56, 45, 36, 1, 8, 1, 3, 4, 8, 8, 1,
38, 48, 58, 57, 46, 37, 2, 7, 1, 5, 3, 7, 7, 2,
39, 49, 59, 58, 47, 38, 3, 6, 1, 7, 2, 6, 6, 3,
40, 50, 60, 59, 48, 39, 4, 5, 1, 9, 2, 5, 5, 4,
41, 51, 61, 60, 49, 40, 5, 4, 1, 11, 2, 5, 4, 5,
42, 52, 62, 61, 50, 41, 6, 3, 1, 13, 2, 6, 3, 6,
43, 53, 63, 62, 51, 42, 7, 2, 1, 15, 3, 7, 2, 7,
44, 54, 64, 63, 52, 43, 8, 1, 1, 17, 4, 8, 1, 8,
-1, -1, 65, 64, 53, 44, 9, 0, 1, 19, 5, 9, 1, 9,
45, 56, -1, -1, -1, -1, 0, 10, 0, 0, 6, 10, 10, 0,
46, 57, -1, -1, 55, 45, 1, 9, 0, 2, 5, 9, 9, 1,
47, 58, -1, -1, 56, 46, 2, 8, 0, 4, 4, 8, 8, 2,
48, 59, -1, -1, 57, 47, 3, 7, 0, 6, 3, 7, 7, 3,
49, 60, -1, -1, 58, 48, 4, 6, 0, 8, 3, 6, 6, 4,
50, 61, -1, -1, 59, 49, 5, 5, 0, 10, 3, 5, 5, 5,
51, 62, -1, -1, 60, 50, 6, 4, 0, 12, 3, 6, 4, 6,
52, 63, -1, -1, 61, 51, 7, 3, 0, 14, 3, 7, 3, 7,
53, 64, -1, -1, 62, 52, 8, 2, 0, 16, 4, 8, 2, 8,
54, 65, -1, -1, 63, 53, 9, 1, 0, 18, 5, 9, 1, 9,
-1, -1, -1, -1, 64, 54, 10, 0, 0, 20, 6, 10, 0, 10
};
} // End of anonymous namespace
} // End of Groovie namespace

View File

@@ -0,0 +1,71 @@
/* 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.
*
*/
#ifndef GROOVIE_LOGIC_TRIANGLE_H
#define GROOVIE_LOGIC_TRIANGLE_H
#include "common/random.h"
#include "common/system.h"
namespace Groovie {
class TriangleGame {
public:
TriangleGame();
void run(byte *scriptVariables);
private:
void init();
int8 sub02();
int8 sub03(int8 player);
void sub05(int8 *triangleCells, int8 *a2, int8 *a3);
void sub07(int8 *tempMoves, int8 *triangleCells, int8 *tempTriangle3, int8 *tempTriangle2, int8 *tempTriangle1, int8 *tempMoves2);
int8 sub09(int8 key, int8 *a2, int8 *a3, int8 *a4, int8 *triangleCells);
int8 sub10(int8 key, int8 *a2, int8 *triangleCells);
int8 sub12(int8 a1, int8 *a2, int8 *triangleCells, int8 *a4);
int sub13(int8 row, int8 *triangleCells, int8 *moves);
void setCell(int8 cellnum, int8 val);
void copyLogicRow(int row, int8 key, int8 *dest);
void replaceCells(int8 *tempTriangle, int limit, int8 from, int8 to);
int copyLookup(const int8 *lookup, int8 *start, int8 *dest);
void collapseLoops(int8 *route, int8 *singleRow);
bool testGame(uint32 seed, Common::Array<uint8> moves, bool player_win);
void ensureSamanthaWin(uint32 seed);
void testPlayRandomly(uint32 seed);
void test();
private:
int _triangleCellCount;
int8 _triangleCells[66];
Common::RandomSource _random;
};
} // End of Groovie namespace
#endif // GROOVIE_LOGIC_TRIANGLE_H

View File

@@ -0,0 +1,642 @@
/* 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/groovie.h"
#include "groovie/logic/winerack.h"
namespace Groovie {
enum WineBottle {
kWineBottleOpponent = 1,
kWineBottlePlayer = 2
};
namespace {
extern const int8 wineRackLogicTable[1200];
}
WineRackGame::WineRackGame() : _random("WineRackGame"), _totalBottles(0) {
memset(_wineRackGrid, 0, 100);
#if 0
runTests();
#endif
}
void WineRackGame::run(byte *scriptVariables) {
char op = scriptVariables[3];
byte pos = 0;
/* positions on the board
* north = 9 (0, 9)
south = 90 (9, 0)
east = 99 (9, 9)
west = 0 (0, 0)
*/
switch (op) {
case 3:
debugC(kDebugLogic, "WineRackGame initGrid, seed: %u", _random.getSeed());
initGrid(scriptVariables[4]);
break;
case 4: // Player's move
pos = calculateNextMove(kWineBottlePlayer);
placeBottle(pos, kWineBottlePlayer);
scriptVariables[0] = pos / 10;
scriptVariables[1] = pos % 10;
scriptVariables[3] = didPlayerWin();
break;
case 5: // Opponent's move
scriptVariables[3] = 0;
pos = calculateNextMove(kWineBottleOpponent);
placeBottle(pos, kWineBottleOpponent);
scriptVariables[0] = pos / 10;
scriptVariables[1] = pos % 10;
scriptVariables[3] = didAiWin() != 0 ? 1 : 0;
break;
default:
scriptVariables[3] = 0;
placeBottle(scriptVariables[0] * 10 + scriptVariables[1], 2);
if (didPlayerWin()) {
scriptVariables[3] = 2;
} else {
pos = calculateNextMove(kWineBottleOpponent);
placeBottle(pos, kWineBottleOpponent);
scriptVariables[0] = pos / 10;
scriptVariables[1] = pos % 10;
scriptVariables[3] = didAiWin() != 0 ? 1 : 0;
}
break;
}
}
void WineRackGame::initGrid(byte difficulty) {
memset(_wineRackGrid, 0, 100);
switch (difficulty) {
case 0:
_totalBottles = 24;
_wineRackGrid[15] = kWineBottlePlayer;
_wineRackGrid[18] = kWineBottleOpponent;
_wineRackGrid[19] = kWineBottleOpponent;
_wineRackGrid[20] = kWineBottleOpponent;
_wineRackGrid[21] = kWineBottleOpponent;
_wineRackGrid[22] = kWineBottleOpponent;
_wineRackGrid[23] = kWineBottleOpponent;
_wineRackGrid[25] = kWineBottlePlayer;
_wineRackGrid[26] = kWineBottleOpponent;
_wineRackGrid[27] = kWineBottleOpponent;
_wineRackGrid[28] = kWineBottleOpponent;
_wineRackGrid[33] = kWineBottleOpponent;
_wineRackGrid[34] = kWineBottleOpponent;
_wineRackGrid[35] = kWineBottlePlayer;
_wineRackGrid[36] = kWineBottleOpponent;
_wineRackGrid[44] = kWineBottlePlayer;
_wineRackGrid[45] = kWineBottlePlayer;
_wineRackGrid[54] = kWineBottlePlayer;
_wineRackGrid[62] = kWineBottlePlayer;
_wineRackGrid[63] = kWineBottlePlayer;
_wineRackGrid[64] = kWineBottlePlayer;
_wineRackGrid[72] = kWineBottlePlayer;
_wineRackGrid[82] = kWineBottlePlayer;
_wineRackGrid[91] = kWineBottlePlayer;
break;
case 1:
_totalBottles = 12;
_wineRackGrid[75] = kWineBottlePlayer;
_wineRackGrid[56] = kWineBottlePlayer;
_wineRackGrid[45] = kWineBottlePlayer;
_wineRackGrid[27] = kWineBottlePlayer;
_wineRackGrid[24] = kWineBottlePlayer;
_wineRackGrid[15] = kWineBottlePlayer;
_wineRackGrid[64] = kWineBottleOpponent;
_wineRackGrid[34] = kWineBottleOpponent;
_wineRackGrid[33] = kWineBottleOpponent;
_wineRackGrid[18] = kWineBottleOpponent;
_wineRackGrid[16] = kWineBottleOpponent;
_wineRackGrid[14] = kWineBottleOpponent;
break;
default:
_totalBottles = 0;
break;
}
}
void WineRackGame::placeBottle(byte pos, byte val) {
debugC(kDebugLogic, "placeBottle(%d, %d)", (int)pos, (int)val);
_totalBottles++;
assert(_wineRackGrid[pos] == 0);
_wineRackGrid[pos] = val;
}
int8 WineRackGame::calculateNextMove(byte player) {
int8 moves1[24];
int8 moves2[24];
byte playerIndex = (player == 1) + 1;
if (!_totalBottles)
return randomMoveStart();
if (_totalBottles == 1)
return randomMoveStart2();
sub05(player, moves1);
sub05(playerIndex, moves2);
int8 result = sub06(moves1, moves2);
if (result == -1)
return findEmptySpot();
assert(_wineRackGrid[result] == 0);
return result;
}
int8 WineRackGame::findEmptySpot() {
int8 result = 0;
while (_wineRackGrid[result]) {
if (result == 99)
return 100;
else
++result;
}
return result;
}
void WineRackGame::sub05(int8 player, int8 *moves) {
int8 moves1[23];
int8 moves2[23];
int8 to, delta, playerIndex;
if (player == 1) {
to = 90;
delta = 10;
playerIndex = 2;
} else {
to = 9;
delta = 1;
playerIndex = 3;
}
memset(moves, 0, 23);
for (int i = 0; i < to; i += delta) {
if (!_wineRackGrid[i] || _wineRackGrid[i] == player) {
memset(moves1, 0, sizeof(moves1));
memset(moves2, 0, sizeof(moves2));
sub13(i, playerIndex, moves1, moves2);
if (moves[0] < moves1[0]) {
memcpy(moves, moves1, 23);
}
}
}
}
int8 WineRackGame::sub06(int8 *moves1, int8 *moves2) {
for (int i = 0; i < moves1[2]; i++) {
int8 result = moves1[i + 3];
if (!_wineRackGrid[result]) {
for (int j = 0; j < moves2[2]; j++)
if (moves2[j + 3] == result)
return result;
}
}
for (int i = 0; i < moves1[2]; i++) {
if (!_wineRackGrid[moves1[i + 3]])
return moves1[i + 3];
}
return -1;
}
uint32 WineRackGame::didPlayerWin() {
memset(_wineRackGrid2, 0, 100);
for (int i = 0; i < 10; i++) {
if (_wineRackGrid[i] == kWineBottlePlayer) {
int var = 0;
sub10(100, i, 2, 3, &var);
if (var == 1)
return 1;
}
}
return 0;
}
void WineRackGame::sub10(int8 endPos, int8 pos, int unused, int player, int *val) {
int8 candidates[8];
if (*val)
return;
if (wineRackLogicTable[12 * pos + player] == -1) {
*val = 1;
return;
}
sub11(pos, candidates);
for (int i = 0; candidates[i] != 100; i++) {
int8 nextPos = candidates[i];
if (endPos != nextPos)
sub10(pos, nextPos, unused, player, val);
}
}
void WineRackGame::sub11(int8 pos, int8 *candidates) {
int cnt = 0;
_wineRackGrid2[pos] = 1;
for (int i = 0; i < 6; i++) {
int8 val = wineRackLogicTable[12 * pos + i];
if (!_wineRackGrid2[val] && _wineRackGrid[pos] == _wineRackGrid[val])
candidates[cnt++] = val;
}
candidates[cnt] = 100;
}
uint32 WineRackGame::didAiWin() {
memset(_wineRackGrid2, 0, 100);
for (int i = 0; i < 100; i += 10) {
if (_wineRackGrid[i] == kWineBottleOpponent) {
int var = 0;
sub10(100, i, 1, 2, &var);
if (var == 1)
return 1;
}
}
return 0;
}
void WineRackGame::sub13(int8 cell, int8 player, int8 *moves1, int8 *moves2) {
int8 candidates[4];
if (cell == -1)
return;
moves2[moves2[2] + 3] = cell;
if (wineRackLogicTable[12 * cell + player] < 0) {
++moves2[2];
moves2[0] = countEmtpy(moves2);
if (moves2[0] > moves1[0])
memcpy(moves1, moves2, 23);
--moves2[2];
} else {
++moves2[2];
if (player == 2)
sub15(cell, candidates);
else
sub16(cell, candidates);
for (int i = 0; candidates[i] != -1; i++)
sub13(candidates[i], player, moves1, moves2);
--moves2[2];
}
}
void WineRackGame::sub15(int8 cell, int8 *candidates) {
int8 depth = 0;
int8 pos2 = wineRackLogicTable[12 * cell + 2];
int8 pos1 = wineRackLogicTable[12 * cell + 1];
if (_wineRackGrid[pos2] == 2) {
if (pos1 < 0 || _wineRackGrid[pos1] == 2) {
if (cell >= 20) {
int8 val1 = _wineRackGrid[cell - 10];
if (val1 != 2) {
int8 val2 = _wineRackGrid[cell - 10];
if (val2 != 2 && (val1 == 1 || val2 == 1)) {
depth = 1;
candidates[0] = cell - 10;
}
}
}
if (cell < 80) {
int8 val1 = _wineRackGrid[cell + 10];
if (val1 != 2) {
int8 val2 = _wineRackGrid[cell + 11];
if (val2 != 2 && (val1 == 1 || val2 == 1)) {
candidates[depth] = cell + 10;
depth++;
}
}
}
} else if (_wineRackGrid[cell] == 1 || _wineRackGrid[pos1] == 1) {
depth = 1;
candidates[0] = pos1;
}
} else if (pos1 < 0 || _wineRackGrid[pos1] == 2) {
if (_wineRackGrid[cell] == 1 || _wineRackGrid[pos2] == 1) {
depth = 1;
candidates[0] = pos2;
}
} else {
depth = 2;
candidates[0] = pos2;
candidates[1] = pos1;
}
candidates[depth] = -1;
}
void WineRackGame::sub16(int8 cell, int8 *candidates) {
int8 depth = 0;
int8 pos3 = wineRackLogicTable[12 * cell + 3];
int8 pos4 = wineRackLogicTable[12 * cell + 4];
if (_wineRackGrid[pos3] == 1) {
if (pos4 < 0 || _wineRackGrid[pos4] == 1) {
if (cell % 10 >= 2) {
int8 val1 = _wineRackGrid[cell - 1];
if (val1 != 1) {
int8 val2 = _wineRackGrid[cell + 8];
if (val2 != 1 && (val1 == 2 || val2 == 2)) {
depth = 1;
candidates[0] = cell - 1;
}
}
}
if (cell < 80 && _wineRackGrid[cell + 1] != 1) {
int8 val1 = _wineRackGrid[cell + 11];
if (val1 != 1 && (_wineRackGrid[cell + 1] == 2 || val1 == 2)) {
candidates[depth] = cell + 1;
depth++;
}
}
} else if (_wineRackGrid[cell] == 2 || _wineRackGrid[pos4] == 2) {
depth = 1;
candidates[0] = pos4;
}
} else if (pos4 < 0 || _wineRackGrid[pos4] == 1) {
if (_wineRackGrid[cell] == 2 || _wineRackGrid[pos3] == 2) {
depth = 1;
candidates[0] = pos3;
}
} else {
depth = 2;
candidates[0] = pos3;
candidates[1] = pos4;
}
candidates[depth] = -1;
}
int8 WineRackGame::countEmtpy(int8 *moves) {
int8 cnt = 0;
for (int i = 0; i < moves[2]; i++) {
if (!_wineRackGrid[moves[i + 3]])
++cnt;
}
return 20 - cnt;
}
int8 WineRackGame::randomMoveStart() {
const int8 moves[] = { 44, 45, 54, 55 };
return moves[_random.getRandomNumber(3)];
}
int8 WineRackGame::randomMoveStart2() {
const int8 moves[] = { 25, 26, 63, 64 };
// the original game doesn't ensure the spot isn't taken
int8 res = 0;
do {
res = moves[_random.getRandomNumber(3)];
} while (_wineRackGrid[res] != 0);
return res;
}
void WineRackGame::testWinCondition(byte player, int baseX, int baseY) {
initGrid(2);
int basePos = baseX * 10 + baseY;
for (int i = 0; i < 10; i++) {
if (player == kWineBottlePlayer)
placeBottle(i * 10 + basePos, player);
else
placeBottle(i + basePos, player);
}
if (player == kWineBottlePlayer && !didPlayerWin()) {
error("WineRackGame::testWinCondition(%d, %d, %d) failed", (int)player, baseX, baseY);
} else if (player == kWineBottleOpponent && !didAiWin()) {
error("WineRackGame::testWinCondition(%d, %d, %d) failed", (int)player, baseX, baseY);
}
}
void WineRackGame::testGame(uint32 seed, Common::Array<int> moves, bool playerWin) {
byte vars[1024] = {};
byte &x = vars[0];
byte &y = vars[1];
byte &op = vars[3];
byte &winner = vars[3];
byte &difficulty = vars[4];
_random.setSeed(seed);
difficulty = 2;
op = 3;
run(vars);
winner = 0;
for (uint i = 0; i < moves.size(); i += 2) {
if (winner)
error("early winner");
x = moves[i];
y = moves[i + 1];
op = 1;
run(vars);
}
if (playerWin && winner != 2)
error("WineRackGame::testGame(%u, %u, %d) player didn't win", seed, moves.size(), (int)playerWin);
else if (playerWin == false && winner != 1)
error("WineRackGame::testGame(%u, %u, %d) ai didn't win", seed, moves.size(), (int)playerWin);
}
void WineRackGame::runTests() {
warning("WineRackGame::runTests() starting");
uint32 oldSeed = _random.getSeed();
for (int i = 0; i < 10; i++) {
testWinCondition(kWineBottlePlayer, 0, i);
testWinCondition(kWineBottleOpponent, i, 0);
}
// pairs of x,y for the player's moves
testGame(1, {9,0, 9,1, 9,2, 9,3, 9,4, 9,5, 9,6, 9,7, 9,8, 9,9}, false);
testGame(2, {5,5, 3,5, 7,4, 1,6, 9,3, 0,7, 2,6, 4,5, 6,5, 8,4}, true);
// in the original game, the AI had a 25% chance to move to 2,6 as its first move, even if your first move was to 2,6 already
testGame(147160395, {2,6, 3,6, 4,6, 5,6, 7,6, 8,6, 9,6, 2,7, 3,7, 4,7}, false);
_random.setSeed(oldSeed);
warning("WineRackGame::runTests() finished");
}
namespace {
const int8 wineRackLogicTable[1200] = {
-1, -1, 1, 10, -1, -1, -1, -1, 11, -1, -1, -1,
-1, -1, 2, 11, 10, 0, -1, -1, 12, 20, -1, -1,
-1, -1, 3, 12, 11, 1, -1, -1, 13, 21, 10, -1,
-1, -1, 4, 13, 12, 2, -1, -1, 14, 22, 11, -1,
-1, -1, 5, 14, 13, 3, -1, -1, 15, 23, 12, -1,
-1, -1, 6, 15, 14, 4, -1, -1, 16, 24, 13, -1,
-1, -1, 7, 16, 15, 5, -1, -1, 17, 25, 14, -1,
-1, -1, 8, 17, 16, 6, -1, -1, 18, 26, 15, -1,
-1, -1, 9, 18, 17, 7, -1, -1, 19, 27, 16, -1,
-1, -1, -1, 19, 18, 8, -1, -1, -1, 28, 17, -1,
0, 1, 11, 20, -1, -1, -1, 2, 21, -1, -1, -1,
1, 2, 12, 21, 20, 10, -1, 3, 22, 30, -1, 0,
2, 3, 13, 22, 21, 11, -1, 4, 23, 31, 20, 1,
3, 4, 14, 23, 22, 12, -1, 5, 24, 32, 21, 2,
4, 5, 15, 24, 23, 13, -1, 6, 25, 33, 22, 3,
5, 6, 16, 25, 24, 14, -1, 7, 26, 34, 23, 4,
6, 7, 17, 26, 25, 15, -1, 8, 27, 35, 24, 5,
7, 8, 18, 27, 26, 16, -1, 9, 28, 36, 25, 6,
8, 9, 19, 28, 27, 17, -1, -1, 29, 37, 26, 7,
9, -1, -1, 29, 28, 18, -1, -1, -1, 38, 27, 8,
10, 11, 21, 30, -1, -1, 1, 12, 31, -1, -1, -1,
11, 12, 22, 31, 30, 20, 2, 13, 32, 40, -1, 10,
12, 13, 23, 32, 31, 21, 3, 14, 33, 41, 30, 11,
13, 14, 24, 33, 32, 22, 4, 15, 34, 42, 31, 12,
14, 15, 25, 34, 33, 23, 5, 16, 35, 43, 32, 13,
15, 16, 26, 35, 34, 24, 6, 17, 36, 44, 33, 14,
16, 17, 27, 36, 35, 25, 7, 18, 37, 45, 34, 15,
17, 18, 28, 37, 36, 26, 8, 19, 38, 46, 35, 16,
18, 19, 29, 38, 37, 27, 9, -1, 39, 47, 36, 17,
19, -1, -1, 39, 38, 28, -1, -1, -1, 48, 37, 18,
20, 21, 31, 40, -1, -1, 11, 22, 41, -1, -1, -1,
21, 22, 32, 41, 40, 30, 12, 23, 42, 50, -1, 20,
22, 23, 33, 42, 41, 31, 13, 24, 43, 51, 40, 21,
23, 24, 34, 43, 42, 32, 14, 25, 44, 52, 41, 22,
24, 25, 35, 44, 43, 33, 15, 26, 45, 53, 42, 23,
25, 26, 36, 45, 44, 34, 16, 27, 46, 54, 43, 24,
26, 27, 37, 46, 45, 35, 17, 28, 47, 55, 44, 25,
27, 28, 38, 47, 46, 36, 18, 29, 48, 56, 45, 26,
28, 29, 39, 48, 47, 37, 19, -1, 49, 57, 46, 27,
29, -1, -1, 49, 48, 38, -1, -1, -1, 58, 47, 28,
30, 31, 41, 50, -1, -1, 21, 32, 51, -1, -1, -1,
31, 32, 42, 51, 50, 40, 22, 33, 52, 60, -1, 30,
32, 33, 43, 52, 51, 41, 23, 34, 53, 61, 50, 31,
33, 34, 44, 53, 52, 42, 24, 35, 54, 62, 51, 32,
34, 35, 45, 54, 53, 43, 25, 36, 55, 63, 52, 33,
35, 36, 46, 55, 54, 44, 26, 37, 56, 64, 53, 34,
36, 37, 47, 56, 55, 45, 27, 38, 57, 65, 54, 35,
37, 38, 48, 57, 56, 46, 28, 39, 58, 66, 55, 36,
38, 39, 49, 58, 57, 47, 29, -1, 59, 67, 56, 37,
39, -1, -1, 59, 58, 48, -1, -1, -1, 68, 57, 38,
40, 41, 51, 60, -1, -1, 31, 42, 61, -1, -1, -1,
41, 42, 52, 61, 60, 50, 32, 43, 62, 70, -1, 40,
42, 43, 53, 62, 61, 51, 33, 44, 63, 71, 60, 41,
43, 44, 54, 63, 62, 52, 34, 45, 64, 72, 61, 42,
44, 45, 55, 64, 63, 53, 35, 46, 65, 73, 62, 43,
45, 46, 56, 65, 64, 54, 36, 47, 66, 74, 63, 44,
46, 47, 57, 66, 65, 55, 37, 48, 67, 75, 64, 45,
47, 48, 58, 67, 66, 56, 38, 49, 68, 76, 65, 46,
48, 49, 59, 68, 67, 57, 39, -1, 69, 77, 66, 47,
49, -1, -1, 69, 68, 58, -1, -1, -1, 78, 67, 48,
50, 51, 61, 70, -1, -1, 41, 52, 71, -1, -1, -1,
51, 52, 62, 71, 70, 60, 42, 53, 72, 80, -1, 50,
52, 53, 63, 72, 71, 61, 43, 54, 73, 81, 70, 51,
53, 54, 64, 73, 72, 62, 44, 55, 74, 82, 71, 52,
54, 55, 65, 74, 73, 63, 45, 56, 75, 83, 72, 53,
55, 56, 66, 75, 74, 64, 46, 57, 76, 84, 73, 54,
56, 57, 67, 76, 75, 65, 47, 58, 77, 85, 74, 55,
57, 58, 68, 77, 76, 66, 48, 59, 78, 86, 75, 56,
58, 59, 69, 78, 77, 67, 49, -1, 79, 87, 76, 57,
59, -1, -1, 79, 78, 68, -1, -1, -1, 88, 77, 58,
60, 61, 71, 80, -1, -1, 51, 62, 81, -1, -1, -1,
61, 62, 72, 81, 80, 70, 52, 63, 82, 90, -1, 60,
62, 63, 73, 82, 81, 71, 53, 64, 83, 91, 80, 61,
63, 64, 74, 83, 82, 72, 54, 65, 84, 92, 81, 62,
64, 65, 75, 84, 83, 73, 55, 66, 85, 93, 82, 63,
65, 66, 76, 85, 84, 74, 56, 67, 86, 94, 83, 64,
66, 67, 77, 86, 85, 75, 57, 68, 87, 95, 84, 65,
67, 68, 78, 87, 86, 76, 58, 69, 88, 96, 85, 66,
68, 69, 79, 88, 87, 77, 59, -1, 89, 97, 86, 67,
69, -1, -1, 89, 88, 78, -1, -1, -1, 98, 87, 68,
70, 71, 81, 90, -1, -1, 61, 72, 91, -1, -1, -1,
71, 72, 82, 91, 90, 80, 62, 73, 92, -1, -1, 70,
72, 73, 83, 92, 91, 81, 63, 74, 93, -1, 90, 71,
73, 74, 84, 93, 92, 82, 64, 75, 94, -1, 91, 72,
74, 75, 85, 94, 93, 83, 65, 76, 95, -1, 92, 73,
75, 76, 86, 95, 94, 84, 66, 77, 96, -1, 93, 74,
76, 77, 87, 96, 95, 85, 67, 78, 97, -1, 94, 75,
77, 78, 88, 97, 96, 86, 68, 79, 98, -1, 95, 76,
78, 79, 89, 98, 97, 87, 69, -1, 99, -1, 96, 77,
79, -1, -1, 99, 98, 88, -1, -1, -1, -1, 97, 78,
80, 81, 91, -1, -1, -1, 71, 82, -1, -1, -1, -1,
81, 82, 92, -1, -1, 90, 72, 83, -1, -1, -1, 80,
82, 83, 93, -1, -1, 91, 73, 84, -1, -1, -1, 81,
83, 84, 94, -1, -1, 92, 74, 85, -1, -1, -1, 82,
84, 85, 95, -1, -1, 93, 75, 86, -1, -1, -1, 83,
85, 86, 96, -1, -1, 94, 76, 87, -1, -1, -1, 84,
86, 87, 97, -1, -1, 95, 77, 88, -1, -1, -1, 85,
87, 88, 98, -1, -1, 96, 78, 89, -1, -1, -1, 86,
88, 89, 99, -1, -1, 97, 79, -1, -1, -1, -1, 87,
89, -1, -1, -1, -1, 98, -1, -1, -1, -1, -1, 88
};
} // End of anonymous namespace
} // End of Groovie namespace

View File

@@ -0,0 +1,76 @@
/* 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.
*
*/
#ifndef GROOVIE_LOGIC_WINERACK_H
#define GROOVIE_LOGIC_WINERACK_H
#include "common/random.h"
#include "common/system.h"
namespace Groovie {
/*
* Wine rack puzzle in Clandestiny.
* The player needs to create a path of bottles from one side to the other
* before the ghost of Auld Sot does.
*/
class WineRackGame {
public:
WineRackGame();
void run(byte *scriptVariables);
private:
void initGrid(byte difficulty);
void placeBottle(byte pos, byte val);
int8 calculateNextMove(byte op);
int8 findEmptySpot();
void sub05(int8 player, int8 *moves);
int8 sub06(int8 *moves1, int8 *moves2);
uint32 didPlayerWin();
void sub10(int8 endPos, int8 pos, int unused, int player, int *val);
void sub11(int8 pos, int8 *candidates);
uint32 didAiWin();
void sub13(int8 cell, int8 player, int8 *moves1, int8 *moves2);
void sub15(int8 cell, int8 *candidates);
void sub16(int8 cell, int8 *candidates);
int8 countEmtpy(int8 *moves);
int8 randomMoveStart();
int8 randomMoveStart2();
void testWinCondition(byte player, int baseX, int baseY);
void testGame(uint32 seed, Common::Array<int> moves, bool playerWin);
void runTests();
int _totalBottles;
byte _wineRackGrid[100];
byte _wineRackGrid2[100];
Common::RandomSource _random;
};
} // End of Groovie namespace
#endif // GROOVIE_LOGIC_WINERACK_H