- 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>
54 lines
1.5 KiB
TypeScript
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);
|