From d6f2d6e407f07c7aeb503d1586b20b5f925db2c5 Mon Sep 17 00:00:00 2001 From: Inga Date: Tue, 19 Nov 2024 20:47:20 +0000 Subject: [PATCH] solver support for 3x4/4x3 boards --- src/shared/gameplay/solver.ts | 4 +- .../integration-tests/tictactoe-all.test.ts | 17 ++ .../integration-tests/tictactoe-three.test.ts | 207 +++++++++--------- 3 files changed, 128 insertions(+), 100 deletions(-) create mode 100644 src/shared/integration-tests/tictactoe-all.test.ts diff --git a/src/shared/gameplay/solver.ts b/src/shared/gameplay/solver.ts index 85b0f16..4ca2c3e 100644 --- a/src/shared/gameplay/solver.ts +++ b/src/shared/gameplay/solver.ts @@ -60,7 +60,9 @@ export const getPreferredNextOutcome = ( }; export const computeAllSolutions = (rows: number, columns: number, rules: GameRules) => { - if (rows * columns > 9) { + if (rows * columns > 12) { + // Even for 3x5 board computing all solutions takes 20 seconds on my computer. + // There are probably many opportunities for optimization, but they're outside of the scope for now. throw new Error("Board is too large, solving requires too many computational resources"); } diff --git a/src/shared/integration-tests/tictactoe-all.test.ts b/src/shared/integration-tests/tictactoe-all.test.ts new file mode 100644 index 0000000..d0af4ad --- /dev/null +++ b/src/shared/integration-tests/tictactoe-all.test.ts @@ -0,0 +1,17 @@ +import t from "tap"; + +import { tictactoeThreeRules } from "../game-variants/tictactoe/tictactoe-three-rules.ts"; +import { checkSolutionsIncomplete } from "./test-helpers.ts"; + +const rules = tictactoeThreeRules; + +void t.test("computeAllSolutions", async (t) => { + // These numbers are not checked, but at least these tests will help us catch possible changes + void t.test("number of combinations for 3x4 board", async (t) => { + checkSolutionsIncomplete(t, rules, 3, 4, 111973, {}); + }); + + void t.test("number of combinations for 4x3 board", async (t) => { + checkSolutionsIncomplete(t, rules, 4, 3, 111973, {}); + }); +}); diff --git a/src/shared/integration-tests/tictactoe-three.test.ts b/src/shared/integration-tests/tictactoe-three.test.ts index 54a3d66..9428906 100644 --- a/src/shared/integration-tests/tictactoe-three.test.ts +++ b/src/shared/integration-tests/tictactoe-three.test.ts @@ -4,113 +4,122 @@ import { FinalOutcome, Player } from "../datatypes/types.ts"; import { createOpponent } from "../gameplay/opponent.ts"; import { computeAllSolutions } from "../gameplay/solver.ts"; import { tictactoeThreeRules } from "../game-variants/tictactoe/tictactoe-three-rules.ts"; -import { checkNextMove, checkSolutionsComplete } from "./test-helpers.ts"; +import { checkNextMove, checkSolutionsComplete, checkSolutionsIncomplete } from "./test-helpers.ts"; const rules = tictactoeThreeRules; void t.test("computeAllSolutions", async (t) => { - // smallest possible board where X can win - checkSolutionsComplete(t, rules, 1, 5, { - _____: { finalOutcome: FinalOutcome.Draw, movesLeft: 5 }, + void t.test("1x5, smallest possible board where X can win", async (t) => { + checkSolutionsComplete(t, rules, 1, 5, { + _____: { finalOutcome: FinalOutcome.Draw, movesLeft: 5 }, - X____: { finalOutcome: FinalOutcome.Draw, movesLeft: 4 }, - XO___: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, - X_O__: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, - X__O_: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, - X___O: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, - _X___: { finalOutcome: FinalOutcome.Draw, movesLeft: 4 }, - OX___: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, - _XO__: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, - _X_O_: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, - _X__O: { finalOutcome: FinalOutcome.WinX, movesLeft: 3 }, - __X__: { finalOutcome: FinalOutcome.Draw, movesLeft: 4 }, - O_X__: { finalOutcome: FinalOutcome.WinX, movesLeft: 3 }, - _OX__: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, - __XO_: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, - __X_O: { finalOutcome: FinalOutcome.WinX, movesLeft: 3 }, - ___X_: { finalOutcome: FinalOutcome.Draw, movesLeft: 4 }, - O__X_: { finalOutcome: FinalOutcome.WinX, movesLeft: 3 }, - _O_X_: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, - __OX_: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, - ___XO: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, - ____X: { finalOutcome: FinalOutcome.Draw, movesLeft: 4 }, - O___X: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, - _O__X: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, - __O_X: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, - ___OX: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, + X____: { finalOutcome: FinalOutcome.Draw, movesLeft: 4 }, + XO___: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, + X_O__: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, + X__O_: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, + X___O: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, + _X___: { finalOutcome: FinalOutcome.Draw, movesLeft: 4 }, + OX___: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, + _XO__: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, + _X_O_: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, + _X__O: { finalOutcome: FinalOutcome.WinX, movesLeft: 3 }, + __X__: { finalOutcome: FinalOutcome.Draw, movesLeft: 4 }, + O_X__: { finalOutcome: FinalOutcome.WinX, movesLeft: 3 }, + _OX__: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, + __XO_: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, + __X_O: { finalOutcome: FinalOutcome.WinX, movesLeft: 3 }, + ___X_: { finalOutcome: FinalOutcome.Draw, movesLeft: 4 }, + O__X_: { finalOutcome: FinalOutcome.WinX, movesLeft: 3 }, + _O_X_: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, + __OX_: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, + ___XO: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, + ____X: { finalOutcome: FinalOutcome.Draw, movesLeft: 4 }, + O___X: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, + _O__X: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, + __O_X: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, + ___OX: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, - XOX__: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, - XO_X_: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, - XO__X: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, - XXO__: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, - X_OX_: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, - X_O_X: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, - XX_O_: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, - X_XO_: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, - X__OX: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, - XX__O: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, - X_X_O: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, - X__XO: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, - OXX__: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, - OX_X_: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, - OX__X: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, - _XOX_: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, - _XO_X: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, - _XXO_: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, - _X_OX: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, - _XX_O: { finalOutcome: FinalOutcome.WinX, movesLeft: 2 }, - _X_XO: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, - O_XX_: { finalOutcome: FinalOutcome.WinX, movesLeft: 2 }, - O_X_X: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, - _OXX_: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, - _OX_X: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, - __XOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, - __XXO: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, - O__XX: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, - _O_XX: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, - __OXX: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, + XOX__: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, + XO_X_: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, + XO__X: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, + XXO__: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, + X_OX_: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, + X_O_X: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, + XX_O_: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, + X_XO_: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, + X__OX: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, + XX__O: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, + X_X_O: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, + X__XO: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, + OXX__: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, + OX_X_: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, + OX__X: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, + _XOX_: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, + _XO_X: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, + _XXO_: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, + _X_OX: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, + _XX_O: { finalOutcome: FinalOutcome.WinX, movesLeft: 2 }, + _X_XO: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, + O_XX_: { finalOutcome: FinalOutcome.WinX, movesLeft: 2 }, + O_X_X: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, + _OXX_: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, + _OX_X: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, + __XOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, + __XXO: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, + O__XX: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, + _O_XX: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, + __OXX: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, - XOXO_: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, - XOX_O: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, - XOOX_: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, - XO_XO: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, - XOO_X: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, - XO_OX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, - XXOO_: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, - XXO_O: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, - X_OXO: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, - X_OOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, - XX_OO: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, - X_XOO: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, - OXXO_: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, - OXX_O: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, - OXOX_: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, - OX_XO: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, - OXO_X: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, - OX_OX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, - _XOXO: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, - _XOOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, - _XXOO: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, - OOXX_: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, - O_XXO: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, - OOX_X: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, - O_XOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, - _OXXO: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, - _OXOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, - OO_XX: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, - O_OXX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, - _OOXX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, + XOXO_: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, + XOX_O: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, + XOOX_: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, + XO_XO: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, + XOO_X: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, + XO_OX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, + XXOO_: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, + XXO_O: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, + X_OXO: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, + X_OOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, + XX_OO: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, + X_XOO: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, + OXXO_: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, + OXX_O: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, + OXOX_: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, + OX_XO: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, + OXO_X: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, + OX_OX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, + _XOXO: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, + _XOOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, + _XXOO: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, + OOXX_: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, + O_XXO: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, + OOX_X: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, + O_XOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, + _OXXO: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, + _OXOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, + OO_XX: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, + O_OXX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, + _OOXX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, - XOXOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, - XOXXO: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, - XOOXX: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, - XXOOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, - XXOXO: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, - XXXOO: { finalOutcome: FinalOutcome.WinX, movesLeft: 0 }, - OXXOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, - OXXXO: { finalOutcome: FinalOutcome.WinX, movesLeft: 0 }, - OXOXX: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, - OOXXX: { finalOutcome: FinalOutcome.WinX, movesLeft: 0 }, + XOXOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, + XOXXO: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, + XOOXX: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, + XXOOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, + XXOXO: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, + XXXOO: { finalOutcome: FinalOutcome.WinX, movesLeft: 0 }, + OXXOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, + OXXXO: { finalOutcome: FinalOutcome.WinX, movesLeft: 0 }, + OXOXX: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, + OOXXX: { finalOutcome: FinalOutcome.WinX, movesLeft: 0 }, + }); + }); + + void t.test("number of combinations for 3x4 board", async (t) => { + checkSolutionsIncomplete(t, rules, 3, 4, 111973, {}); + }); + + void t.test("number of combinations for 4x3 board", async (t) => { + checkSolutionsIncomplete(t, rules, 4, 3, 111973, {}); }); });