agomizi1cmandClaude Opus 5.5 8de55f7ca5 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>
2026-10-15 17:34:25 +09:00

Hold'em Night

友達専用のテキサスホールデム(リングゲーム)サイト。Node.js + Express + Socket.IO。

「鍵入力 → ロビー → テーブル作成 → プレイ」まで動く。

ルール・仕様

  • ノーリミット・テキサスホールデムのリングゲーム(最大9人)。ホストが START を押すと、以降は2人以上いる限り自動でハンドが続く。
  • ブラインドはハンド開始時のレベルで固定。ヘッズアップはボタンが SB。
  • 最低レイズ額は直前のレイズ幅以上。足りないオールインのレイズは、すでにアクションした人に再レイズ権を与えない。
  • サイドポット・山分け(端数はボタンの左から)・誰もコールしなかった分の返金に対応。
  • 全員オールインになったら手札を公開して残りのカードを順にめくる。ショーダウンでは残った全員の手札を公開。
  • 持ち時間は1手30秒(server/config.js の ACTION_TIME_MS)。過ぎるとチェック(できなければフォールド)して「離席中」になり、「復帰する」を押すまで配られない。
  • チップがなくなったら開始スタックでリバイできる。画面下に収支(現在のチップ − バイイン合計)を表示。
  • ハンド中に退席するとフォールド扱い(出したチップはポットに残る)。
  • ホストの一時停止中は、持ち時間・ブラインドタイマー・次のハンドの開始がすべて止まる。

ローカルで動かす

cp .env.example .env   # ACCESS_KEY を設定
npm install
npm start

http://localhost:4671 を開く。

設定(.env)

秘密情報はソースコードに書かず、プロジェクト直下の .env(git 管理外)か環境変数で渡す。ACCESS_KEY が無いとサーバは起動しない。

変数 既定値 内容
ACCESS_KEY なし(必須) 6桁の参加キー
PORT 4671 待ち受けポート
HOST 127.0.0.1 待ち受けアドレス。既定では外部から直接つながらない(nginx 経由で公開する)
PUBLIC_ORIGIN なし 本番の公開URL https://poker.kanawo.net。設定するとこのドメイン以外(IP直打ちや別ドメイン)へのアクセスと、他オリジンからの操作を拒否する。ローカル開発では設定しない
SESSION_SECRET 自動生成 Cookie 署名用。未指定なら data/session-secret に生成・保存

セキュリティ

  • 参加キー: 正しいキーを入れたブラウザに HttpOnly / SameSite=Lax(HTTPS 時は Secure)の Cookie を保存。アクセスのたびに有効期限(400日)が延長されるので、以降は入力不要。
  • 総当たり対策: 同じIP(IPv6 は /64 単位)から30分に5回間違えるとロック。サーバ全体で1時間に30回間違えると新規の鍵入力を一時停止(入室済みの人は影響なし)。失敗はログに出る(journalctl -u holdem)。
  • 入室状態の取り消し: キーを変えるか、data/session-secret を削除して再起動すると、全員の Cookie が無効になる。
  • CSRF / Cross-Site WebSocket: 状態を変える API と WebSocket 接続は Origin が自サイトのときだけ受け付ける。Socket.IO は WebSocket のみ。
  • XSS: 厳格な CSP(インラインスクリプト・インラインスタイル禁止)、表示時のエスケープ、名前から制御文字・不可視文字を除去。
  • その他: クリックジャッキング防止(frame-ancestors 'none')、nosniff、no-referrer、HTML/API は no-store、検索エンジン除外、エラー時に内部情報を返さない、リクエストサイズ・イベント連打・テーブル数の上限。
  • 他人の席を操作できないよう、席の持ち主を示すプレイヤーIDはブラウザ内だけに保存し、他のプレイヤーには送らない。
  • カード: 山札は暗号論的乱数(crypto.randomInt)でシャッフルしてサーバ内だけに保持。各プレイヤーには自分の手札だけを送り、他人の手札は公開時(オールイン・ショーダウン)まで送らない。アクションはサーバ側で手番・額をすべて検証する。

6桁のキーはそれ自体が強い秘密ではないので、上記の回数制限が守りの要になっている。キーは友達にだけ直接伝えること。

構成

パス 内容
server/index.js HTTP ルーティング・セキュリティヘッダ・REST API・Socket.IO
server/auth.js 参加キー認証(Cookie)・回数制限・Origin チェック
server/tables.js テーブル状態(席・ボタン・ブラインド・タイマー・ハンドの開始/進行)
server/hand.js 1ハンドの進行(配札・ベッティング・サイドポット・ショーダウン)
server/cards.js 山札と役判定
test/ ゲーム処理のテスト(npm test)
server/config.js .env の読み込みと各種上限値
public/assets/blinds.js ブラインド計算(サーバ・ブラウザ共用)
public/*.html, public/assets/*.js 各画面
  • テーブルはメモリ上にのみ保持(サーバ再起動で消える)。誰も接続していない状態が30分続くと自動削除。
  • 名前とプレイヤーID はブラウザの localStorage に保存。リロードしても同じ席に戻れる。
  • 作成者がホスト(START・一時停止ができる)。ホストが退席すると次の着席者に移る。

VPS へのデプロイ例(Ubuntu + nginx + systemd + HTTPS)

公開URLは https://poker.kanawo.net 。HTTPS なしで公開するとキーと Cookie が平文で流れるので、必ず nginx + Let's Encrypt 経由で公開する。

事前に DNS で poker.kanawo.net の A レコード(IPv6 があれば AAAA も)を VPS の IP に向けておく。

# 専用ユーザーで動かす
sudo useradd --system --home /opt/holdem --shell /usr/sbin/nologin holdem
sudo git clone <このリポジトリ> /opt/holdem
cd /opt/holdem
sudo npm ci --omit=dev

# 秘密情報(本人のみ読める)
sudo cp .env.example .env
sudo nano .env                       # ACCESS_KEY=197107, PUBLIC_ORIGIN=https://poker.kanawo.net を設定
sudo mkdir -p data
sudo chown -R holdem:holdem .env data
sudo chmod 600 .env && sudo chmod 700 data

/etc/systemd/system/holdem.service:

[Unit]
Description=Hold'em Night
After=network.target

[Service]
User=holdem
WorkingDirectory=/opt/holdem
ExecStart=/usr/bin/node server/index.js
Environment=NODE_ENV=production
Restart=always

# サンドボックス
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
PrivateDevices=true
ReadWritePaths=/opt/holdem/data
CapabilityBoundingSet=
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX

[Install]
WantedBy=multi-user.target

nginx(/etc/nginx/sites-available/holdem、WebSocket を通す):

# poker.kanawo.net 以外のホスト名(IP直打ち等)は接続ごと切る
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    return 444;
}

server {
    server_name poker.kanawo.net;
    client_max_body_size 8k;

    location / {
        proxy_pass http://127.0.0.1:4671;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 1h;
    }
}
sudo ln -s /etc/nginx/sites-available/holdem /etc/nginx/sites-enabled/
sudo certbot --nginx -d poker.kanawo.net      # HTTPS 化(HTTP→HTTPS リダイレクトも設定される)
sudo systemctl daemon-reload && sudo systemctl enable --now holdem

# ファイアウォール: 4671 は開けない(nginx だけが 127.0.0.1 経由で使う)
sudo ufw allow OpenSSH && sudo ufw allow 'Nginx Full' && sudo ufw enable

更新(デプロイ済みのサーバに反映)

テーブルはメモリ上にしかないので、再起動すると進行中のテーブルは消える。誰も遊んでいないときに行う。

cd /opt/holdem
sudo git pull
sudo npm ci --omit=dev
sudo systemctl restart holdem
S
Description
No description provided
Readme
5.3 MiB
0 Stars 1 Watchers 0 Forks
Languages
JavaScript 74.8%
CSS 17.5%
HTML 7.7%