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
@@ -10,6 +10,8 @@ const { TableManager, MODES } = require('./tables');
|
||||
const reviews = require('./review/store');
|
||||
const { summarize } = require('./review/summary');
|
||||
const { buildHandPrompt } = require('./review/prompt');
|
||||
const stats = require('./stats/store');
|
||||
const { summarize: summarizeStats } = require('./stats/summary');
|
||||
|
||||
const PUBLIC = path.join(__dirname, '..', 'public');
|
||||
// 管理者専用のファイル(public の外に置き、管理者の Cookie があるときだけ返す)
|
||||
@@ -89,6 +91,7 @@ app.use(auth.requireSameOrigin);
|
||||
app.get('/', page('lobby.html'));
|
||||
app.get('/create', page('create.html'));
|
||||
app.get('/table/:id', page('table.html'));
|
||||
app.get('/stats', page('stats.html'));
|
||||
|
||||
// ---------- 管理者専用: ハンドレンジ学習 ----------
|
||||
app.get('/range', auth.requireAdmin, page('range.html', ADMIN));
|
||||
@@ -136,6 +139,17 @@ app.get('/api/reviews/:id/hands/:number/prompt', auth.requireAdmin, async (req,
|
||||
});
|
||||
|
||||
app.get('/api/me', (req, res) => res.json({ admin: auth.isAdmin(req) }));
|
||||
|
||||
// 自分の統計。プレイヤーID(席のトークン)を URL やアクセスログに残さないよう POST の本文で受け取る
|
||||
app.post('/api/stats', async (req, res, next) => {
|
||||
const token = cleanToken(req.body && req.body.token);
|
||||
if (!token) return res.status(400).json({ error: 'プレイヤーIDがありません' });
|
||||
try {
|
||||
res.json(summarizeStats(await stats.get(token)));
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
app.get('/api/tables', (req, res) => res.json(tables.list(auth.isAdmin(req))));
|
||||
app.get('/api/tables/suggest-name', (req, res) => res.json({ name: tables.suggestName() }));
|
||||
|
||||
@@ -335,6 +349,12 @@ io.on('connection', (socket) => {
|
||||
'table:sitIn': (t, token) => t.setSittingOut(token, false),
|
||||
'table:rebuy': (t, token) => t.rebuy(token),
|
||||
'table:next': (t, token) => t.reviewNext(token),
|
||||
'table:seating': (t, token, p) => {
|
||||
// order: 時計回りに並べた席番号
|
||||
const order = p && typeof p === 'object' ? p.order : null;
|
||||
const ok = Array.isArray(order) && order.length <= config.MAX_SEATS && order.every((i) => Number.isInteger(i));
|
||||
return ok ? t.setSeating(token, order) : '席順の指定が不正です';
|
||||
},
|
||||
};
|
||||
for (const [event, fn] of Object.entries(actions)) {
|
||||
socket.on(event, (p, ack) => {
|
||||
|
||||
Reference in new issue
Block a user