Add admin-only CPU tables with per-hand reviews

A new table type, "review" (レビュー付きCPU戦), can only be created by
admins. Only the creator and CPUs sit there: friends cannot join, and the
table is hidden from non-admin lobbies. After every hand a review opens,
and the next hand waits until the creator presses "次のハンドへ".

- Preflop: shows which color of the registered hand range the hole cards
  are and whether the action followed it. With no entrant before you,
  open with the color for the number of players still to act; facing an
  entrant, estimate their range from their position, call one color
  above it and raise two above. Purple ranks just below white (SB only).
- Flop onward: equity vs ranges estimated from those colors (narrowed by
  later bets/calls) gives a rough EV per option. The score is the EV lost
  against the best option, relative to the pot. It is a guide, not GTO.
- Also: pot-odds call check, all-in equity and luck-adjusted P&L,
  VPIP/PFR/3bet/Cbet/WTSD/W$SD/AF, per-color and per-position compliance,
  habits to watch, worst-5 hands, a P&L chart and a step-by-step replay
  on the admin-only /review page.

Hand now logs every action for the review. The range data moves to
admin/range-data.js, shared by the range page and the server. Reviews are
saved under data/reviews/ (newest 50 sessions). Admin scripts and CSS are
no longer cached for a day, so updates show up immediately.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
agomizi1cmandClaude Opus 5.5 committed 2026-10-19 22:11:16 +09:00
1 parent 3116b1da9f
commit 3f2515a9fa
25 files changed
+2262 -60

No files matched your search

+145
View File
@@ -0,0 +1,145 @@
/* CPU戦のレビュー(管理者専用)。基本のデザインは /assets/style.css、タブは range.css、
ハンドレンジの色・点数の印(.rv-color / .rv-mark など)は style.css の「レビュー付きCPU戦」を使う */
.review-wrap { padding-bottom: 40px; }
.plus { color: var(--green); }
.minus { color: var(--red); }
/* 点数の色(◎ ○ △ ✕ と同じ段階) */
.score-great { color: var(--green); }
.score-good { color: #8fd18f; }
.score-warn { color: var(--orange); }
.score-bad { color: var(--red); }
.score-none { color: var(--muted); }
/* 対局の一覧 */
.rv-sessions { display: grid; gap: 8px; margin-top: 12px; }
.rv-session {
display: grid;
gap: 4px;
text-align: left;
font: inherit;
color: var(--text);
background: var(--panel);
border: 0;
border-radius: var(--radius);
padding: 14px 16px;
cursor: pointer;
}
.rv-session:hover, .rv-session:focus-visible { background: var(--panel-2); }
.rv-session-name { font-weight: 800; font-size: 16px; display: flex; align-items: center; gap: 8px; }
.rv-session-meta { color: var(--muted); font-size: 12px; }
/* 概要とタイル */
.rv-overview { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-top: 12px; }
.rv-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 8px; }
.rv-tile { background: var(--panel); border-radius: 12px; padding: 10px 12px; display: grid; gap: 2px; min-width: 0; }
.rv-tile span { font-size: 12px; color: var(--muted); font-weight: 700; }
.rv-tile b { font-size: 22px; font-variant-numeric: tabular-nums; }
.rv-tile small { font-size: 11px; color: var(--muted); line-height: 1.4; }
.rv-tabs .seg-btn { font-size: 13px; padding: 9px 2px; }
/* 表 */
.rv-table-wrap { overflow-x: auto; margin-top: 8px; border-radius: 12px; background: var(--panel); }
.rv-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.rv-table th, .rv-table td { padding: 8px 6px; text-align: left; white-space: nowrap; border-bottom: 1px solid var(--line); }
.rv-table th { color: var(--muted); font-size: 11px; font-weight: 700; }
.rv-table .num { text-align: right; font-variant-numeric: tabular-nums; font-weight: 700; }
.rv-table tbody tr[data-hand] { cursor: pointer; }
.rv-table tbody tr[data-hand]:hover, .rv-table tbody tr[data-hand]:focus-visible { background: var(--panel-2); outline: none; }
.rv-table tfoot td { font-weight: 800; border-bottom: 0; }
.rv-hand-cell b { margin-right: 2px; }
.rv-table .rv-color { font-size: 11px; padding: 1px 6px; }
.rv-leaks { margin: 0; padding-left: 20px; display: grid; gap: 4px; font-size: 14px; }
.rv-leaks b { color: var(--gold); }
/* 見直したいハンド */
.rv-worst { display: grid; gap: 8px; }
.rv-worst-item {
display: grid;
grid-template-columns: 28px 1fr;
gap: 2px 8px;
align-items: center;
text-align: left;
font: inherit;
color: var(--text);
background: var(--panel);
border: 0;
border-radius: 12px;
padding: 10px 12px;
cursor: pointer;
}
.rv-worst-item:hover, .rv-worst-item:focus-visible { background: var(--panel-2); }
.rv-rank { grid-row: span 2; font-size: 20px; font-weight: 900; color: var(--gold); text-align: center; }
.rv-worst-meta { font-size: 12px; color: var(--muted); }
.rv-worst-item .rv-color { width: 14px; height: 14px; padding: 0; vertical-align: -2px; }
/* 収支のグラフ(実際 = 実線、運を除く = 破線。色だけに頼らない) */
.rv-legend { display: flex; flex-wrap: wrap; gap: 6px 16px; font-size: 13px; margin-bottom: 6px; }
.sw { display: inline-block; width: 16px; height: 0; border-top: 2px solid; vertical-align: middle; margin-right: 6px; }
.sw.actual { border-color: #b8891c; }
.sw.ev { border-color: #4a90e2; border-top-style: dashed; }
.rv-chart { position: relative; background: var(--panel); border-radius: 12px; padding: 8px; }
.rv-chart svg { display: block; width: 100%; height: auto; touch-action: pan-y; }
.rv-chart .gl { stroke: var(--line); stroke-width: 1; }
.rv-chart .zero { stroke: var(--muted); stroke-width: 1; stroke-dasharray: 2 3; }
.rv-chart .tick { fill: var(--muted); font-size: 11px; font-variant-numeric: tabular-nums; }
.rv-chart .line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.rv-chart .line.actual { stroke: #b8891c; }
.rv-chart .line.ev { stroke: #4a90e2; stroke-dasharray: 6 4; }
.rv-chart .cross { stroke: var(--muted); stroke-width: 1; }
.rv-chart .dot { stroke: var(--panel); stroke-width: 2; }
.rv-chart .dot.actual { fill: #b8891c; }
.rv-chart .dot.ev { fill: #4a90e2; }
.rv-chart .hit { fill: transparent; cursor: crosshair; }
.rv-tip {
position: absolute;
top: 8px;
background: var(--bg-deep);
border: 1px solid var(--line);
border-radius: 8px;
padding: 6px 10px;
font-size: 12px;
pointer-events: none;
white-space: nowrap;
}
/* リプレイ */
.rv-note { margin-top: 10px; font-size: 13px; background: var(--panel); border-radius: 10px; padding: 8px 10px; }
.rv-board-box { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 14px; background: var(--felt-dark); border-radius: 12px; padding: 10px; }
.rv-board { display: flex; gap: 3px; }
.rv-pot { font-weight: 900; color: #fff; font-variant-numeric: tabular-nums; }
.rv-step { margin-top: 10px; background: var(--panel); border-radius: 12px; padding: 10px 12px; font-size: 14px; min-height: 64px; }
.rv-step-title { font-size: 12px; font-weight: 800; color: var(--gold); margin-bottom: 2px; }
.rv-controls { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; margin-top: 8px; }
.rv-controls .btn { padding: 10px 0; font-size: 16px; }
.rv-analysis { margin-top: 10px; background: var(--panel); border: 1px solid var(--gold); border-radius: 12px; padding: 6px 12px 10px; }
.rv-analysis .rv-item:first-child { border-top: 0; }
.rv-ev { margin-top: 6px; }
.rv-ev tr.actual td { background: rgba(246, 199, 68, 0.08); }
.rv-ev .best { color: var(--green); }
.rv-players { display: grid; gap: 6px; margin-top: 12px; }
.rv-player { display: grid; grid-template-columns: auto 1fr auto; gap: 10px; align-items: center; background: var(--panel); border-radius: 10px; padding: 6px 10px; border: 2px solid transparent; }
.rv-player.hero { border-color: rgba(246, 199, 68, 0.35); }
.rv-player.acting { border-color: var(--gold); }
.rv-player.folded { opacity: 0.55; }
.rv-player-info { min-width: 0; font-size: 14px; }
.rv-player-act { font-size: 12px; font-weight: 800; color: var(--muted); text-align: right; }
.rv-pos { font-size: 11px; font-weight: 800; color: var(--gold); margin-left: 4px; }
.rv-log { display: flex; flex-wrap: wrap; gap: 4px; }
.rv-log-street { width: 100%; font-size: 11px; color: var(--muted); font-weight: 800; margin-top: 6px; }
.rv-log-item {
font: inherit;
font-size: 12px;
color: var(--text);
background: var(--panel);
border: 1px solid var(--line);
border-radius: 8px;
padding: 4px 8px;
cursor: pointer;
}
.rv-log-item .hint { margin: 0 4px; font-size: 11px; }
.rv-log-item.mine { border-color: rgba(246, 199, 68, 0.6); }
.rv-log-item.current { background: var(--gold); color: #1a1a1a; }
.rv-log-item.current .hint { color: #333; }