/* 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 . * * * 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