# 체스 (`chess`) > 마일스톤: M2 · 인원: 최소 2 ~ 최대 2명 · 예상 시간: 블리츠 약 10분, 래피드 약 20~40분 · 난이도: 보통~어려움 ## 1. 개요 - 백과 흑이 16개씩의 기물로 상대 킹을 체크메이트하면 이기는 서양 장기. 한국에서는 학교 방과후 수업과 온라인(chess.com, lichess)으로 어린이·청소년층 인지도가 높다. - 인원 근거: 원작이 2인 게임. 최소 2, 최대 2. - 규칙 기준: FIDE 체스 규칙(Laws of Chess, 2023년 1월 시행판). 온라인 환경에 맞춘 차이는 7장과 6장에 명시. - 완전 정보 게임. view에서 제외할 것은 RNG 상태뿐. ## 2. 모드와 옵션 | 옵션 키 | 설명 | 선택지 | 기본값 | |---|---|---|---| | `variant` | 변형 | `standard` / `chess960` | `standard` | | `timeControl` | 시간 방식(피셔: 기본 시간 + 수당 추가) | 프리셋: 블리츠 `3+2`, `5+0`, `5+3` / 래피드 `10+0`, `10+5`, `15+10` / 클래식 `30+0` / `none` / 사용자 지정(1~90분, 0~60초) | `10+5` | | `drawMode` | 반복·50수 무승부 처리 | `auto`(3회 반복·50수 즉시 자동 무승부) / `fide`(3회 반복·50수는 클레임, 5회 반복·75수는 자동) | `auto` | | `colorAssignment` | 백흑 결정 | `random` / `hostWhite` / `hostBlack` / `alternate` | `random` | | `autoQueen` | 승진 시 퀸 자동 선택(설정 끄면 선택 창) | `true` / `false` | `false` (선택 창, 퀸이 첫 번째 큰 버튼) | | `undo` | 무르기 허용 횟수(1인당, 상대 동의) | `off` / `1` / `3` / `unlimited` | `1` | | `drawOffer` | 무승부 제안 | `true` / `false` | `true` | | `firstMoveSec` | 첫 수 제한(넘기면 대국 취소) | 15 / 30 / 60 | 30 | | `idleLimitSec` | `timeControl=none`일 때 무응답 한도 | 300 / 600 | 600 | | `hints` | 초보 도움말(이동 가능 칸 표시, 공격받는 기물 경고) | `off` / `moves` / `movesAndThreats` | `moves` | | `disconnectGraceSec` | 연결 끊김 유예 | 30 / 60 / 120 | 60 | ## 3. 구성물 - 8×8 칸(64칸), a1이 흑색 칸(백 기준 왼쪽 아래가 어두운 칸). - 각 색: 킹 1, 퀸 1, 룩 2, 비숍 2, 나이트 2, 폰 8. - 좌표: 열 a~h(백 기준 왼→오), 행 1~8(백 쪽부터). 기보는 SAN(예: `Nf3`, `exd5`, `O-O`, `e8=Q+`) 저장, 내부 처리는 UCI(`e2e4`, `e7e8q`). ## 4. 준비(셋업) - `standard`: 백 1행 a~h: R N B Q K B N R, 2행 폰. 흑은 8행·7행에 대칭(퀸은 자기 색 칸: 백 Q d1, 흑 Q d8). FEN `rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1`. - `chess960`: 시드 RNG로 0~959 중 번호 n을 뽑아 백 1행 배치를 만들고 흑은 같은 열에 대칭 배치. 폰 배치는 동일. 번호 → 배치(Scharnagl 방식): 1. `b1 = n % 4; n = floor(n/4)` → 밝은 칸 비숍을 열 `2*b1 + 1`(b, d, f, h)에. 2. `b2 = n % 4; n = floor(n/4)` → 어두운 칸 비숍을 열 `2*b2`(a, c, e, g)에. 3. `q = n % 6; n = floor(n/6)` → 남은 6칸 중 왼쪽부터 `q`번째(0부터)에 퀸. 4. `n`(0~9)로 남은 5칸 중 나이트 두 자리: 0:(0,1) 1:(0,2) 2:(0,3) 3:(0,4) 4:(1,2) 5:(1,3) 6:(1,4) 7:(2,3) 8:(2,4) 9:(3,4). 5. 남은 3칸에 왼쪽부터 R, K, R. - 검증: n = 518 → RNBQKBNR(표준 배치). - 백이 먼저 둔다. 시계: 양쪽 `baseSec`. 첫 수 규칙은 8.5 참고. ## 5. 진행 규칙 ### 5.1 기물 이동 - 킹: 8방향 1칸. 공격받는 칸으로는 못 간다. - 퀸: 가로·세로·대각 어느 거리든(가로막히면 정지). - 룩: 가로·세로 어느 거리든. 비숍: 대각 어느 거리든. 나이트: L자(2+1), 다른 기물을 뛰어넘음. - 폰: 앞으로 1칸(빈칸), 처음 위치에서는 2칸(두 칸 모두 빈칸). 대각 앞 1칸으로만 잡는다. - 잡기: 상대 기물이 있는 칸으로 이동하면 그 기물을 제거. 자기 기물 칸으로는 못 간다. ### 5.2 특수 규칙 - 앙파상(en passant): 상대 폰이 직전 수에 처음 위치에서 2칸 전진해 내 폰 바로 옆에 섰을 때, 바로 다음 수에 한해 그 폰이 1칸만 전진한 것처럼 대각선으로 잡을 수 있다. - 승진(promotion): 폰이 마지막 행에 도달하면 같은 색 퀸·룩·비숍·나이트 중 하나로 반드시 바꾼다(기존 기물 수와 무관). - 캐슬링(standard): 킹과 룩이 모두 한 번도 움직이지 않았고, 사이 칸이 모두 비었으며, 킹이 현재 체크 상태가 아니고, 킹이 지나가는 칸과 도착 칸이 공격받지 않을 때. 킹이 룩 쪽으로 2칸 이동하고 룩이 킹이 넘어간 칸으로 이동. 킹사이드: Ke1→g1, Rh1→f1. 퀸사이드: Ke1→c1, Ra1→d1(b1은 비어야 하지만 공격받아도 무관). - 캐슬링(chess960): 결과 위치는 표준과 동일(킹사이드: 킹 g, 룩 f / 퀸사이드: 킹 c, 룩 d). 조건: 해당 킹·룩 미이동, 킹의 현재 칸~도착 칸 사이 모든 칸(양 끝 포함)과 룩의 현재 칸~도착 칸 사이 모든 칸이 그 킹과 룩을 제외하고 비어 있음, 킹이 현재 체크 아님, 킹이 지나가는 칸·도착 칸이 공격받지 않음. 킹이나 룩이 이미 도착 칸에 있을 수 있다(이동 0칸). - 체크: 킹이 공격받는 상태. 자기 킹을 공격받게 두는 수(핀 된 기물 이동, 공격받는 칸으로 킹 이동 포함)는 불법. ### 5.3 턴 구조 - 차례인 플레이어는 합법 수 하나를 둔다. 패스 없음. - 합법 수가 없으면: 체크 상태면 체크메이트(패배), 아니면 스테일메이트(무승부). ## 6. 승패와 점수 계산 - 승리: 체크메이트, 상대 기권, 상대 시간패(단 아래 예외), 상대 연결 끊김 시간 초과. - 무승부: 1. 스테일메이트. 2. 기물 부족(데드 포지션의 결정적 부분집합, 자동): 킹 대 킹 / 킹+비숍 대 킹 / 킹+나이트 대 킹 / 킹+비숍(들) 대 킹+비숍(들)에서 모든 비숍이 같은 색 칸. 3. 3회 반복: 같은 국면(같은 차례, 같은 기물 배치, 같은 캐슬링 권리, 같은 앙파상 가능 여부 — 앙파상 칸은 실제로 합법적 앙파상 잡기가 가능할 때만 국면에 포함)이 3번째 나옴. `auto`면 즉시 무승부, `fide`면 차례인 플레이어가 클레임 가능, 5번째는 자동 무승부. 4. 50수 규칙: 양쪽 모두 50수(100 플라이) 동안 폰 이동·잡기 없음. `auto`면 즉시 무승부, `fide`면 클레임 가능, 75수(150 플라이)는 자동. 단 그 수가 체크메이트면 체크메이트 우선. 5. 무승부 합의. 6. 시간패 예외(FIDE 6.9): 시간이 다 된 쪽의 상대가 어떤 합법 수순으로도 체크메이트할 수 없으면 무승부. 판정 함수 `cannotMate(side)`: `side`가 킹만 있음 / 킹+나이트 1개만 있고 상대가 킹만 / 킹+비숍 1개만 있고 상대가 킹만 / `side`가 킹+같은 색 칸 비숍들만 있고 상대도 킹+같은 색 칸 비숍들만(또는 킹만). 이 외에는 메이트 가능으로 보수적으로 판단(→ 시간 초과한 쪽 패배). 예: 백이 시간 초과, 흑이 킹+나이트, 백이 킹+퀸이면 흑이 이론상 헬프메이트 가능하므로 흑 승. - 점수 개념 없음. 결과 요약: "백 승 (체크메이트, 34수)". - 대국 취소(`aborted`): 백 또는 흑이 첫 수를 `firstMoveSec` 안에 두지 않으면 결과 없이 취소(승패 기록 없음). ## 7. 지역 룰 / 하우스 룰 (옵션으로 켜고 끄기) - `drawMode=auto`(기본): chess.com처럼 3회 반복과 50수를 클레임 없이 자동 무승부. 어린이·어르신이 "클레임" 개념을 몰라 무한 반복하는 일을 막기 위함. - `drawMode=fide`: FIDE 9.2/9.3/9.6 그대로(클레임 버튼 제공 + 5회 반복·75수 자동). - Chess960: 무작위 첫 배치. 오프닝 암기 부담을 없앤 변형. - 터치 무브(손 댄 기물은 움직여야 함), 불법 수 둔 사람 패배 등 오프라인 규칙은 온라인에서 적용하지 않는다(불법 수는 서버가 거부). ## 8. 엔진 설계 ### 8.0 라이브러리 vs 자체 구현 평가 | 후보 | 장점 | 단점 | |---|---|---| | chess.js (BSD-2-Clause, TypeScript) | 오래 쓰인 검증된 수 생성, FEN/PGN/SAN, `isCheckmate/isStalemate/isInsufficientMaterial/isThreefoldRepetition/isDrawByFiftyMoves` 제공, 서버·클라이언트 공용, 라이선스 자유로움 | Chess960 캐슬링 미지원(이슈 #67, #441 등 장기 미해결). 3회 반복 판정의 국면 동일성 기준, 기물 부족 기준을 우리 규칙과 맞춰 확인 필요 | | chessops (TypeScript, lichess 개발자 제작) | Chess960 포함 각종 변형 지원, lichess에서 실사용 | GPL-3.0 이상. 클라이언트 번들에 포함하면 클라이언트 코드 공개 의무 발생 가능. 서버 전용으로 쓰면 배포가 아니므로 일반적으로 문제 없으나 법적 판단 필요 | | 자체 구현 | 의존성·라이선스 없음, 960 포함 완전 통제 | 수 생성 버그 위험. perft 테스트로 검증 필수, 개발 시간 증가 | - 권장: v1(standard)은 **chess.js를 서버에서만** 사용해 판정하고, 클라이언트는 서버 view의 `legalMoves`로 하이라이트(클라이언트 라이브러리 불필요 → 번들 경량화, 판정 이중화 불일치 방지). 대신 아래를 어댑터에서 우리 규칙으로 직접 판정한다: 반복 국면 키(앙파상 정규화), 50/75수, 기물 부족 목록, 시간패 예외 `cannotMate`. - Chess960(후속 마일스톤): (a) chessops를 서버 전용으로 도입(GPL 적용 범위를 사용자/법률 검토 후) 또는 (b) 자체 구현 + perft 검증. 사용자가 오픈소스 공개를 원치 않고 법률 검토도 부담이면 (b). 이 문서의 상태·액션 정의는 두 방식 모두에 맞도록 FEN(960은 Shredder-FEN: 캐슬링 권리를 룩의 열 문자로 표기, 예 `HAha`)을 기준으로 한다. - 어느 쪽이든 perft로 검증: 초기 국면 깊이 1~5 = 20 / 400 / 8,902 / 197,281 / 4,865,609, "Kiwipete" 국면(`r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1`) 깊이 1~4 = 48 / 2,039 / 97,862 / 4,085,603. 960은 공개된 chess960 perft 세트 사용. ### 8.1 상태(State) ```ts type Color = 'white' | 'black'; interface ChessState { options: ChessOptions; players: Record; startFen: string; // 960 배치 포함 chess960Index: number | null; fen: string; // 현재 국면 (권위 있는 단일 진실) moves: { uci: string; san: string; color: Color; at: number; fenAfter: string }[]; repetition: Record; // 국면 키 -> 등장 횟수 phase: 'playing' | 'finished'; clock: { remainingMs: Record; turnStartedAt: number; started: Record }; undo: { usedBy: Record; pending: { by: Color; plies: 1 | 2 } | null }; draw: { pendingBy: Color | null; lastOfferPly: Record }; claimable: 'threefold' | 'fiftyMove' | null; // drawMode=fide에서 차례인 쪽이 클레임 가능한 사유 result: ChessResult | null; rng: RngState; } // 국면 키 = FEN의 앞 4필드(배치, 차례, 캐슬링 권리, 앙파상 칸). // 단 앙파상 칸은 합법적인 앙파상 잡기가 실제로 있을 때만 남기고 아니면 '-'로 정규화. ``` ### 8.2 액션 | type | payload | 누가 / 언제 | 검증 조건 | |---|---|---|---| | `move` | `{ from: 'e2', to: 'e4', promotion?: 'q'\|'r'\|'b'\|'n' }` | 차례인 플레이어, `playing` | 합법 수 목록에 존재. 승진 수인데 `promotion` 없으면 거부("승진할 기물을 골라 주세요"). 승진 수가 아닌데 있으면 무시. 실패 사유 예: "그 기물은 그렇게 움직일 수 없어요", "킹이 공격받게 되어 둘 수 없어요", "지금은 상대 차례예요" | | `castle` | `{ side: 'king' \| 'queen' }` | 차례인 플레이어 | 5.2 조건. 960에서 킹 이동과 혼동되는 경우(킹이 1칸 옆 룩 칸 등) 명시적으로 사용. standard에서는 `move e1g1`도 캐슬링으로 인정 | | `claimDraw` | `{}` | 차례인 플레이어, `drawMode=fide` | `claimable != null` (FIDE: 클레임하는 수를 두기 직전 또는 직후 국면). 수락 없이 즉시 무승부 | | `requestUndo` | `{}` | 플레이어 | `undo != off`, 횟수 남음, 대기 없음, 본인 수 존재. 1 또는 2 플라이 | | `respondUndo` | `{ accept }` | 상대 | 수락 시 `moves` 끝에서 제거하고 `startFen`에서 재생해 `fen`·`repetition` 재구성. 시계는 되돌리지 않음 | | `offerDraw` | `{}` | 플레이어 | `drawOffer=true`, 대기 없음, 거절 후 본인 3수 이상 | | `respondDraw` | `{ accept }` | 상대 | 수락 → 무승부 | | `cancelOffer` | `{}` | 요청자 | | | `resign` | `{}` | 플레이어 | 언제든(첫 수 전이면 `aborted`로 처리) | | `timeout` | `{}` | 시스템 | 8.5 | - 수 처리 순서: 시계 차감(0 이하면 시간패 처리, 6장 예외 포함) → 합법성 → 적용 → 증가분 가산 → 종료 판정 순서: 체크메이트 > 스테일메이트 > 기물 부족 > (75수/5회 반복 자동) > (`auto`면 50수/3회 반복) → `claimable` 갱신. - 대기 요청은 요청받은 쪽이 수를 두면 자동 거절. - 이벤트: `moved`(SAN, 잡힌 기물, 체크 여부), `check`, `checkmate`, `stalemate`, `promoted`, `castled`, `enPassant`, `drawClaimable`, `gameEnded` 등. ### 8.3 공개/비공개 정보 (view) - 공통 공개: `fen`, 기보(SAN), 마지막 수, 체크 여부, 시계, `deadline`, 잡은 기물 목록과 기물 점수 차(표시용: 퀸9 룩5 비숍3 나이트3 폰1), 대기 중인 요청, `claimable`, 결과. - 차례인 플레이어에게: `legalMoves: { from; to; promotion?; castle? }[]`(하이라이트용). `hints=movesAndThreats`면 `threatenedSquares`(내 기물 중 공격받고 방어 안 된 칸). - 차례가 아닌 플레이어: 프리무브 UI를 위해 자기 기물의 의사 합법 수를 클라이언트에서 계산하지 않고, v1은 프리무브 미지원(단순성·공정성). - 관전자: `legalMoves`/`threatenedSquares` 없음. - 제외: `rng`(960 배치는 셋업 직후 공개되므로 문제없지만 다음 대국 배치 예측 방지). ### 8.4 랜덤 요소 (시드 RNG 사용 지점) 1. `colorAssignment=random`의 색 결정. 2. `chess960` 배치 번호 0~959 선택. 대국 중에는 RNG를 쓰지 않는다. ### 8.5 타이머·시간 초과·연결 끊김 시 자동 행동 - 시계 시작: 각 색의 첫 수는 `firstMoveSec` 제한만 있고 본 시계를 차감하지 않는다(lichess 방식). 백이 첫 수를 두면 흑의 첫 수 제한이 시작되고, 흑이 첫 수를 두면 백의 본 시계가 시작된다. - `deadline(state)`: - 해당 색 첫 수 전: `turnStartedAt + firstMoveSec*1000` - 피셔: `turnStartedAt + remainingMs[turn]` - `none`: `turnStartedAt + idleLimitSec*1000` - 종료: `null` - `onTimeout(state, player)` → `{ type: 'timeout' }`. `apply`: - 첫 수 제한 초과 → `aborted`(승패 없음). - 그 외 → `player` 시간패. 단 상대가 `cannotMate`면 무승부(`timeoutVsInsufficient`). - 자동 수는 두지 않는다(체스에서 무작위 수는 승패를 왜곡). - 연결 끊김: 끊긴 플레이어의 차례에 `min(deadline, 끊김 + disconnectGraceSec)`에 `onTimeout`. 상대 차례 중에 끊겼다면 자기 차례가 되는 순간부터 유예 계산. ### 8.6 종료 조건과 결과(GameResult) ```ts interface ChessResult { ranking: { playerId: PlayerId; rank: 1 | 2 }[]; // 무승부면 둘 다 1, aborted면 빈 배열 winner: Color | null; reason: 'checkmate' | 'resign' | 'timeLoss' | 'stalemate' | 'insufficientMaterial' | 'threefold' | 'fivefold' | 'fiftyMove' | 'seventyFiveMove' | 'drawAgreed' | 'timeoutVsInsufficient' | 'aborted'; pgn: string; // 태그(Variant, FEN 포함) + SAN 기보 moveCount: number; // 전체 수(풀무브) summaryKo: string; // "흑 승 (체크메이트, 28수)" } ``` ## 9. UI/UX - 모바일 세로: 상단 상대(잡은 기물, 시계), 가운데 정사각 판(폰 360px에서 칸 45px — 탭 정확도를 위해 칸 전체를 터치 영역으로), 하단 내 시계와 버튼 줄(무르기, 무승부, 기권, 기보, 규칙 보기). 판은 내 색이 아래로 오게 자동 회전. - 조작: 기물 탭 → 이동 가능 칸 점 표시(잡기 칸은 테두리 링) → 도착 칸 탭. 드래그도 지원. 다른 내 기물 탭 시 선택 변경. - 승진: 도착 시 큰 4개 버튼(퀸, 룩, 비숍, 나이트) 모달. - 캐슬링: 킹 선택 시 캐슬링 도착 칸(standard: g1/c1, 960: 룩 칸도 탭 가능) 표시. - 체크 시 킹 칸 빨간 배경 + "체크!" 말풍선, 체크메이트·스테일메이트 결과 모달에 이유 설명(초보용: "킹이 피할 곳이 없어요"). - 마지막 수 출발·도착 칸 강조, 이동 애니메이션 150ms 내외(저사양 고려). - 규칙 보기: 기물별 움직임 애니메이션 카드, 특수 규칙(앙파상·캐슬링·승진) 그림 설명. - 시계 10초 이하 시 빨간색 + 효과음(끌 수 있음). - 관전자: 판 방향 전환 버튼, 기보 탐색(로컬). ## 10. 테스트 체크리스트 - [ ] perft: 초기 국면 깊이 1~4(가능하면 5), Kiwipete 깊이 1~3이 8.0의 수치와 일치(어댑터 경유로 검증). - [ ] 캐슬링 금지 조건: 킹 체크 중, 지나가는 칸 공격받음, 도착 칸 공격받음, 룩 이동 이력, 킹 이동 이력. 퀸사이드에서 b1만 공격받는 경우는 허용. - [ ] 앙파상: 2칸 전진 직후에만 가능, 한 수 늦으면 불가. 앙파상으로 자기 킹이 노출되는(가로 핀) 경우 불법. - [ ] 승진: 4종 모두 가능, `promotion` 누락 시 거부, 잡으면서 승진. - [ ] 체크메이트·스테일메이트 판정(예: 스칼라 메이트 4수, 퀸으로 인한 스테일메이트). - [ ] 3회 반복: 앙파상 칸이 FEN에 있지만 실제 잡기 불가능한 경우 같은 국면으로 취급, 캐슬링 권리가 달라지면 다른 국면. - [ ] 50수: `auto`는 100플라이 째 무승부, 단 그 수가 체크메이트면 메이트 우선. `fide`는 클레임 버튼 활성화, 150플라이 자동. - [ ] 기물 부족: K vs K, KB vs K, KN vs K, 같은 색 비숍끼리 자동 무승부. KNN vs K는 자동 무승부 아님. - [ ] 시간패 예외: 시간 초과한 쪽 상대가 킹만 → 무승부, 상대 킹+나이트이고 시간 초과한 쪽이 킹+퀸 → 상대 승. - [ ] 첫 수 제한 초과 → `aborted`, 랭킹 비어 있음. - [ ] 피셔 증가분: 수를 둔 뒤 가산, 0 이하로 도착한 수는 거부. - [ ] Chess960: n=518이 표준 배치, 무작위 1000회 생성 시 비숍 반대 색·킹이 두 룩 사이 조건 항상 만족, 킹이 g열에 이미 있는 배치에서 킹사이드 캐슬링(킹 0칸 이동). - [ ] 무르기: 1/2 플라이 복원 후 `repetition` 카운트 정확, 거절 시 무변화. - [ ] 연결 끊김 유예 후 시간패 처리. - [ ] 정보 누출: 관전자·비차례 플레이어 view에 `legalMoves`/`threatenedSquares` 없음, 모든 view에 `rng` 없음. - [ ] 리플레이 결정성(같은 시드 → 같은 960 배치와 결과). ## 11. 참고 자료 - FIDE Laws of Chess (2023년 1월 1일 시행): https://handbook.fide.com/chapter/E012023 (3.8 캐슬링, 3.7 앙파상·승진, 5.2 스테일메이트·데드 포지션, 6.9 시간패 예외, 9.2 3회 반복, 9.3 50수, 9.6 5회 반복·75수) - FIDE 부록 Chess960 규칙(Guidelines III): 같은 핸드북 - Wikipedia "Fischer random chess": https://en.wikipedia.org/wiki/Fischer_random_chess (Scharnagl 번호 체계) - chess.js (BSD-2-Clause): https://github.com/jhlywa/chess.js , Chess960 미지원 이슈: https://github.com/jhlywa/chess.js/issues/67 - chessops (GPL-3.0-or-later): https://github.com/niklasf/chessops - Chess Programming Wiki "Perft Results": https://www.chessprogramming.org/Perft_Results ## 12. 메모 (상표·법적 주의 등) - "체스", "Chess"는 일반 명칭. "Chess960"/"Fischer Random"은 일반적으로 쓰이는 변형 명칭이지만 "피셔 랜덤" 대신 "체스960" 표기를 권장. - 라이선스: chess.js는 BSD-2(저작권 고지 포함 필요). chessops 도입 시 GPL-3 의무(특히 클라이언트 배포) 검토 필요 — 사용자 결정 사항. - 금전·베팅 요소 없음. 레이팅(Elo 등)은 범위 밖(방 단위 대국만).