Switch to chips-only play: real cards at the table, winners picked on screen

- Remove the deck, hole cards, board, hand evaluator and reveal logic
  (cards are dealt physically; the site only tracks bets, pots and stacks)
- Showdown now waits for a winner per pot (main/side), chosen by a player
  still in the hand or the host; multiple winners split the pot
- All-in with no further betting goes straight to showdown
- Remove the 30s action clock / auto sit-out (everyone is at the same table)
- UI: winner picker panel (main winner auto-fills side pots), compact table

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
agomizi1cmandClaude Opus 5.5 committed 2026-10-15 17:46:10 +09:00
1 parent 8de55f7ca5
commit b5d84e5f65
11 files changed
+408 -605

No files matched your search

+33 -45
View File
@@ -96,13 +96,13 @@ class Table {
return free;
}
leave(token, now = Date.now()) {
leave(token) {
const i = this.seatOf(token);
if (i < 0 || this.seats[i].left) return false;
if (this.hand && this.hand.players.has(i)) {
// 結果表示やポット計算のため、ハンドが終わるまで席は残す
this.seats[i].left = true;
this.hand.forceFold(i, now);
this.hand.forceFold(i);
} else {
this.seats[i] = null;
}
@@ -125,7 +125,7 @@ class Table {
return null;
}
startNewHand(now) {
startNewHand() {
this.removeLeftSeats();
const eligible = this.eligibleSeats();
this.buttonSeat =
@@ -139,26 +139,41 @@ class Table {
this.handNumber++;
const { sb, bb } = this.blinds;
this.nextHandAt = null;
this.hand = new Hand({ number: this.handNumber, seats: this.seats, order, button: this.buttonSeat, sb, bb, now });
this.hand = new Hand({ number: this.handNumber, seats: this.seats, order, button: this.buttonSeat, sb, bb });
}
// ---- プレイヤー操作(エラー時はメッセージを返す) ----
act(token, type, amount, now = Date.now()) {
act(token, type, amount) {
const i = this.seatOf(token);
if (i < 0) return '着席していません';
if (!this.handActive) return 'ハンドが進行していません';
if (this.paused) return '一時停止中です';
return this.hand.act(i, type, amount, now);
return this.hand.act(i, type, amount);
}
setSittingOut(token, value, now = Date.now()) {
// ショーダウンの勝者を選べるのは、ショーダウンに残った人とホスト
canAward(token) {
if (!this.handActive || !this.hand.awaitingWinners) return false;
const i = this.seatOf(token);
const p = i >= 0 ? this.hand.players.get(i) : null;
return this.isHost(token) || Boolean(p && !p.folded && !this.seats[i].left);
}
award(token, winners) {
if (!this.handActive || !this.hand.awaitingWinners) return 'ショーダウンではありません';
if (!this.canAward(token)) return 'ショーダウンに残った人かホストが選んでください';
if (this.paused) return '一時停止中です';
return this.hand.award(winners);
}
setSittingOut(token, value) {
const i = this.seatOf(token);
if (i < 0) return '着席していません';
const s = this.seats[i];
s.sittingOut = value;
// 手番中に離席したら、その場でチェック/フォールド
if (value && this.handActive && this.hand.toAct === i && !this.paused) this.hand.autoAct(now);
if (value && this.handActive && this.hand.toAct === i && !this.paused) this.hand.autoAct();
return null;
}
@@ -182,7 +197,7 @@ class Table {
if (this.eligibleSeats().length < 2) return '2人以上必要です';
this.started = true;
if (this.timerEnabled) this.levelEndsAt = now + this.intervalMs;
this.startNewHand(now);
this.startNewHand();
return null;
}
@@ -190,12 +205,8 @@ class Table {
if (!this.isHost(token)) return 'ホストのみ操作できます';
if (!this.started) return 'まだ開始していません';
if (this.paused) {
// 止めていた時間だけ各期限を後ろにずらす
const shift = now - this.pausedAt;
const h = this.hand;
if (h && h.deadline != null) h.deadline += shift;
if (h && h.runoutAt != null) h.runoutAt += shift;
if (this.nextHandAt != null) this.nextHandAt += shift;
// 止めていた時間だけ次のハンドの開始を後ろにずらす
if (this.nextHandAt != null) this.nextHandAt += now - this.pausedAt;
if (this.timerEnabled) this.levelEndsAt = now + this.levelRemainingMs;
this.paused = false;
this.pausedAt = null;
@@ -210,7 +221,7 @@ class Table {
return null;
}
// 時間で進む処理(ブラインドタイマー・手番の時間切れ・オールイン時のめくり・次のハンド)
// 時間で進む処理(ブラインドタイマー・次のハンドの開始)
// 状態が変わったら true
tick(now) {
let changed = false;
@@ -221,24 +232,11 @@ class Table {
}
if (this.paused) return changed;
const h = this.hand;
if (h && !h.finished) {
if (h.toAct != null && now >= h.deadline) {
this.seats[h.toAct].sittingOut = true; // 時間切れの人は離席扱い(戻るボタンで復帰)
h.timeout(now);
changed = true;
}
if (h.runoutAt != null && now >= h.runoutAt) {
h.runoutStep(now);
changed = true;
}
}
if (this.hand && this.hand.finished) {
if (this.nextHandAt == null) {
this.nextHandAt = now + (this.hand.showdown ? config.SHOWDOWN_MS : config.FOLD_WIN_MS);
} else if (now >= this.nextHandAt) {
if (this.canDeal()) this.startNewHand(now);
if (this.canDeal()) this.startNewHand();
else {
this.hand = null;
this.nextHandAt = null;
@@ -250,7 +248,7 @@ class Table {
if (!this.canDeal()) this.nextHandAt = null;
else if (this.nextHandAt == null) this.nextHandAt = now + config.NEXT_HAND_DELAY_MS;
else if (now >= this.nextHandAt) {
this.startNewHand(now);
this.startNewHand();
changed = true;
}
}
@@ -276,28 +274,17 @@ class Table {
};
}
// token = 閲覧者のトークン(他人のトークンや手札は送らない)
// token = 閲覧者のトークン(他人のトークンは送らない)
publicState(token, connectedTokens) {
const now = Date.now();
const { sb, bb } = this.blinds;
const next = Blinds.blindsAt(this.baseSmallBlind, this.baseBigBlind, this.level + 1);
const mySeat = this.seatOf(token);
const h = this.hand;
const hv = h ? h.view(mySeat, token) : null;
const hv = h ? h.view() : null;
const me = mySeat >= 0 ? this.seats[mySeat] : null;
const inLiveHand = Boolean(this.handActive && h.players.has(mySeat) && !h.players.get(mySeat).folded);
let turn = null;
if (this.handActive && h.toAct != null) {
const remainingMs = Math.max(0, h.deadline - (this.paused ? this.pausedAt : now));
turn = {
seat: h.toAct,
endsAt: this.paused ? null : h.deadline,
remainingMs,
totalMs: config.ACTION_TIME_MS,
};
}
return {
id: this.id,
name: this.name,
@@ -319,7 +306,7 @@ class Table {
paused: this.paused,
handNumber: this.handNumber,
hand: hv,
turn,
toAct: this.handActive ? h.toAct : null,
seats: this.seats.map((s, i) =>
s && !s.left
? {
@@ -342,6 +329,7 @@ class Table {
sittingOut: me.sittingOut,
canRebuy: me.stack === 0 && !inLiveHand,
options: this.handActive && !this.paused ? h.options(mySeat) : null,
canAward: this.canAward(token),
}
: null,
isHost: this.isHost(token),