/** 구십구 [?] "지금 할 수 있는 것" (docs/07 §5). Pure: only reads the client view. */ import type { NNOptions, NinetyNineView } from '@bg/games/ninety-nine'; import type { PublicUser } from '@bg/shared/lite'; /** Same as the engine's: `auto` deals 5 with the dedicated cards, 3 with trump cards. */ export const resolvedHandSize = (o: Pick): number => (o.handSize === 'auto' ? (o.mode === 'classic' ? 3 : 5) : o.handSize); /** Same as the engine's: the start card shows 40 for 2 players, 20 for 3, 0 for 4 or more. */ export const resolvedStartTotal = (o: Pick, players: number): number => o.mode === 'kr-product' && o.startTotal === 'auto' ? (players <= 2 ? 40 : players === 3 ? 20 : 0) : 0; const nm = (players: Record, id: string | null | undefined) => `${(id && players[id]?.nickname) || '다른 사람'}님`; /** What happens to someone who has no card to play (option dependent). */ function loseText(v: NinetyNineView): string { if (v.livesStart === 0) return '바로 탈락해요'; return `목숨 1개를 잃고 합계 ${resolvedStartTotal(v.options, v.seats.filter((s) => s.alive).length)}부터 새 판을 해요`; } export function helpNow(v: NinetyNineView, me: string | null, players: Record): string { const goal = '카드를 1장씩 내며 합계를 더해요. 합계가 99를 넘게 될 카드밖에 없으면 져요.'; if (v.phase === 'finished') return `게임이 끝났어요. ${v.result?.summary ?? ''}`.trim() + '\n결과 창에서 [한 판 더]를 누르면 다시 할 수 있어요.'; const seat = me ? v.seats.find((s) => s.id === me) : undefined; const my = seat ? v.my : null; const cur = v.current; const curNick = nm(players, cur); const turnLine = cur ? `${curNick} 차례예요(지금 합계 ${v.total}).` : `지금 합계는 ${v.total}까지 왔어요.`; if (!seat || !my) return `${turnLine}\n${goal}`; if (!seat.alive) return `${seat.left ? '게임에서 나갔어요.' : '탈락했어요. 남은 사람의 게임을 지켜봐요.'}\n${turnLine}`; const lifeLine = v.livesStart > 0 ? seat.onBoard ? '목숨을 다 잃어서 마지막 기회예요. 한 번 더 지면 탈락해요.' : `내 목숨은 ${seat.lives}개예요.` : ''; if (!my.myTurn) { const lines = [`${turnLine} 기다려 주세요.`]; lines.push(`내 차례에 낼 수 있는 카드가 없으면 ${loseText(v)}. 카드를 누르면 낼 때 합계를 미리 볼 수 있어요.`); if (lifeLine) lines.push(lifeLine); return lines.join('\n'); } const legal = my.hand.filter((c) => c.legal); const lines = [`내 차례예요(지금 합계 ${v.total}). 합계가 99를 넘지 않게 카드 1장을 내요.`]; if (legal.length < my.hand.length) lines.push(`초록 테두리로 밝게 보이는 카드만 낼 수 있어요(${legal.length}장). 어두운 카드는 99를 넘거나 0보다 작아져요.`); lines.push('카드를 누르면 낸 뒤 합계가 보여요. 한 번 더 누르면 내요. 패스는 없어요.'); if (legal.some((c) => c.kind === 'plusMinus')) lines.push('±카드는 누른 뒤 더하기(+)와 빼기(−) 중 하나를 골라요. 합계가 클 때는 빼기가 좋아요.'); if (legal.some((c) => c.kind === 'ace')) lines.push('A는 누른 뒤 더할 값을 골라요.'); if (legal.some((c) => c.kind === 'joker')) lines.push(`조커는 합계를 ${v.jokerRange.min}~${v.jokerRange.max} 중 원하는 수로 만들어요. 수를 고르고 [조커 내기]를 눌러요.`); const face = (k: string) => legal.find((c) => c.kind === k)?.face.label; if (face('zero')) lines.push(`${face('zero')} 카드는 합계를 그대로 둬요. 누른 뒤 [방향 그대로]와 [방향 바꾸기] 중 하나를 골라요.`); if (face('reverse')) lines.push(`${face('reverse')} 카드는 합계를 그대로 두고 진행 방향만 바꿔요.`); if (face('hold')) lines.push(`${face('hold')} 카드는 합계를 그대로 두고 다음 사람에게 넘겨요.`); if (face('to99')) lines.push(`${face('to99')} 카드는 합계를 바로 99로 만들어요.`); if (face('minus')) lines.push(`${face('minus')} 카드는 합계에서 10을 빼요.`); if (lifeLine) lines.push(lifeLine); return lines.join('\n'); }