53 lines
1.7 KiB
JavaScript
53 lines
1.7 KiB
JavaScript
// Genius Square AI — pre-solves the board at round start, then ticks through
|
|
// placements at a skill-scaled delay (mirrors the Nerts real-time pattern).
|
|
//
|
|
// The tick loop lives in GeniusSquareGame.js (Phaser's time.delayedCall).
|
|
// This module is pure JS with no Phaser dependency.
|
|
|
|
import { solveFromBlockers } from './GeniusSquareLogic.js';
|
|
|
|
const SKILL_PROFILES = {
|
|
1: { delay: [8000, 12000] }, // very slow — human wins easily
|
|
2: { delay: [4000, 7000] },
|
|
3: { delay: [2000, 4000] }, // balanced
|
|
4: { delay: [1000, 2000] }, // challenging
|
|
5: { delay: [400, 800] }, // expert
|
|
};
|
|
|
|
function profileFor(skill) {
|
|
return SKILL_PROFILES[Math.max(1, Math.min(5, skill | 0))] ?? SKILL_PROFILES[3];
|
|
}
|
|
|
|
/** Milliseconds until the AI's next piece placement, randomized within skill's band. */
|
|
export function nextThinkDelay(skill) {
|
|
const [lo, hi] = profileFor(skill).delay;
|
|
return lo + Math.random() * (hi - lo);
|
|
}
|
|
|
|
/**
|
|
* Create AI state. Pass in a pre-computed solution to avoid solving twice.
|
|
* solution: array of { pieceId, oriIdx, anchorR, anchorC, cells } | null
|
|
*/
|
|
export function createAIState(blockers, skill, solution) {
|
|
return {
|
|
skill,
|
|
solution: solution ?? solveFromBlockers(blockers),
|
|
stepIndex: 0,
|
|
done: false,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Return the next placement to visually execute, or null if done.
|
|
* Mutates aiState.stepIndex and aiState.done.
|
|
*/
|
|
export function getNextPlacement(aiState) {
|
|
if (aiState.done || !aiState.solution || aiState.stepIndex >= aiState.solution.length) {
|
|
return null;
|
|
}
|
|
const placement = aiState.solution[aiState.stepIndex];
|
|
aiState.stepIndex++;
|
|
if (aiState.stepIndex >= aiState.solution.length) aiState.done = true;
|
|
return placement;
|
|
}
|