implemented opponent

feature/modern-browsers
Inga 🏳‍🌈 1 month ago
parent 945d4cea64
commit d823eab61f
  1. 67
      src/lib/opponent.spec.ts
  2. 52
      src/lib/opponent.ts
  3. 3
      src/lib/solver.ts

@ -0,0 +1,67 @@
import t from "tap";
import { ExpectedOutcome, FinalOutcome, Player } from "./datatypes.ts";
import { createOpponent } from "./opponent.ts";
import { Board } from "./board.ts";
void t.test("createOpponent", async (t) => {
const outcomesByBoard: Record<string, ExpectedOutcome> = {
"X_|__|__": { finalOutcome: FinalOutcome.WinO, movesLeft: 10 },
"XO|__|__": { finalOutcome: FinalOutcome.Draw, movesLeft: 9 },
"X_|O_|__": { finalOutcome: FinalOutcome.WinO, movesLeft: 20 },
"X_|_O|__": { finalOutcome: FinalOutcome.WinX, movesLeft: 9 },
"X_|__|O_": { finalOutcome: FinalOutcome.WinO, movesLeft: 10 },
"X_|__|_O": { finalOutcome: FinalOutcome.WinO, movesLeft: 9 },
"XX|__|__": { finalOutcome: FinalOutcome.WinO, movesLeft: 9 },
"X_|X_|__": { finalOutcome: FinalOutcome.WinO, movesLeft: 20 },
"X_|_X|__": { finalOutcome: FinalOutcome.WinX, movesLeft: 9 },
"X_|__|X_": { finalOutcome: FinalOutcome.WinO, movesLeft: 10 },
"X_|__|_X": { finalOutcome: FinalOutcome.WinX, movesLeft: 9 },
"XO|XO|XO": { finalOutcome: FinalOutcome.WinX, movesLeft: 0 },
// Incorrect scenarios
// movesLeft is not zero, but there is no information about next boards
"XX|XX|X_": { finalOutcome: FinalOutcome.WinO, movesLeft: 10 },
// movesLeft is not zero, and information about next boards
// is inconsistent with information about current board
"OO|OO|__": { finalOutcome: FinalOutcome.WinO, movesLeft: 10 },
"OO|OO|O_": { finalOutcome: FinalOutcome.WinO, movesLeft: 0 },
"OO|OO|_O": { finalOutcome: FinalOutcome.WinX, movesLeft: 9 },
};
const opponent = createOpponent(new Map(Object.entries(outcomesByBoard)));
const checkNextMove = (
currentBoardSerialized: string,
currentPlayer: Player,
expectedNextBoardSerialized: string,
) => {
t.equal(
opponent
.getNextMove(
Board.fromSerialized(currentBoardSerialized),
currentPlayer,
)
.serialize(),
expectedNextBoardSerialized,
);
};
checkNextMove("X_|__|__", Player.O, "X_|__|_O");
checkNextMove("X_|__|__", Player.X, "XX|__|__");
t.throws(
() => opponent.getNextMove(Board.fromSerialized("XO|XO|XO"), Player.X),
{ message: "There are no possible moves left: XO|XO|XO" },
);
t.throws(
() => opponent.getNextMove(Board.fromSerialized("XX|XX|XX"), Player.X),
{ message: "Board is not solved: XX|XX|XX" },
);
t.throws(
() => opponent.getNextMove(Board.fromSerialized("XX|XX|X_"), Player.X),
{ message: "Next board is not solved: XX|XX|XX" },
);
t.throws(
() => opponent.getNextMove(Board.fromSerialized("OO|OO|__"), Player.O),
{ message: "Cannot find appropriate next move: OO|OO|__" },
);
});

@ -0,0 +1,52 @@
import {
BoardType,
ExpectedOutcome,
Player,
SquareState,
getOccupiedStateByPlayer,
} from "./datatypes.ts";
export const createOpponent = (
outcomesByBoard: Map<string, ExpectedOutcome>,
) => {
const getNextMove = (board: BoardType, currentPlayer: Player) => {
const currentExpectedOutcome = outcomesByBoard.get(board.serialize());
if (!currentExpectedOutcome) {
throw new Error(`Board is not solved: ${board.serialize()}`);
}
if (!currentExpectedOutcome.movesLeft) {
throw new Error(`There are no possible moves left: ${board.serialize()}`);
}
const occupiedState = getOccupiedStateByPlayer(currentPlayer);
for (let row = 0; board.hasRow(row); row++) {
for (let column = 0; board.hasSquare(row, column); column++) {
if (board.get(row, column) === SquareState.Unoccupied) {
const nextBoard = board.with(row, column, occupiedState);
const nextExpectedOutcome = outcomesByBoard.get(
nextBoard.serialize(),
);
if (!nextExpectedOutcome) {
throw new Error(
`Next board is not solved: ${nextBoard.serialize()}`,
);
}
if (
nextExpectedOutcome.finalOutcome ===
currentExpectedOutcome.finalOutcome &&
nextExpectedOutcome.movesLeft ===
currentExpectedOutcome.movesLeft - 1
) {
return nextBoard;
}
}
}
}
throw new Error(`Cannot find appropriate next move: ${board.serialize()}`);
};
return { getNextMove };
};

@ -1,5 +1,6 @@
import { Board } from "./board.ts";
import {
BoardType,
CurrentOutcome,
ExpectedOutcome,
FinalOutcome,
@ -77,7 +78,7 @@ export const computeAllSolutions = (
const expectedOutcomesByBoard = new Map<string, ExpectedOutcome>();
const getExpectedOutcomeForBoard = (
board: Board,
board: BoardType,
currentPlayer: Player,
): ExpectedOutcome => {
// assuming that currentPlayer is always the same for the same board

Loading…
Cancel
Save