Add CPU opponents and a table type selector (cash only / friends / CPU)

- Create screen: choose キャッシュオンリー (real cards, chips only), 友人戦
  (server deals and settles) or CPU戦 (友人戦 plus 1–8 CPU players); replaces
  the card toggle. The lobby shows each table's type
- Poker AI written from scratch (server/ai.js, no libraries): fast 7-card
  evaluator (checked against bestHand), Monte Carlo equity, and decisions from
  hand rank, pot odds, raise count, opponents and a per-CPU style (aggression,
  bluff rate); continuation bets via the last aggressor. It never sees other
  players' hole cards
- CPU seats: think time of 0.7–1.8s (faster when no human is left in the hand),
  automatic rebuy, never the host, no new hand unless a human is playing,
  re-think after pause
- Play UI: robot avatars and a "考えています…" status for CPU turns
- Tests for the evaluator, equity, legality of AI actions and CPU tables

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
agomizi1cmandClaude Opus 5.5 committed 2026-10-17 16:55:25 +09:00
1 parent 66907345ff
commit 0d3a054f0d
12 files changed
+550 -46

No files matched your search

+17 -9
View File
@@ -16,8 +16,8 @@
const timerInput = $('timer');
const intervalInput = $('interval');
const scheduleEl = $('schedule');
const cardsInput = $('cards');
const cardsHint = $('cards-hint');
const modeHint = $('mode-hint');
const cpuOptions = $('cpu-options');
const blindHint = $('blind-hint');
const errorEl = $('error');
const submitBtn = $('submit');
@@ -68,16 +68,23 @@
.join('');
}
function updateCardsHint() {
cardsHint.textContent = cardsInput.checked
? 'オン: 各自のスマホに手札が配られ、勝敗とポットの分配は自動'
: 'オフ: 実物のカードで遊び、ショーダウンの勝者はホストが選ぶ';
const MODE_HINTS = {
chips: '実物のカードで遊び、サイトはチップだけを管理。ショーダウンの勝者はホストが選ぶ',
friends: '各自のスマホに手札が配られ、勝敗とポットの分配は自動',
cpu: 'CPU と対戦(友達も空いている席に参加できる)。カードの配布・勝敗の判定は自動',
};
const radioValue = (name) => (form.querySelector(`input[name="${name}"]:checked`) || {}).value;
function updateMode() {
const mode = radioValue('mode');
modeHint.textContent = MODE_HINTS[mode] || '';
cpuOptions.hidden = mode !== 'cpu';
}
function refresh() {
updateHint();
updateSchedule();
updateCardsHint();
updateMode();
}
stackInput.addEventListener('input', () => {
@@ -100,7 +107,7 @@
});
timerInput.addEventListener('change', refresh);
intervalInput.addEventListener('input', refresh);
cardsInput.addEventListener('change', refresh);
form.querySelectorAll('input[type="radio"]').forEach((el) => el.addEventListener('change', refresh));
$('reroll').addEventListener('click', suggestName);
function validate() {
@@ -138,7 +145,8 @@
bigBlind: int(bbInput),
blindTimerEnabled: timerInput.checked,
blindIntervalMin: int(intervalInput),
cardsEnabled: cardsInput.checked,
mode: radioValue('mode'),
cpuCount: Number(radioValue('cpu-count')),
token: Store.token,
});
location.href = '/table/' + encodeURIComponent(id);