Implement Texas Hold'em play: dealing, betting, side pots, showdown
- Hand engine (server/hand.js): crypto-shuffled deck kept server-side, blinds (heads-up button = SB), min-raise rules incl. incomplete all-in, uncalled bet refunds, side pots, split pots, all-in runout - Hand evaluator (server/cards.js) and node:test suite (npm test) - Table flow: hands auto-continue after host START, 30s action clock (timeout = check/fold + sit out), sit out / sit in, rebuy, mid-hand leave folds and keeps the seat reserved until the hand ends, pause freezes all clocks - Only each player's own hole cards are sent until a showdown/runout reveal; all actions validated server-side - Play UI: board, hole cards, bets, action tags, turn timer, raise panel with presets, showdown results with winning cards highlighted, compact 7+ layout - Remove the host crown and the temporary "next phase" control Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b2c91062f1
commit
8de55f7ca5
12 files changed
+1648
-167
No files matched your search
+15
-6
@@ -216,18 +216,27 @@ io.on('connection', (socket) => {
|
||||
changed(table);
|
||||
});
|
||||
|
||||
// ホスト操作・退席は同じ形で処理する
|
||||
// プレイヤー操作・ホスト操作は同じ形で処理する(操作者は join 時のトークンで決まる)
|
||||
const ACTION_TYPES = new Set(['fold', 'check', 'call', 'raise']);
|
||||
const actions = {
|
||||
'table:leave': (t, token) => (t.leave(token) ? null : '着席していません'),
|
||||
'table:start': (t, token) => t.start(token),
|
||||
'table:nextPhase': (t, token) => t.nextPhase(token),
|
||||
'table:pause': (t, token) => t.togglePause(token),
|
||||
'table:action': (t, token, p) => {
|
||||
if (!p || typeof p !== 'object' || !ACTION_TYPES.has(p.type)) return '不正なアクションです';
|
||||
const amount = p.type === 'raise' ? p.amount : 0;
|
||||
if (!Number.isSafeInteger(amount)) return '額が不正です';
|
||||
return t.act(token, p.type, amount);
|
||||
},
|
||||
'table:sitOut': (t, token) => t.setSittingOut(token, true),
|
||||
'table:sitIn': (t, token) => t.setSittingOut(token, false),
|
||||
'table:rebuy': (t, token) => t.rebuy(token),
|
||||
};
|
||||
for (const [event, fn] of Object.entries(actions)) {
|
||||
socket.on(event, (_p, ack) => {
|
||||
socket.on(event, (p, ack) => {
|
||||
const table = socket.data.tableId && tables.get(socket.data.tableId);
|
||||
if (!table) return reply(ack, { ok: false, error: 'NOT_FOUND' });
|
||||
const error = fn(table, socket.data.token);
|
||||
const error = fn(table, socket.data.token, p);
|
||||
reply(ack, error ? { ok: false, error } : { ok: true });
|
||||
if (!error) changed(table);
|
||||
});
|
||||
@@ -239,13 +248,13 @@ io.on('connection', (socket) => {
|
||||
});
|
||||
});
|
||||
|
||||
// ブラインドタイマー
|
||||
// ブラインドタイマー・持ち時間・次のハンドなど時間で進む処理
|
||||
setInterval(() => {
|
||||
const now = Date.now();
|
||||
for (const table of tables.tables.values()) {
|
||||
if (table.tick(now)) changed(table);
|
||||
}
|
||||
}, 1000);
|
||||
}, 250);
|
||||
|
||||
// 誰も見ていないテーブルを片付ける
|
||||
setInterval(() => {
|
||||
|
||||
Reference in new issue
Block a user