Give CPUs distinct personalities built on different poker libraries
Each CPU now thinks with one of four engines, each backed by a different published library (pinned versions): - montecarlo (@pokertools/evaluator): the existing balanced Monte Carlo equity player, moved to server/bots/montecarlo.js. - calculator (poker-calculations): the library's decideAction over the table state; push/fold below 12BB via its jam breakeven equity. Skipped automatically if the native addon cannot load. - range (poker-utils): tight player; preflop by hand-strength percentile, postflop equity vs an estimated opponent range, bluffs only when alpha/bluffEV say they pay. - madehand (pokersolver): loose, sticky player that only looks at the made hand and draws. server/ai.js assigns a mix of engines per table (makeStyles), falls back to the Monte Carlo engine on errors, and legalizes every decision. The engine and its parameters stay server-side; players cannot tell which library a CPU uses. Tests cover every engine and the no-leak rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d92953a270
commit
3116b1da9f
11 files changed
+702
-131
No files matched your search
@@ -708,6 +708,76 @@ test('CPU: どんな状況でも正しいアクションを返す', () => {
|
||||
assert.ok(actions > 300, `actions=${actions}`);
|
||||
});
|
||||
|
||||
test('CPU: どの思考(ライブラリ)も自分で正しいアクションを決める', () => {
|
||||
const brains = ai.availableBrains();
|
||||
// 少なくとも純粋な JavaScript のライブラリの思考は必ず使える
|
||||
for (const b of ['montecarlo', 'range', 'madehand']) assert.ok(brains.includes(b), b);
|
||||
for (const brain of brains) {
|
||||
const rng = seeded(brain.length * 101);
|
||||
let decisions = 0;
|
||||
for (let game = 0; game < 6; game++) {
|
||||
const n = 2 + (game % 5);
|
||||
const stacks = Array.from({ length: n }, () => 100 + Math.floor(rng() * 1500));
|
||||
const t = makeTable(stacks, { mode: 'friends' });
|
||||
t.start(T(0), 0);
|
||||
let now = 0;
|
||||
for (let step = 0; step < 3000 && t.hand && t.hand.number <= 8; step++) {
|
||||
const h = t.hand;
|
||||
if (!h.finished && h.toAct != null) {
|
||||
const style = ai.randomStyle(rng, brain);
|
||||
assert.equal(style.brain, brain);
|
||||
// 思考そのものがエラーにならず、何かを返す(バランス型に頼らない)
|
||||
assert.ok(ai.BRAINS[brain].decide(h, h.toAct, { style, rng, iterations: 80 }), brain);
|
||||
const d = ai.decide(h, h.toAct, { style, rng, iterations: 80 });
|
||||
assert.equal(h.act(h.toAct, d.type, d.amount), null, `${brain} ${JSON.stringify(d)}`);
|
||||
decisions++;
|
||||
} else {
|
||||
t.tick((now += 10 ** 5));
|
||||
}
|
||||
}
|
||||
}
|
||||
assert.ok(decisions > 60, `${brain} decisions=${decisions}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('CPU: 思考の性格は弱い手で降り、強い手では降りない', () => {
|
||||
for (const brain of ai.availableBrains()) {
|
||||
const rng = seeded(3);
|
||||
const t = makeTable([1000, 1000, 1000], { mode: 'friends' });
|
||||
t.start(T(0), 0);
|
||||
const h = t.hand;
|
||||
const [sb, bb, btn] = h.order;
|
||||
rig(h, [['7c', '2d'], ['8h', '3s'], ['As', 'Ah']], ['Kd', '9c', '4s', '5h', 'Jd']);
|
||||
const style = ai.randomStyle(rng, brain);
|
||||
assert.notEqual(ai.decide(h, btn, { style, rng }).type, 'fold', brain); // AA
|
||||
t.act(tk(t, btn), 'raise', 1000); // オールイン
|
||||
assert.equal(ai.decide(h, sb, { style, rng }).type, 'fold', brain); // 72o
|
||||
}
|
||||
});
|
||||
|
||||
test('CPU戦: CPU ごとに違う思考が割り当てられ、どれを使っているかは画面に出ない', () => {
|
||||
const brains = ai.availableBrains();
|
||||
assert.deepEqual(new Set(ai.makeStyles(brains.length).map((s) => s.brain)), new Set(brains));
|
||||
const t = new Table({
|
||||
id: 'c',
|
||||
name: 'cpu',
|
||||
startingStack: 1000,
|
||||
smallBlind: 10,
|
||||
bigBlind: 20,
|
||||
blindTimerEnabled: false,
|
||||
blindIntervalMin: 10,
|
||||
hostToken: T(0),
|
||||
mode: 'cpu',
|
||||
cpuCount: 8,
|
||||
});
|
||||
const used = new Set(t.seats.filter((s) => s && s.bot).map((s) => s.style.brain));
|
||||
assert.equal(used.size, brains.length);
|
||||
t.join(T(0), 'me');
|
||||
t.start(T(0), 0);
|
||||
const json = JSON.stringify([t.publicState(T(0), new Set()), t.summary()]);
|
||||
for (const word of ['brain', 'style', ...brains, 'aggression', 'Threshold']) assert.ok(!json.includes(word), word);
|
||||
});
|
||||
|
||||
test('CPU戦: CPU が席に着き、ホストは人間のまま、CPU は自動で行動する', () => {
|
||||
const t = new Table({
|
||||
id: 'c',
|
||||
|
||||
Reference in new issue
Block a user