solver support for 3x4/4x3 boards

main
Inga 🏳‍🌈 2 days ago
parent 4d13d2b1a4
commit d6f2d6e407
  1. 4
      src/shared/gameplay/solver.ts
  2. 17
      src/shared/integration-tests/tictactoe-all.test.ts
  3. 207
      src/shared/integration-tests/tictactoe-three.test.ts

@ -60,7 +60,9 @@ export const getPreferredNextOutcome = (
}; };
export const computeAllSolutions = (rows: number, columns: number, rules: GameRules) => { 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"); throw new Error("Board is too large, solving requires too many computational resources");
} }

@ -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, {});
});
});

@ -4,113 +4,122 @@ import { FinalOutcome, Player } from "../datatypes/types.ts";
import { createOpponent } from "../gameplay/opponent.ts"; import { createOpponent } from "../gameplay/opponent.ts";
import { computeAllSolutions } from "../gameplay/solver.ts"; import { computeAllSolutions } from "../gameplay/solver.ts";
import { tictactoeThreeRules } from "../game-variants/tictactoe/tictactoe-three-rules.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; const rules = tictactoeThreeRules;
void t.test("computeAllSolutions", async (t) => { void t.test("computeAllSolutions", async (t) => {
// smallest possible board where X can win void t.test("1x5, smallest possible board where X can win", async (t) => {
checkSolutionsComplete(t, rules, 1, 5, { checkSolutionsComplete(t, rules, 1, 5, {
_____: { finalOutcome: FinalOutcome.Draw, movesLeft: 5 }, _____: { finalOutcome: FinalOutcome.Draw, movesLeft: 5 },
X____: { finalOutcome: FinalOutcome.Draw, movesLeft: 4 }, X____: { finalOutcome: FinalOutcome.Draw, movesLeft: 4 },
XO___: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, 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__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 }, _X___: { finalOutcome: FinalOutcome.Draw, movesLeft: 4 },
OX___: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, OX___: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 },
_XO__: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, _XO__: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 },
_X_O_: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, _X_O_: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 },
_X__O: { finalOutcome: FinalOutcome.WinX, movesLeft: 3 }, _X__O: { finalOutcome: FinalOutcome.WinX, movesLeft: 3 },
__X__: { finalOutcome: FinalOutcome.Draw, movesLeft: 4 }, __X__: { finalOutcome: FinalOutcome.Draw, movesLeft: 4 },
O_X__: { finalOutcome: FinalOutcome.WinX, movesLeft: 3 }, O_X__: { finalOutcome: FinalOutcome.WinX, movesLeft: 3 },
_OX__: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, _OX__: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 },
__XO_: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, __XO_: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 },
__X_O: { finalOutcome: FinalOutcome.WinX, movesLeft: 3 }, __X_O: { finalOutcome: FinalOutcome.WinX, movesLeft: 3 },
___X_: { finalOutcome: FinalOutcome.Draw, movesLeft: 4 }, ___X_: { finalOutcome: FinalOutcome.Draw, movesLeft: 4 },
O__X_: { finalOutcome: FinalOutcome.WinX, movesLeft: 3 }, O__X_: { finalOutcome: FinalOutcome.WinX, movesLeft: 3 },
_O_X_: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, _O_X_: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 },
__OX_: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, __OX_: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 },
___XO: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, ___XO: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 },
____X: { finalOutcome: FinalOutcome.Draw, movesLeft: 4 }, ____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 }, _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 }, ___OX: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 },
XOX__: { 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 },
XO__X: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, XO__X: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 },
XXO__: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, XXO__: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 },
X_OX_: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, X_OX_: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 },
X_O_X: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, X_O_X: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 },
XX_O_: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, XX_O_: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 },
X_XO_: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, X_XO_: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 },
X__OX: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, X__OX: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 },
XX__O: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, XX__O: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 },
X_X_O: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, X_X_O: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 },
X__XO: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, X__XO: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 },
OXX__: { 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 },
OX__X: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, OX__X: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 },
_XOX_: { 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 }, _XXO_: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 },
_X_OX: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, _X_OX: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 },
_XX_O: { finalOutcome: FinalOutcome.WinX, movesLeft: 2 }, _XX_O: { finalOutcome: FinalOutcome.WinX, movesLeft: 2 },
_X_XO: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, _X_XO: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 },
O_XX_: { finalOutcome: FinalOutcome.WinX, movesLeft: 2 }, O_XX_: { finalOutcome: FinalOutcome.WinX, movesLeft: 2 },
O_X_X: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, O_X_X: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 },
_OXX_: { 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 }, __XOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 },
__XXO: { 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 },
_O_XX: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, _O_XX: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 },
__OXX: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, __OXX: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 },
XOXO_: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, XOXO_: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 },
XOX_O: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, XOX_O: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 },
XOOX_: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, XOOX_: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 },
XO_XO: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, XO_XO: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 },
XOO_X: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, XOO_X: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 },
XO_OX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, XO_OX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 },
XXOO_: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, XXOO_: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 },
XXO_O: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, XXO_O: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 },
X_OXO: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, X_OXO: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 },
X_OOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, X_OOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 },
XX_OO: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, XX_OO: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 },
X_XOO: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, X_XOO: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 },
OXXO_: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, OXXO_: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 },
OXX_O: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, OXX_O: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 },
OXOX_: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, OXOX_: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 },
OX_XO: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, OX_XO: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 },
OXO_X: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, OXO_X: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 },
OX_OX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, OX_OX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 },
_XOXO: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, _XOXO: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 },
_XOOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, _XOOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 },
_XXOO: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, _XXOO: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 },
OOXX_: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, OOXX_: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 },
O_XXO: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, O_XXO: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 },
OOX_X: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, OOX_X: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 },
O_XOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, O_XOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 },
_OXXO: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, _OXXO: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 },
_OXOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, _OXOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 },
OO_XX: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, OO_XX: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 },
O_OXX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, O_OXX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 },
_OOXX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, _OOXX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 },
XOXOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, XOXOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 },
XOXXO: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, XOXXO: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 },
XOOXX: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, XOOXX: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 },
XXOOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, XXOOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 },
XXOXO: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, XXOXO: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 },
XXXOO: { finalOutcome: FinalOutcome.WinX, movesLeft: 0 }, XXXOO: { finalOutcome: FinalOutcome.WinX, movesLeft: 0 },
OXXOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, OXXOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 },
OXXXO: { finalOutcome: FinalOutcome.WinX, movesLeft: 0 }, OXXXO: { finalOutcome: FinalOutcome.WinX, movesLeft: 0 },
OXOXX: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, OXOXX: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 },
OOXXX: { finalOutcome: FinalOutcome.WinX, 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, {});
}); });
}); });

Loading…
Cancel
Save