34 lines
1.2 KiB
JavaScript
34 lines
1.2 KiB
JavaScript
// Bingo AI — opponents auto-daub inside BingoLogic.drawBall, so the only AI
|
|
// "decision" is the claim race: how long an eligible opponent waits before
|
|
// shouting BINGO. That delay is the window in which the human can beat them.
|
|
|
|
import { hasCompletedLine } from './BingoLogic.js';
|
|
|
|
// Non-human seats that currently hold a completed line.
|
|
export function aiEligibleSeats(state) {
|
|
return state.players
|
|
.filter((p) => !p.isHuman && hasCompletedLine(p))
|
|
.map((p) => p.seat);
|
|
}
|
|
|
|
// Suspense delay (ms) before an eligible AI auto-calls Bingo. Jittered around
|
|
// ~1.5s; sharper (higher-skill) opponents react a touch faster.
|
|
export function chooseClaimDelayMs(seat, skill = 3) {
|
|
const base = 1700 - skill * 120;
|
|
return base + Math.random() * 500;
|
|
}
|
|
|
|
// When the human forfeits (advances without claiming) and ≥1 AI is eligible,
|
|
// the fastest reactor wins. Tie-break by lowest seat for determinism.
|
|
export function pickEarliestAIWinner(state) {
|
|
const seats = aiEligibleSeats(state);
|
|
if (seats.length === 0) return null;
|
|
let best = seats[0];
|
|
let bestDelay = chooseClaimDelayMs(best);
|
|
for (let i = 1; i < seats.length; i++) {
|
|
const d = chooseClaimDelayMs(seats[i]);
|
|
if (d < bestDelay) { best = seats[i]; bestDelay = d; }
|
|
}
|
|
return best;
|
|
}
|