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