Add player stats, in-table ranking, host seating and review return
- 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>
This commit is contained in:
1 parent
49203f202c
commit
dd39c043c4
18 files changed
+2699
-18
No files matched your search
@@ -0,0 +1,55 @@
|
||||
'use strict';
|
||||
|
||||
// 保存してある回数から、画面に出す割合を作る。テーブルの種類ごとと、すべてを合わせた合計(total)
|
||||
const { emptyBucket, MODES } = require('./store');
|
||||
|
||||
const pct = (a, b) => (b > 0 ? Math.round((a / b) * 1000) / 10 : null);
|
||||
const sum = (xs) => xs.reduce((a, b) => a + b, 0);
|
||||
|
||||
function merge(buckets) {
|
||||
const out = emptyBucket();
|
||||
for (const b of buckets) {
|
||||
for (const [k, v] of Object.entries(b)) {
|
||||
if (Array.isArray(v)) v.forEach((x, i) => (out[k][i] += x));
|
||||
else out[k] += v;
|
||||
}
|
||||
}
|
||||
out.netBB = Math.round(out.netBB * 100) / 100;
|
||||
return out;
|
||||
}
|
||||
|
||||
function view(b) {
|
||||
return {
|
||||
hands: b.hands,
|
||||
won: b.won,
|
||||
winRate: pct(b.won, b.hands),
|
||||
vpip: pct(b.vpip, b.preflop),
|
||||
pfr: pct(b.pfr, b.preflop),
|
||||
ats: pct(b.ats, b.atsOpp),
|
||||
atsOpp: b.atsOpp,
|
||||
threeBet: pct(b.threeBet, b.threeBetOpp),
|
||||
threeBetOpp: b.threeBetOpp,
|
||||
sawFlop: b.sawFlop,
|
||||
showdowns: b.showdown,
|
||||
wtsd: pct(b.showdown, b.sawFlop),
|
||||
wsd: pct(b.showdownWon, b.showdown),
|
||||
wwsf: pct(b.wonSawFlop, b.sawFlop),
|
||||
// ストリートごとの降りた割合(そのストリートで判断したハンドのうち)
|
||||
fold: b.folds.map((f, i) => pct(f, b.seen[i])),
|
||||
// アグレッション頻度: 全部の行動(フォールド・チェック・コール・ベット・レイズ)のうちベット・レイズ
|
||||
afq: pct(sum(b.aggressive), sum(b.actions)),
|
||||
afqStreets: b.aggressive.map((a, i) => pct(a, b.actions[i])),
|
||||
netBB: b.netBB,
|
||||
bb100: b.hands > 0 ? Math.round((b.netBB / b.hands) * 1000) / 10 : null,
|
||||
hours: Math.round((b.ms / 3600000) * 10) / 10,
|
||||
};
|
||||
}
|
||||
|
||||
function summarize(data) {
|
||||
const modes = {};
|
||||
for (const m of MODES) modes[m] = view(data.modes[m] || emptyBucket());
|
||||
modes.total = view(merge(MODES.map((m) => data.modes[m]).filter(Boolean)));
|
||||
return { since: data.firstAt, modes };
|
||||
}
|
||||
|
||||
module.exports = { summarize };
|
||||
Reference in new issue
Block a user