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
+22
-10
@@ -40,14 +40,26 @@
|
||||
return `<span class="pcard suit-${c[1]} ${extra}"><b>${rank}</b><i>${SUIT_SYMBOL[c[1]]}</i></span>`;
|
||||
}
|
||||
|
||||
// ---------- 画面の切り替え(URL: ?s=対局ID&h=ハンド番号) ----------
|
||||
// ---------- 画面の切り替え(URL: ?s=対局ID&h=ハンド番号&t=テーブルID) ----------
|
||||
function show(view) {
|
||||
for (const v of ['list', 'session', 'replay']) $(`view-${v}`).hidden = v !== view;
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
// テーブルのレビューから開いたときのテーブルID。どの画面に移っても引き継ぎ、対局の画面からテーブルへ戻れるようにする
|
||||
function fromTable() {
|
||||
const t = new URLSearchParams(location.search).get('t');
|
||||
return t && /^[0-9a-f]{8}$/.test(t) ? t : null;
|
||||
}
|
||||
|
||||
function setBack(href, label) {
|
||||
$('back').href = href;
|
||||
$('back-label').textContent = label;
|
||||
}
|
||||
|
||||
function go(params, push = true) {
|
||||
const q = new URLSearchParams(params).toString();
|
||||
const table = fromTable();
|
||||
const q = new URLSearchParams(table ? { ...params, t: table } : params).toString();
|
||||
const url = location.pathname + (q ? `?${q}` : '');
|
||||
if (push) history.pushState(null, '', url);
|
||||
else history.replaceState(null, '', url);
|
||||
@@ -58,10 +70,11 @@
|
||||
const p = new URLSearchParams(location.search);
|
||||
const id = p.get('s');
|
||||
const hand = Number(p.get('h'));
|
||||
const table = fromTable();
|
||||
if (!id) {
|
||||
$('title').textContent = 'CPU戦のレビュー';
|
||||
$('back').href = '/';
|
||||
$('back-label').textContent = 'ロビー';
|
||||
if (table) setBack(`/table/${table}`, 'テーブル');
|
||||
else setBack('/', 'ロビー');
|
||||
show('list');
|
||||
return loadList();
|
||||
}
|
||||
@@ -77,22 +90,21 @@
|
||||
if (hand) {
|
||||
const h = session.hands.find((x) => x.number === hand);
|
||||
if (h) {
|
||||
$('back').href = `?s=${encodeURIComponent(id)}`;
|
||||
$('back-label').textContent = '対局';
|
||||
setBack(`?s=${encodeURIComponent(id)}`, '対局');
|
||||
return openReplay(h);
|
||||
}
|
||||
}
|
||||
$('back').href = '/review';
|
||||
$('back-label').textContent = '一覧';
|
||||
if (table) setBack(`/table/${table}`, 'テーブル');
|
||||
else setBack('/review', '一覧');
|
||||
show('session');
|
||||
renderSession();
|
||||
}
|
||||
|
||||
window.addEventListener('popstate', route);
|
||||
// 戻るボタンは履歴を積まずに一つ上へ
|
||||
// 戻るボタンは履歴を積まずに一つ上へ(ロビー・テーブルへはそのままページを移る)
|
||||
$('back').addEventListener('click', (e) => {
|
||||
const href = $('back').getAttribute('href');
|
||||
if (href === '/') return;
|
||||
if (href === '/' || href.startsWith('/table/')) return;
|
||||
e.preventDefault();
|
||||
const p = new URLSearchParams(location.search);
|
||||
go(p.get('h') ? { s: p.get('s') } : {});
|
||||
|
||||
Reference in new issue
Block a user