mirror of
https://github.com/scummvm/scummvm.git
synced 2025-04-02 10:52:32 -04:00
250 lines
6.9 KiB
C++
250 lines
6.9 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*
|
|
* This file is dual-licensed.
|
|
* In addition to the GPLv3 license mentioned above, MojoTouch has exclusively licensed
|
|
* this code on November 10th, 2021, to be use in closed-source products.
|
|
* Therefore, any contributions (commits) to it will also be dual-licensed.
|
|
*
|
|
*/
|
|
|
|
#include "groovie/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
|