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) => {
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");
}

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

Loading…
Cancel
Save