Files
joke-app/packages/shared/src/bots.ts
EJClaw a3f81ded37 M9: 컴퓨터 상대 — 게임 22종 컴퓨터 전략, 서버 구동(BotDriver), 연습 방
- packages/shared/src/bots.ts: 컴퓨터 id(bot:<n>:<ulid>)·프로필(컴퓨터 n)·BOT_GAMES
  (도블·할리갈리·그림 맞히기·마피아 제외)
- packages/games/src/bots: 전략 계약(자기 view + 합법 수만), decideBotAction(validate 재검사),
  게임별 <게임>/bot.ts 22개 + 행동 테스트, 전 게임 판 끝까지 돌리는 공통 테스트
- 서버: Room.addBot/removeBot(방장·대기실만, 방은 친구만), RoomManager.createPractice
  (POST /api/rooms {practice:true}), BotDriver(사람 같은 지연, 사람과 같은 창이면 더 천천히,
  재시작 후 다시 예약), 연습 게임은 전적 제외(result_json.practice), 사람이 모두 나가면 무효
- 프로토콜 addBot/removeBot, 문서 03·06 §12·08·09 §8

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 13:32:28 +09:00

54 lines
1.5 KiB
TypeScript

/** Computer players for solo practice (docs/06-lobby-rooms.md §12). Shared by server and web (no runtime deps). */
import type { PublicUser } from './types';
/** Bot user ids look like `bot:<n>:<ulid>`; real user ids are plain ULIDs, so they can never collide. */
export const BOT_PREFIX = 'bot:';
export const isBotId = (id: string): boolean => id.startsWith(BOT_PREFIX);
export const makeBotId = (n: number, unique: string): string => `${BOT_PREFIX}${n}:${unique}`;
/** Number shown in the bot's name ("컴퓨터 2"). */
export function botNumber(id: string): number | null {
if (!isBotId(id)) return null;
const n = Number(id.slice(BOT_PREFIX.length).split(':')[0]);
return Number.isInteger(n) && n > 0 ? n : null;
}
/** Public profile of a bot id, or null for anything else. */
export function botProfile(id: string): PublicUser | null {
const n = botNumber(id);
return n === null ? null : { id, nickname: `컴퓨터 ${n}`, avatar: null, kind: 'bot' };
}
/**
* Games that offer computer players. Real-time and talk-based games (도블, 할리갈리, 그림 맞히기, 마피아)
* don't: a bot there is either unbeatable or meaningless.
*/
export const BOT_GAMES: readonly string[] = [
'omok',
'othello',
'chess',
'janggi',
'baduk',
'seotda',
'poker',
'yacht',
'yut',
'one-card',
'rummikub',
'gostop',
'bang',
'davinci-code',
'mahjong',
'marble',
'dalmuti',
'splendor',
'quoridor',
'las-vegas',
'cockroach-poker',
'ninety-nine',
];
export const botsAllowed = (gameId: string): boolean => BOT_GAMES.includes(gameId);