Files
joke-app/docs/games/chess.md
EJClaw f4d63e1544 docs: 온라인 보드게임 사이트 계획서 (공통 14편 + 게임 26종)
- 개요, 조사(BGA·한국 인기 보드게임·UI/UX), 아키텍처, 실시간 프로토콜,
  안정성·보안, 계정, 로비·방, UI/UX, 데이터 모델, 게임 엔진, 테스트,
  배포·운영, 로드맵(M0~M9), 사용자 결정 항목
- 게임별 규칙·엔진 설계·UI·테스트 체크리스트 26종

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 04:18:29 +09:00

20 KiB
Raw Blame History

체스 (chess)

마일스톤: M2 · 인원: 최소 2 ~ 최대 2명 · 예상 시간: 블리츠 약 10분, 래피드 약 2040분 · 난이도: 보통어려움

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 / 사용자 지정(190분, 060초) 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.
  • 좌표: 열 ah(백 기준 왼→오), 행 18(백 쪽부터). 기보는 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로 검증: 초기 국면 깊이 15 = 20 / 400 / 8,902 / 197,281 / 4,865,609, "Kiwipete" 국면(r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1) 깊이 14 = 48 / 2,039 / 97,862 / 4,085,603. 960은 공개된 chess960 perft 세트 사용.

8.1 상태(State)

type Color = 'white' | 'black';

interface ChessState {
  options: ChessOptions;
  players: Record<Color, PlayerId>;
  startFen: string;                          // 960 배치 포함
  chess960Index: number | null;
  fen: string;                               // 현재 국면 (권위 있는 단일 진실)
  moves: { uci: string; san: string; color: Color; at: number; fenAfter: string }[];
  repetition: Record<string, number>;        // 국면 키 -> 등장 횟수
  phase: 'playing' | 'finished';
  clock: { remainingMs: Record<Color, number>; turnStartedAt: number; started: Record<Color, boolean> };
  undo: { usedBy: Record<Color, number>; pending: { by: Color; plies: 1 | 2 } | null };
  draw: { pendingBy: Color | null; lastOfferPly: Record<Color, number> };
  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)

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: 초기 국면 깊이 14(가능하면 5), Kiwipete 깊이 13이 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. 참고 자료

12. 메모 (상표·법적 주의 등)

  • "체스", "Chess"는 일반 명칭. "Chess960"/"Fischer Random"은 일반적으로 쓰이는 변형 명칭이지만 "피셔 랜덤" 대신 "체스960" 표기를 권장.
  • 라이선스: chess.js는 BSD-2(저작권 고지 포함 필요). chessops 도입 시 GPL-3 의무(특히 클라이언트 배포) 검토 필요 — 사용자 결정 사항.
  • 금전·베팅 요소 없음. 레이팅(Elo 등)은 범위 밖(방 단위 대국만).