import { defineBot, simulate } from "@openturn/bot";
import { ticTacToe } from "../game";
const OTHER: Record<string, string> = { "0": "1", "1": "0" };
function score(snapshot, me) {
const r = snapshot.meta.result;
if (r?.draw) return 0;
if (r?.winner === me) return 10;
if (r?.winner) return -10;
return null; // non-terminal
}
function search(snapshot, toMove, me, depth, alpha, beta) {
const terminal = score(snapshot, me);
if (terminal !== null) return terminal - Math.sign(terminal) * depth;
const moves = enumerate(snapshot, toMove);
if (moves.length === 0) return 0;
if (toMove === me) {
let best = -Infinity;
for (const m of moves) {
const sim = simulate(ticTacToe, snapshot, toMove, m);
if (!sim.ok) continue;
best = Math.max(best, search(sim.next, OTHER[toMove], me, depth + 1, alpha, beta));
alpha = Math.max(alpha, best);
if (alpha >= beta) break;
}
return best;
}
let best = Infinity;
for (const m of moves) {
const sim = simulate(ticTacToe, snapshot, toMove, m);
if (!sim.ok) continue;
best = Math.min(best, search(sim.next, OTHER[toMove], me, depth + 1, alpha, beta));
beta = Math.min(beta, best);
if (alpha >= beta) break;
}
return best;
}
export const minimaxBot = defineBot<typeof ticTacToe>({
name: "minimax",
decide({ legalActions, snapshot, playerID }) {
if (snapshot === null) return legalActions[0]!; // hosted host: no full snapshot
let bestAction = legalActions[0]!;
let bestScore = -Infinity;
for (const action of legalActions) {
const sim = simulate(ticTacToe, snapshot, playerID, action);
if (!sim.ok) continue;
const s = search(sim.next, OTHER[playerID]!, playerID, 1, -Infinity, Infinity);
if (s > bestScore) { bestScore = s; bestAction = action; }
}
return bestAction;
},
});