- Stats page (/stats): play style radar (TAG/LAG/rock/fish), VPIP, PFR, ATS, 3bet, WTSD, W$SD, WWSF, per-street fold rate, aggression frequency, hand win rate and net in BB, per table type, each with a bands-and-explanation sheet. Hands are counted per human player when they finish and stored as totals under data/stats/, keyed by a hash of the player ID (sent in a POST body, never in the URL). - Ranking sheet at the table: everyone seated, ordered by stack or by net (stack minus buy-ins), updating live. - The host can reorder seats by dragging (with a mini table preview and shuffle). Takes effect immediately between hands, otherwise at the start of the next hand; the dealer button stays with the same player. - The full-session review opens in the same tab and its back button returns to the table it was opened from. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
91 lines
3.6 KiB
JavaScript
91 lines
3.6 KiB
JavaScript
'use strict';
|
|
|
|
// node --test で実行(ACCESS_KEY はテスト用のダミー、統計の保存先は一時ディレクトリ)
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
process.env.ACCESS_KEY = process.env.ACCESS_KEY || '000000';
|
|
process.env.STATS_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'holdem-stats-'));
|
|
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const { Table } = require('../server/tables');
|
|
|
|
const T = (n) => 'q'.repeat(31) + n; // 32文字のトークン
|
|
|
|
function makeTable(n) {
|
|
const t = new Table({
|
|
id: 'x',
|
|
name: 'test',
|
|
startingStack: 1000,
|
|
smallBlind: 10,
|
|
bigBlind: 20,
|
|
blindTimerEnabled: false,
|
|
blindIntervalMin: 10,
|
|
hostToken: T(0),
|
|
mode: 'friends',
|
|
});
|
|
for (let i = 0; i < n; i++) t.join(T(i), 'p' + i);
|
|
return t;
|
|
}
|
|
|
|
// 席の並び(名前)
|
|
const names = (t) => t.seats.filter(Boolean).map((s) => s.name);
|
|
|
|
test('席順: 開始前はすぐに並べ替え、ホスト以外や着席者と合わない指定は受け付けない', () => {
|
|
const t = makeTable(4);
|
|
assert.equal(t.setSeating(T(1), [3, 2, 1, 0]), 'ホストのみ操作できます');
|
|
assert.match(t.setSeating(T(0), [3, 2, 1]), /もう一度/);
|
|
assert.match(t.setSeating(T(0), [3, 3, 1, 0]), /もう一度/);
|
|
assert.match(t.setSeating(T(0), [3, 2, 1, 5]), /もう一度/);
|
|
assert.equal(t.setSeating(T(0), [0, 3, 1, 2]), null);
|
|
assert.deepEqual(names(t), ['p0', 'p3', 'p1', 'p2']);
|
|
assert.equal(t.pendingSeating, null);
|
|
// 席は詰めて座らせ、新しく座った人は空いている次の席(並びの最後)に入る
|
|
t.leave(T(1));
|
|
t.join(T(9), 'p9');
|
|
assert.deepEqual(names(t), ['p0', 'p3', 'p9', 'p2']);
|
|
});
|
|
|
|
test('席順: ハンド中は次のハンドの始めに並べ替え、ボタンは同じ人に付いたまま次へ回る', () => {
|
|
const t = makeTable(4);
|
|
t.start(T(0), 0);
|
|
const h = t.hand;
|
|
const buttonName = t.seats[h.button].name;
|
|
const order = [0, 2, 1, 3]; // p0 → p2 → p1 → p3
|
|
assert.equal(t.setSeating(T(0), order), null);
|
|
assert.deepEqual(names(t), ['p0', 'p1', 'p2', 'p3']); // まだ変わらない
|
|
assert.deepEqual(t.publicState(T(0), new Set()).pendingSeating, order);
|
|
assert.equal(t.publicState(T(1), new Set()).pendingSeating, null); // ホスト以外には送らない
|
|
|
|
// 全員降りてハンドを終わらせ、次のハンドへ
|
|
for (let i = 0; i < 10 && !h.finished; i++) t.act(t.seats[h.toAct].token, 'fold', 0);
|
|
assert.ok(h.finished);
|
|
t.tick(10 ** 6);
|
|
t.tick(10 ** 7);
|
|
assert.equal(t.hand.number, 2);
|
|
assert.deepEqual(names(t), ['p0', 'p2', 'p1', 'p3']);
|
|
assert.equal(t.pendingSeating, null);
|
|
// 前のハンドのボタンの人の、新しい並びでの次の人がボタン
|
|
const after = ['p0', 'p2', 'p1', 'p3'];
|
|
const next = after[(after.indexOf(buttonName) + 1) % 4];
|
|
assert.equal(t.seats[t.hand.button].name, next);
|
|
// 参加者の順もボタンの次から新しい並びどおり
|
|
const seq = t.hand.order.map((s) => t.seats[s].name);
|
|
const k = after.indexOf(next);
|
|
assert.deepEqual(seq, [1, 2, 3, 4].map((d) => after[(k + d) % 4]));
|
|
});
|
|
|
|
test('席順: 決めた後に座った人は最後に加え、いなくなった人は外す', () => {
|
|
const t = makeTable(3);
|
|
t.start(T(0), 0);
|
|
const h = t.hand;
|
|
assert.equal(t.setSeating(T(0), [2, 1, 0]), null);
|
|
t.join(T(5), 'p5'); // ハンド中に新しく座る
|
|
t.leave(T(1)); // ハンド中に退席
|
|
for (let i = 0; i < 10 && !h.finished; i++) t.act(t.seats[h.toAct].token, 'fold', 0);
|
|
t.tick(10 ** 6);
|
|
t.tick(10 ** 7);
|
|
assert.deepEqual(names(t), ['p2', 'p0', 'p5']);
|
|
});
|