/** 바퀴벌레 포커 [?] "지금 할 수 있는 것" (docs/07 §5). Pure: only reads the client view. */ import type { CockroachView, Kind } from '@bg/games/cockroach-poker'; import type { PublicUser } from '@bg/shared/lite'; // Kept local (like index.tsx) so the screen chunk does not pull in the rules module. const KIND_KO: Record = { cockroach: '바퀴벌레', bat: '박쥐', fly: '파리', toad: '두꺼비', rat: '쥐', scorpion: '전갈', spider: '거미', stinkbug: '노린재', }; const hasBatchim = (word: string) => { const c = word.charCodeAt(word.length - 1); return c >= 0xac00 && c <= 0xd7a3 && (c - 0xac00) % 28 !== 0; }; /** "이건 쥐야!" / "이건 전갈이야!" — the same words as the screen. */ const claimLine = (k: Kind) => `이건 ${KIND_KO[k]}${hasBatchim(KIND_KO[k]) ? '이야' : '야'}!`; const kindIs = (k: Kind) => `${KIND_KO[k]}${hasBatchim(KIND_KO[k]) ? '이에요' : '예요'}`; export function helpNow(v: CockroachView, me: string | null, players: Record): string { const mine = me && v.order.includes(me) ? me : null; const name = (id: string) => `${players[id]?.nickname ?? '다른 사람'}님`; const subj = (id: string) => (id === mine ? '내가' : `${name(id)}이`); const goal = `같은 벌레가 앞마당(내 앞에 펼친 카드)에 ${v.loseCount}장 모이면 져요.`; if (v.phase === 'finished') { const f = v.final; if (!f) return '게임이 끝났어요.'; const who = f.loser === mine ? '내가 졌어요.' : mine ? `${name(f.loser)}이 졌고, 나는 이겼어요!` : `${name(f.loser)}이 졌고, 나머지 모두 이겼어요.`; const why = f.reason === 'four' && f.losingKind ? `앞마당에 ${KIND_KO[f.losingKind]} ${v.loseCount}장이 모였어요.` : f.reason === 'eight' ? '앞마당에 벌레 8종이 모두 모였어요.' : f.reason === 'emptyHand' ? '카드를 건넬 차례인데 손에 카드가 없었어요.' : '한 사람이 게임을 나갔어요.'; return `게임이 끝났어요. ${why}\n${who}`; } const r = v.round; const danger = mine ? (Object.keys(v.fronts[mine]?.level ?? {}) as Kind[]).filter((k) => v.fronts[mine]!.level[k] === 'danger') : []; const warn = danger.length ? `조심! 내 앞마당의 ${danger.map((k) => `${KIND_KO[k]} ${v.fronts[mine!]!.counts[k]}장`).join(', ')}은 한 장만 더 오면 져요.` : null; const lines: string[] = []; if (r.holder === null) { // 건네기: the round starter picks a card, a receiver and what to say. if (r.starter === mine) { lines.push('내 차례예요. 카드 한 장을 뒤집어 다른 사람에게 건네며 무슨 벌레인지 말해요. 진짜를 말해도, 거짓말을 해도 돼요.'); lines.push('아래에서 ① 건넬 카드 ② 받을 사람 ③ 할 말(벌레 그림)을 차례로 고르고, 맨 아래 [건네기] 버튼을 눌러요.'); } else { lines.push(`${r.no}라운드예요. ${subj(r.starter)} 건넬 카드를 고르는 중이에요.${mine ? ' 나에게 올 수도 있으니 기다려 주세요.' : ''}`); lines.push(goal); } } else { const holder = r.holder; const passer = r.lastPasser ?? r.starter; const said = r.claim ? `"${claimLine(r.claim)}"` : '카드'; if (holder === mine && !r.peeked) { lines.push(`${name(passer)}이 ${said}라며 카드를 줬어요. 진짜 같으면 [⭕ 진짜!], 거짓말 같으면 [❌ 거짓말!]을 눌러요.`); lines.push(`맞히면 ${name(passer)}이, 틀리면 내가 이 카드를 앞마당에 놓아요.`); if (v.my?.canPeek) lines.push('자신 없으면 [👁 몰래 보고 넘기기]로 카드를 본 뒤, 아직 안 본 사람에게 넘길 수 있어요. 보고 나면 판정은 못 해요.'); else if (v.my?.peekBlocked) lines.push(v.my.peekBlocked); } else if (holder === mine) { const known = v.card && v.card.known ? `몰래 본 카드는 ${kindIs(v.card.kind)}. ` : ''; lines.push(`${known}이제 판정은 못 하고 꼭 다른 사람에게 넘겨야 해요.`); lines.push('아직 이 카드를 안 본 사람(눌리는 버튼)과 할 말을 고르고 [넘기기] 버튼을 눌러요. 진짜를 말해도, 거짓말을 해도 돼요.'); } else { if (r.peeked) lines.push(`${subj(holder)} 몰래 본 카드를 넘길 사람을 고르는 중이에요.`); else lines.push(`${subj(passer)} ${name(holder)}에게 ${said}라며 카드를 건넸어요. ${subj(holder)} 진짜인지 고민하는 중이에요.`); if (mine && r.seen.includes(mine)) { const known = v.card && v.card.known ? ` 실제 카드는 ${kindIs(v.card.kind)}.` : ''; lines.push(`나는 이 카드를 봤기 때문에 다시 받지 않아요.${known}`); } else if (mine && r.peeked) lines.push('아직 이 카드를 안 봤으니 나에게 넘어올 수도 있어요.'); else if (mine) lines.push('기다려 주세요.'); lines.push(goal); } } if (warn) lines.push(warn); return lines.join('\n'); }