|
|
|
@ -1,8 +1,7 @@ |
|
|
|
|
import t from "tap"; |
|
|
|
|
import t, { type Test } from "tap"; |
|
|
|
|
|
|
|
|
|
import { ExpectedOutcome, FinalOutcome, Player } from "./datatypes.ts"; |
|
|
|
|
import { CurrentOutcome, ExpectedOutcome, FinalOutcome, GameRules, Player, SquareState } from "./datatypes.ts"; |
|
|
|
|
import { computeAllSolutions, getPreferredNextOutcome } from "./solver.ts"; |
|
|
|
|
import { rules } from "./tictactoe-rules.ts"; |
|
|
|
|
|
|
|
|
|
void t.test("getPreferredNextOutcome", async (t) => { |
|
|
|
|
const nextOutcomes: ExpectedOutcome[] = [ |
|
|
|
@ -84,237 +83,359 @@ void t.test("getPreferredNextOutcome", async (t) => { |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
void t.test("computeAllSolutions", async (t) => { |
|
|
|
|
// Simple rules: whoever occupies all the squares passed as a parameter, wins
|
|
|
|
|
const createRulesForSquares = (...squares: [number, number][]): GameRules => ({ |
|
|
|
|
getBoardOutcome: (board) => { |
|
|
|
|
let hasX = false; |
|
|
|
|
let hasO = false; |
|
|
|
|
let hasUnoccupied = false; |
|
|
|
|
|
|
|
|
|
for (const [row, column] of squares) { |
|
|
|
|
const state = board.get(row, column); |
|
|
|
|
switch (state) { |
|
|
|
|
case SquareState.X: |
|
|
|
|
hasX = true; |
|
|
|
|
break; |
|
|
|
|
case SquareState.O: |
|
|
|
|
hasO = true; |
|
|
|
|
break; |
|
|
|
|
case SquareState.Unoccupied: |
|
|
|
|
hasUnoccupied = true; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (hasX && hasO) { |
|
|
|
|
return CurrentOutcome.Draw; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (hasUnoccupied) { |
|
|
|
|
return CurrentOutcome.Undecided; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (hasX) { |
|
|
|
|
return CurrentOutcome.WinX; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (hasO) { |
|
|
|
|
return CurrentOutcome.WinO; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Special case: if there are no X, O or unoccupied squares,
|
|
|
|
|
// this means that the squares list is empty,
|
|
|
|
|
// and in this case we only want to decide the board as Draw when it's full
|
|
|
|
|
// and no moves can be made.
|
|
|
|
|
if (board.serialize().includes("_")) { |
|
|
|
|
return CurrentOutcome.Undecided; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return CurrentOutcome.Draw; |
|
|
|
|
}, |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
const checkSolutionsComplete = ( |
|
|
|
|
t: Test, |
|
|
|
|
rows: number, |
|
|
|
|
columns: number, |
|
|
|
|
squares: [number, number][], |
|
|
|
|
expectedSolutions: Record<string, ExpectedOutcome>, |
|
|
|
|
) => { |
|
|
|
|
t.matchOnlyStrict(Object.fromEntries(computeAllSolutions(rows, columns, rules).entries()), expectedSolutions); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const checkSolutionsIncomplete = ( |
|
|
|
|
rows: number, |
|
|
|
|
columns: number, |
|
|
|
|
expectedSolutionsCount: number, |
|
|
|
|
expectedSolutionsIncomplete: Record<string, ExpectedOutcome>, |
|
|
|
|
) => { |
|
|
|
|
const allSolutions = computeAllSolutions(rows, columns, rules); |
|
|
|
|
t.equal(allSolutions.size, expectedSolutionsCount); |
|
|
|
|
t.matchStrict(Object.fromEntries(allSolutions.entries()), expectedSolutionsIncomplete); |
|
|
|
|
const rules = createRulesForSquares(...squares); |
|
|
|
|
const solutions = computeAllSolutions(rows, columns, rules); |
|
|
|
|
t.matchOnlyStrict(Object.fromEntries(solutions.entries()), expectedSolutions); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
checkSolutionsComplete(0, 0, { |
|
|
|
|
"": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
void t.test("empty boards", async (t) => { |
|
|
|
|
void t.test("0x0 board", async (t) => { |
|
|
|
|
checkSolutionsComplete(t, 0, 0, [], { |
|
|
|
|
"": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
void t.test("0x1 board", async (t) => { |
|
|
|
|
checkSolutionsComplete(t, 0, 1, [], { |
|
|
|
|
"": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
void t.test("0x2 board", async (t) => { |
|
|
|
|
checkSolutionsComplete(t, 0, 2, [], { |
|
|
|
|
"": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
void t.test("1x0 board", async (t) => { |
|
|
|
|
checkSolutionsComplete(t, 1, 0, [], { |
|
|
|
|
"": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
void t.test("2x0 board", async (t) => { |
|
|
|
|
checkSolutionsComplete(t, 2, 0, [], { |
|
|
|
|
"|": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
checkSolutionsComplete(1, 0, { |
|
|
|
|
"": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
checkSolutionsComplete(1, 1, { |
|
|
|
|
_: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
X: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
void t.test("1x1 board", async (t) => { |
|
|
|
|
void t.test("no winning squares", async (t) => { |
|
|
|
|
checkSolutionsComplete(t, 1, 1, [], { |
|
|
|
|
_: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
X: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
void t.test("one winning square", async (t) => { |
|
|
|
|
checkSolutionsComplete(t, 1, 1, [[0, 0]], { |
|
|
|
|
_: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, |
|
|
|
|
X: { finalOutcome: FinalOutcome.WinX, movesLeft: 0 }, |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
// making sure that we don't have any bugs that would manifest when width > height
|
|
|
|
|
checkSolutionsComplete(1, 3, { |
|
|
|
|
___: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, |
|
|
|
|
|
|
|
|
|
X__: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
_X_: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
__X: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
|
|
|
|
|
XO_: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
X_O: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
OX_: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
_XO: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
O_X: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
_OX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
|
|
|
|
|
XOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
XXO: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
OXX: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
void t.test("1x3 board", async (t) => { |
|
|
|
|
void t.test("no winning squares", async (t) => { |
|
|
|
|
checkSolutionsComplete(t, 1, 3, [], { |
|
|
|
|
___: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, |
|
|
|
|
|
|
|
|
|
X__: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
_X_: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
__X: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
|
|
|
|
|
XO_: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
X_O: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
OX_: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
_XO: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
O_X: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
_OX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
|
|
|
|
|
XOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
XXO: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
OXX: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
void t.test("one winning square", async (t) => { |
|
|
|
|
checkSolutionsComplete(t, 1, 3, [[0, 1]], { |
|
|
|
|
___: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, |
|
|
|
|
|
|
|
|
|
X__: { finalOutcome: FinalOutcome.WinO, movesLeft: 1 }, |
|
|
|
|
_X_: { finalOutcome: FinalOutcome.WinX, movesLeft: 0 }, |
|
|
|
|
__X: { finalOutcome: FinalOutcome.WinO, movesLeft: 1 }, |
|
|
|
|
|
|
|
|
|
XO_: { finalOutcome: FinalOutcome.WinO, movesLeft: 0 }, |
|
|
|
|
X_O: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, |
|
|
|
|
O_X: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, |
|
|
|
|
_OX: { finalOutcome: FinalOutcome.WinO, movesLeft: 0 }, |
|
|
|
|
|
|
|
|
|
XXO: { finalOutcome: FinalOutcome.WinX, movesLeft: 0 }, |
|
|
|
|
OXX: { finalOutcome: FinalOutcome.WinX, movesLeft: 0 }, |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
void t.test("two winning squares", async (t) => { |
|
|
|
|
checkSolutionsComplete( |
|
|
|
|
t, |
|
|
|
|
1, |
|
|
|
|
3, |
|
|
|
|
[ |
|
|
|
|
[0, 0], |
|
|
|
|
[0, 1], |
|
|
|
|
], |
|
|
|
|
{ |
|
|
|
|
___: { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, |
|
|
|
|
|
|
|
|
|
X__: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
_X_: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
__X: { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
|
|
|
|
|
XO_: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
X_O: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, |
|
|
|
|
OX_: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
_XO: { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, |
|
|
|
|
O_X: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
_OX: { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
|
|
|
|
|
XOX: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
XXO: { finalOutcome: FinalOutcome.WinX, movesLeft: 0 }, |
|
|
|
|
OXX: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
}, |
|
|
|
|
); |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
// making sure that we don't have any bugs that would manifest when height > width
|
|
|
|
|
checkSolutionsComplete(3, 1, { |
|
|
|
|
"_|_|_": { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, |
|
|
|
|
|
|
|
|
|
"X|_|_": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
"_|X|_": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
"_|_|X": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
|
|
|
|
|
"X|O|_": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"X|_|O": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"O|X|_": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"_|X|O": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"O|_|X": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"_|O|X": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
|
|
|
|
|
"X|O|X": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
"X|X|O": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
"O|X|X": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
// smallest possible board where X can win
|
|
|
|
|
checkSolutionsComplete(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 }, |
|
|
|
|
|
|
|
|
|
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 }, |
|
|
|
|
|
|
|
|
|
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 }, |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
checkSolutionsComplete(2, 2, { |
|
|
|
|
"__|__": { finalOutcome: FinalOutcome.Draw, movesLeft: 4 }, |
|
|
|
|
|
|
|
|
|
"X_|__": { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, |
|
|
|
|
"XO|__": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
"X_|O_": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
"X_|_O": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
|
|
|
|
|
"_X|__": { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, |
|
|
|
|
"OX|__": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
"_X|O_": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
"_X|_O": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
|
|
|
|
|
"__|X_": { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, |
|
|
|
|
"O_|X_": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
"_O|X_": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
"__|XO": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
|
|
|
|
|
"__|_X": { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, |
|
|
|
|
"O_|_X": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
"_O|_X": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
"__|OX": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
|
|
|
|
|
"XO|X_": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"XO|_X": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"XX|O_": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"X_|OX": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"XX|_O": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"X_|XO": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"OX|X_": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"OX|_X": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"_X|OX": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"_X|XO": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"O_|XX": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"_O|XX": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
|
|
|
|
|
"XO|XO": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
"XO|OX": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
"XX|OO": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
"OX|XO": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
"OX|OX": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
"OO|XX": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
void t.test("3x1 board", async (t) => { |
|
|
|
|
void t.test("no winning squares", async (t) => { |
|
|
|
|
checkSolutionsComplete(t, 3, 1, [], { |
|
|
|
|
"_|_|_": { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, |
|
|
|
|
|
|
|
|
|
"X|_|_": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
"_|X|_": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
"_|_|X": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
|
|
|
|
|
"X|O|_": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"X|_|O": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"O|X|_": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"_|X|O": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"O|_|X": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"_|O|X": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
|
|
|
|
|
"X|O|X": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
"X|X|O": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
"O|X|X": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
void t.test("one winning square", async (t) => { |
|
|
|
|
checkSolutionsComplete(t, 3, 1, [[1, 0]], { |
|
|
|
|
"_|_|_": { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, |
|
|
|
|
|
|
|
|
|
"X|_|_": { finalOutcome: FinalOutcome.WinO, movesLeft: 1 }, |
|
|
|
|
"_|X|_": { finalOutcome: FinalOutcome.WinX, movesLeft: 0 }, |
|
|
|
|
"_|_|X": { finalOutcome: FinalOutcome.WinO, movesLeft: 1 }, |
|
|
|
|
|
|
|
|
|
"X|O|_": { finalOutcome: FinalOutcome.WinO, movesLeft: 0 }, |
|
|
|
|
"X|_|O": { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, |
|
|
|
|
"O|_|X": { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, |
|
|
|
|
"_|O|X": { finalOutcome: FinalOutcome.WinO, movesLeft: 0 }, |
|
|
|
|
|
|
|
|
|
"X|X|O": { finalOutcome: FinalOutcome.WinX, movesLeft: 0 }, |
|
|
|
|
"O|X|X": { finalOutcome: FinalOutcome.WinX, movesLeft: 0 }, |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
void t.test("two winning squares", async (t) => { |
|
|
|
|
checkSolutionsComplete( |
|
|
|
|
t, |
|
|
|
|
3, |
|
|
|
|
1, |
|
|
|
|
[ |
|
|
|
|
[0, 0], |
|
|
|
|
[1, 0], |
|
|
|
|
], |
|
|
|
|
{ |
|
|
|
|
"_|_|_": { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, |
|
|
|
|
|
|
|
|
|
"X|_|_": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"_|X|_": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"_|_|X": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
|
|
|
|
|
"X|O|_": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
"X|_|O": { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, |
|
|
|
|
"O|X|_": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
"_|X|O": { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, |
|
|
|
|
"O|_|X": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"_|O|X": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
|
|
|
|
|
"X|O|X": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
"X|X|O": { finalOutcome: FinalOutcome.WinX, movesLeft: 0 }, |
|
|
|
|
"O|X|X": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
}, |
|
|
|
|
); |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
// number 5478 taken from https://math.stackexchange.com/a/613505
|
|
|
|
|
checkSolutionsIncomplete(3, 3, 5478, { |
|
|
|
|
"___|___|___": { finalOutcome: FinalOutcome.Draw, movesLeft: 9 }, |
|
|
|
|
"X__|___|___": { finalOutcome: FinalOutcome.Draw, movesLeft: 8 }, |
|
|
|
|
"_X_|___|___": { finalOutcome: FinalOutcome.Draw, movesLeft: 8 }, |
|
|
|
|
"___|_X_|___": { finalOutcome: FinalOutcome.Draw, movesLeft: 8 }, |
|
|
|
|
"XO_|___|___": { finalOutcome: FinalOutcome.WinX, movesLeft: 5 }, |
|
|
|
|
"X__|___|_O_": { finalOutcome: FinalOutcome.WinX, movesLeft: 5 }, |
|
|
|
|
"X_O|___|___": { finalOutcome: FinalOutcome.WinX, movesLeft: 5 }, |
|
|
|
|
"X__|___|__O": { finalOutcome: FinalOutcome.WinX, movesLeft: 5 }, |
|
|
|
|
"OO_|___|_XX": { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, |
|
|
|
|
"OO_|__X|_XX": { finalOutcome: FinalOutcome.WinO, movesLeft: 1 }, |
|
|
|
|
void t.test("2x2 board", async (t) => { |
|
|
|
|
void t.test("no winning squares", async (t) => { |
|
|
|
|
checkSolutionsComplete(t, 2, 2, [], { |
|
|
|
|
"__|__": { finalOutcome: FinalOutcome.Draw, movesLeft: 4 }, |
|
|
|
|
|
|
|
|
|
"X_|__": { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, |
|
|
|
|
"XO|__": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
"X_|O_": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
"X_|_O": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
|
|
|
|
|
"_X|__": { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, |
|
|
|
|
"OX|__": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
"_X|O_": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
"_X|_O": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
|
|
|
|
|
"__|X_": { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, |
|
|
|
|
"O_|X_": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
"_O|X_": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
"__|XO": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
|
|
|
|
|
"__|_X": { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, |
|
|
|
|
"O_|_X": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
"_O|_X": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
"__|OX": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
|
|
|
|
|
"XO|X_": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"XO|_X": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"XX|O_": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"X_|OX": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"XX|_O": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"X_|XO": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"OX|X_": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"OX|_X": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"_X|OX": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"_X|XO": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"O_|XX": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"_O|XX": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
|
|
|
|
|
"XO|XO": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
"XO|OX": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
"XX|OO": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
"OX|XO": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
"OX|OX": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
"OO|XX": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
void t.test("two winning squares", async (t) => { |
|
|
|
|
checkSolutionsComplete( |
|
|
|
|
t, |
|
|
|
|
2, |
|
|
|
|
2, |
|
|
|
|
[ |
|
|
|
|
[0, 0], |
|
|
|
|
[0, 1], |
|
|
|
|
], |
|
|
|
|
{ |
|
|
|
|
"__|__": { finalOutcome: FinalOutcome.Draw, movesLeft: 4 }, |
|
|
|
|
|
|
|
|
|
"X_|__": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"XO|__": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
"X_|O_": { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, |
|
|
|
|
"X_|_O": { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, |
|
|
|
|
|
|
|
|
|
"_X|__": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"OX|__": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
"_X|O_": { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, |
|
|
|
|
"_X|_O": { finalOutcome: FinalOutcome.WinX, movesLeft: 1 }, |
|
|
|
|
|
|
|
|
|
"__|X_": { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, |
|
|
|
|
"O_|X_": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"_O|X_": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"__|XO": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
|
|
|
|
|
"__|_X": { finalOutcome: FinalOutcome.Draw, movesLeft: 3 }, |
|
|
|
|
"O_|_X": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"_O|_X": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"__|OX": { finalOutcome: FinalOutcome.Draw, movesLeft: 2 }, |
|
|
|
|
|
|
|
|
|
"XO|X_": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
"XO|_X": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
"XX|O_": { finalOutcome: FinalOutcome.WinX, movesLeft: 0 }, |
|
|
|
|
"X_|OX": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"XX|_O": { finalOutcome: FinalOutcome.WinX, movesLeft: 0 }, |
|
|
|
|
"X_|XO": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"OX|X_": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
"OX|_X": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
"_X|OX": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"_X|XO": { finalOutcome: FinalOutcome.Draw, movesLeft: 1 }, |
|
|
|
|
"O_|XX": { finalOutcome: FinalOutcome.WinO, movesLeft: 1 }, |
|
|
|
|
"_O|XX": { finalOutcome: FinalOutcome.WinO, movesLeft: 1 }, |
|
|
|
|
|
|
|
|
|
"XO|XO": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
"XO|OX": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
"OX|XO": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
"OX|OX": { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, |
|
|
|
|
"OO|XX": { finalOutcome: FinalOutcome.WinO, movesLeft: 0 }, |
|
|
|
|
}, |
|
|
|
|
); |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|