/** 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::`; 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);