/** 체스 [?] "지금 할 수 있는 것" (docs/07 §5). Pure: only reads the client view. */ import type { ChessView } from '@bg/games/chess'; import type { PublicUser } from '@bg/shared/lite'; const COLOR_KO = { white: '백', black: '흑' } as const; /** Material lead as shown next to the player bars ("+3"). */ function material(v: ChessView): string { if (v.materialDiff === 0) return ''; const lead = v.materialDiff > 0 ? 'white' : 'black'; return `기물 점수는 ${COLOR_KO[lead]}이 ${Math.abs(v.materialDiff)}점 앞서요(퀸 9, 룩 5, 비숍·나이트 3, 폰 1).`; } export function helpNow(v: ChessView, _me: string | null, players: Record): string { if (v.phase === 'finished') return `게임이 끝났어요. ${v.result?.summary ?? ''}`.trim() + '\n결과 창에서 [한 판 더]를 누르면 다시 할 수 있어요.'; const turnNick = players[v.players[v.turn]]?.nickname ?? COLOR_KO[v.turn]; const mat = material(v); if (!v.myColor) { const check = v.checkSquare ? ` ${COLOR_KO[v.turn]} 킹이 체크(공격)를 받고 있어요.` : ''; return [`${turnNick}님(${COLOR_KO[v.turn]}) 차례예요.${check} 상대 킹을 피할 곳 없이 잡히게 만들면(체크메이트) 이겨요.`, mat].filter(Boolean).join('\n'); } const mine = COLOR_KO[v.myColor]; if (v.undo.pending && v.undo.pending.by !== v.myColor) return `상대가 ${v.undo.pending.plies}수 무르기를 부탁했어요. [수락]을 누르면 기물이 되돌아가고(시계는 그대로), [거절]을 누르면 그대로 계속해요.`; if (v.draw.pendingBy && v.draw.pendingBy !== v.myColor) return '상대가 무승부를 제안했어요. [수락]하면 이 판은 비기고 끝나요. 계속하려면 [거절]을 눌러 주세요.'; if (v.myColor !== v.turn) { const lines = [`상대(${turnNick}님) 차례예요. 상대가 둘 때까지 기다려 주세요. 나는 ${mine}이에요.`]; if (v.checkSquare) lines.push('내가 상대 킹에 체크를 걸었어요. 상대는 킹을 지켜야 해요.'); if (v.undo.pending || v.draw.pendingBy) lines.push(`내 ${v.undo.pending ? '무르기 요청' : '무승부 제안'}에 상대가 답하기를 기다리는 중이에요. 그만두려면 [요청 취소]를 눌러요.`); if (mat) lines.push(mat); return lines.join('\n'); } const lines: string[] = []; if (v.checkSquare) lines.push('체크예요! 내 킹이 공격받고 있어요(빨갛게 빛나는 칸). 킹을 피하거나, 사이를 막거나, 공격한 기물을 잡는 수만 둘 수 있어요.'); else lines.push(`내 차례예요(${mine}).`); lines.push( v.hints === 'off' ? '움직일 기물을 누른 뒤 갈 칸을 누르거나, 기물을 끌어다 놓아요.' : '움직일 기물을 누르면 갈 수 있는 칸에 점이 찍혀요(잡을 수 있는 칸은 동그라미). 그 칸을 누르거나 끌어다 놓아요.', ); if (!v.started[v.myColor]) lines.push(`첫 수는 ${v.firstMoveSec}초 안에 둬야 해요. 안 두면 대국이 취소돼요.`); const legal = v.legalMoves ?? []; if (legal.some((m) => m.promotion)) lines.push(v.autoQueen ? '폰을 끝 줄로 보내면 자동으로 퀸이 돼요(승진).' : '폰을 끝 줄로 보내면 퀸·룩·비숍·나이트 중 무엇으로 바꿀지 고르는 창이 떠요(승진). 보통은 퀸이에요.'); if (legal.some((m) => m.castle)) lines.push('지금 캐슬링을 할 수 있어요. 킹을 누르면 캐슬링할 칸도 표시돼요.'); if (v.threatened?.length) lines.push('주황 점선 네모가 쳐진 내 기물은 지켜 주는 기물 없이 공격받고 있어요. 조심하세요.'); if (v.drawMode === 'fide' && v.claimable) lines.push(`[⚖ 무승부 주장]을 누르면 상대 동의 없이 바로 비길 수 있어요(${v.claimable === 'threefold' ? '같은 국면 3번' : '50수 동안 폰 이동·잡기 없음'}).`); if (mat) lines.push(mat); return lines.join('\n'); }