import { expect, test } from 'bun:test'; import { applyAction, setupGame, usersOf } from '../help-kit.test-util'; import { GAMES } from '@bg/games'; import { helpNow } from './help'; const users = usersOf(['A', 'B']); const view = (s: unknown, who: string | null) => GAMES.janggi!.view(s, who) as any; const mv = (f1: number, r1: number, f2: number, r2: number) => ({ type: 'move', from: { f: f1, r: r1 }, to: { f: f2, r: r2 } }); /** A = 초, B = 한; both 상차림 chosen (한 first), 초 to move. */ function started(o: Record = {}) { const { state } = setupGame('janggi', 2, { sideAssignment: 'hostCho', setupOrder: 'hanFirst', ...o }); const s = applyAction('janggi', state, 'B', { type: 'chooseSetup', arrangement: 'MSSM' }); return applyAction('janggi', s, 'A', { type: 'chooseSetup', arrangement: 'SMMS' }); } /** Custom position in play (kept consistent with the rule module: 장군 flag from the real board). */ function custom(pieces: [string, string, number, number][], turn: 'cho' | 'han', inCheck: 'cho' | 'han' | null) { const s = started(); s.board = new Array(90).fill(null); for (const [side, kind, f, r] of pieces) s.board[r * 9 + f] = { side, kind }; s.startBoard = s.board.slice(); s.turn = turn; s.inCheck = inCheck; s.positionCounts = {}; return s; } test('장기: 상차림 단계 (한 먼저 고르기)', () => { const { state } = setupGame('janggi', 2, { sideAssignment: 'hostCho', setupOrder: 'hanFirst' }); expect(helpNow(view(state, 'B'), 'B', users)).toContain('상차림을 골라 주세요(나는 한)'); const cho = helpNow(view(state, 'A'), 'A', users); expect(cho).toContain('한이 먼저 상차림을 골라요'); expect(cho).toContain('고양이님이 상차림을 고르는 중'); expect(helpNow(view(state, null), null, users)).toContain('고양이님이 상차림'); const s = applyAction('janggi', state, 'B', { type: 'chooseSetup', arrangement: 'MSSM' }); expect(helpNow(view(s, 'A'), 'A', users)).toContain('한이 안상차림을 골랐어요'); }); test('장기: 내 차례 / 상대 차례 / 관전 (점수 포함)', () => { const s = started(); const mine = helpNow(view(s, 'A'), 'A', users); expect(mine).toContain('내 차례예요(초)'); expect(mine).toContain('초록 점'); expect(mine).toContain('[⏸ 한수쉼]'); expect(mine).toContain('초 72점, 한 73.5점'); expect(helpNow(view(s, 'B'), 'B', users)).toContain('나는 한이에요'); expect(helpNow(view(s, null), null, users)).toContain('호랑이님(초) 차례예요'); }); test('장기: 장군을 받으면 한수쉼 없이 궁을 지키라고 안내', () => { const s = custom([['cho', 'king', 3, 0], ['han', 'king', 4, 9], ['cho', 'cha', 0, 9]], 'han', 'han'); const t = helpNow(view(s, 'B'), 'B', users); expect(t).toContain('장군이에요!'); expect(t).not.toContain('[⏸ 한수쉼]'); expect(helpNow(view(s, 'A'), 'A', users)).toContain('내가 장군을 불렀어요'); expect(helpNow(view(s, null), null, users)).toContain('한 궁이 장군을 받았어요'); }); test('장기: 빅장을 받은 쪽 안내 (점수제/비점수제)', () => { for (const scoring of [true, false]) { const s0 = started({ bikjang: 'kja30', scoring }); const s = custom([['cho', 'king', 4, 1], ['cho', 'jol', 4, 4], ['cho', 'cha', 0, 0], ['han', 'king', 4, 8], ['han', 'jol', 0, 6], ['han', 'cha', 8, 5], ['han', 'sa', 3, 9]], 'cho', null); s.options = s0.options; const after = applyAction('janggi', s, 'A', mv(4, 4, 3, 4)); expect(after.bikjang?.calledBy).toBe('cho'); const t = helpNow(view(after, 'B'), 'B', users); expect(t).toContain('빅장이에요!'); expect(t).toContain(scoring ? '점수로 승부를 가려요' : '무승부로 끝나요'); expect(helpNow(view(after, null), null, users)).toContain('초가 빅장을 불렀어요'); } });