parent
945d4cea64
commit
d823eab61f
@ -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 }; |
||||
}; |
Loading…
Reference in new issue