M2: бот и headless-прогон матчей
- PlayerView: проекция состояния, в которой боту видно только то, что видно живому игроку (своя рука, стол, размеры чужих рук, счётчики) - BasicBot: политика из GAMEPLAY.md, полностью детерминированная - Match: обёртка с состоянием и подпиской на события (её же возьмёт UI) - runMatch: прогон матча без движка и без таймеров - soak-тесты: 3500 полных матчей (боты и случайные легальные ходы, 2-4 игрока), инварианты состояния проверяются после каждого действия - npm run stats:bot — распределение исходов самоигры Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Not part of the suite — a measuring tool. Run with `npm run stats:bot`.
|
||||
*
|
||||
* Plays a large number of seeded matches between two BasicBots and prints how
|
||||
* they came out. Useful for sanity-checking a rules change (a sudden jump in
|
||||
* draws or in the first-attacker advantage means something moved) and as the
|
||||
* baseline any smarter bot has to beat.
|
||||
*/
|
||||
import { describe, it } from 'vitest';
|
||||
import { BasicBot } from '@core/bot/BasicBot';
|
||||
import { Match } from '@core/match/Match';
|
||||
import { runMatch } from '@core/match/runMatch';
|
||||
|
||||
const MATCHES = 5000;
|
||||
|
||||
describe('BasicBot vs BasicBot', () => {
|
||||
it(`plays ${MATCHES} matches`, () => {
|
||||
const tally: Record<string, number> = { you: 0, bot: 0, draw: 0 };
|
||||
let firstAttackerLost = 0;
|
||||
let actions = 0;
|
||||
let turns = 0;
|
||||
|
||||
for (let seed = 0; seed < MATCHES; seed++) {
|
||||
const match = new Match({ players: ['you', 'bot'], seed });
|
||||
const firstAttacker = match.state.players[match.state.attacker].id;
|
||||
const result = runMatch(match, { you: new BasicBot('you'), bot: new BasicBot('bot') });
|
||||
|
||||
tally[result.loser ?? 'draw'] += 1;
|
||||
if (result.loser === firstAttacker) firstAttackerLost += 1;
|
||||
actions += result.actions;
|
||||
turns += result.turns;
|
||||
}
|
||||
|
||||
const pct = (n: number) => `${((100 * n) / MATCHES).toFixed(1)}%`;
|
||||
console.log([
|
||||
`matches ${MATCHES}`,
|
||||
`seat 'you' lost ${tally.you} (${pct(tally.you)})`,
|
||||
`seat 'bot' lost ${tally.bot} (${pct(tally.bot)})`,
|
||||
`draws ${tally.draw} (${pct(tally.draw)})`,
|
||||
`first attacker lost ${pct(firstAttackerLost)}`,
|
||||
`average actions ${(actions / MATCHES).toFixed(1)}`,
|
||||
`average turns ${(turns / MATCHES).toFixed(1)}`,
|
||||
].join('\n'));
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user