/** 다빈치 코드 [?] "지금 할 수 있는 것" (docs/07 §5). Pure: only reads the client view. */ import type { DavinciView, TileValue } from '@bg/games/davinci-code'; import type { PublicUser } from '@bg/shared/lite'; const COLOR = { black: '검정', white: '흰색' } as const; const nm = (players: Record, id: string | null | undefined) => `${(id && players[id]?.nickname) || '다른 사람'}님`; const valueKo = (n: TileValue | undefined) => (n === 'joker' ? '조커' : n === undefined ? '' : String(n)); const hidden = (v: DavinciView, p: string) => (v.rows[p] ?? []).filter((t) => !t.revealed).length; export function helpNow(v: DavinciView, me: string | null, players: Record): string { const goal = '상대 타일의 숫자를 맞혀 모두 드러내면 그 상대는 탈락해요. 마지막까지 숨은 타일이 남은 사람이 이겨요.'; if (v.phase === 'finished') return `게임이 끝났어요. ${v.final?.summary ?? ''}`.trim() + '\n결과 창에서 [한 판 더]를 누르면 다시 할 수 있어요.'; const seated = !!me && v.seats.includes(me); const alive = seated && !!v.alive[me!]; const cur = v.current; const curNick = nm(players, cur); if (v.phase === 'chooseStart') { const done = v.picked ? Object.values(v.picked).filter(Boolean).length : 0; const total = v.seats.filter((p) => v.alive[p]).length; if (!alive) return `모두 시작 타일 ${v.startCount}개의 색을 고르는 중이에요(${done}/${total}).\n${goal}`; if (v.myPick) return `검정 ${v.myPick.black}개, 흰색 ${v.myPick.white}개를 골랐어요. 다른 사람을 기다리는 중이에요(${done}/${total}).`; return `시작 타일 ${v.startCount}개를 검정과 흰색 몇 개씩 받을지 골라 [이렇게 받기]를 눌러요.\n숫자는 고른 색 안에서 무작위로 정해지고, 내 줄에 작은 수부터 자동으로 놓여요.`; } if (!alive) { const head = seated ? '탈락했어요. 끝까지 구경할 수 있어요.\n' : ''; return `${head}${curNick} 차례예요. ${goal}`; } if (cur !== me) { const doing: Record = { draw: '타일을 가져오는 중이에요', guess: '추리하는 중이에요', afterCorrect: '맞혀서 계속할지 고르는 중이에요', placeJoker: '조커를 넣을 자리를 고르는 중이에요', revealOwn: '틀려서 자기 타일을 공개하는 중이에요', }; return `${curNick}이 ${doing[v.phase] ?? '차례를 진행하는 중이에요'}. 기다려 주세요.\n내 숨은 타일은 ${hidden(v, me!)}개 남았어요. 공개된 숫자와 위치, 추리 기록을 보며 상대 숫자를 짐작해 보세요.`; } const pend = v.pending?.n !== undefined ? `가져온 타일(${COLOR[v.pending.color]} ${valueKo(v.pending.n)})은 나만 볼 수 있어요. ` : ''; switch (v.phase) { case 'draw': return v.options.chooseColors ? '내 차례예요. 가운데 검정·흰색 더미 중 원하는 색을 눌러 타일을 1개 가져와요.\n가져온 타일의 숫자는 나만 봐요.' : '내 차례예요. 가운데 [타일 가져오기]를 눌러 타일을 1개 가져와요.\n가져온 타일의 숫자는 나만 봐요.'; case 'guess': { const penalty = v.pending ? '가져온 타일이 모두에게 공개돼요' : v.options.emptyPoolPenalty === 'reveal-own' ? '내 숨은 타일 하나를 공개해야 해요' : '차례가 넘어가요'; const dim = v.options.deductionHelper ? '숫자판에서 흐린 숫자는 이미 보이는 숫자이거나 공개된 정보로 보면 어려운 숫자예요.' : '숫자판에서 흐린 숫자는 이미 다른 곳에 보이는 숫자예요.'; return `${pend}상대 줄에서 파란 테두리가 있는 숨은 타일을 누르고, 숫자를 골라 [이 숫자!] 버튼을 눌러요.\n타일은 작은 수부터 놓여 있으니 옆에 공개된 숫자가 힌트예요. ${dim}\n틀리면 ${penalty}.`; } case 'afterCorrect': return v.pending ? '맞혔어요! [한 번 더 맞히기]로 계속하거나 [여기서 멈추기]를 눌러요.\n멈추면 가져온 타일이 숨긴 채 내 줄에 들어가요. 계속하다 틀리면 그 타일이 공개돼요.' : `맞혔어요! [한 번 더 맞히기]로 계속하거나 [여기서 멈추기]를 눌러요.\n더미가 비어서 ${v.options.emptyPoolPenalty === 'reveal-own' ? '계속하다 틀리면 내 숨은 타일 하나를 공개해야 해요' : '계속하다 틀려도 벌칙 없이 차례만 넘어가요'}.`; case 'placeJoker': return '조커를 넣을 자리를 골라요. 내 줄의 [여기] 버튼 중 하나를 누르세요.\n조커는 어느 틈에나 넣을 수 있고, 한 번 놓으면 옮길 수 없어요.'; case 'revealOwn': return '틀렸어요. 더미가 비어서 내 숨은 타일 하나를 공개해야 해요.\n내 줄에서 파란 테두리 타일 중 하나를 누르세요.'; default: return `내 차례예요. ${goal}`; } }