Files
joke-app/docs/games/janggi.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

259 lines
22 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 장기 (`janggi`)
> 마일스톤: M2 · 인원: 최소 2 ~ 최대 2명 · 예상 시간: 약 20~40분 · 난이도: 보통
## 1. 개요
- 초(楚)와 한(漢)이 각 16개의 기물로 상대 궁(장)을 외통수로 몰면 이기는 한국 전통 2인 전략 게임. 공원·경로당·가정에서 어르신부터 어린이까지 두는 친숙한 게임이며 장기판 앞의 "장군!" "멍군!"이 상징적이다.
- 인원 근거: 전통 규칙상 초·한 2인. 최소 2, 최대 2.
- 규칙 기준: (사)대한장기연맹(KJF) 규정을 기본으로 하고, 온라인에 맞게 구현 방식을 정했다. 단체마다 다른 부분(빅장 조건, 반복수 처리, 상차림 순서)은 옵션으로 둔다.
- 완전 정보 게임. 단, `pairing=simultaneous` 상차림 옵션을 쓰면 상대 상차림이 확정 전까지 비공개(8.3).
## 2. 모드와 옵션
| 옵션 키 | 설명 | 선택지 | 기본값 |
|---|---|---|---|
| `scoring` | 점수제(판정이 필요한 종료를 기물 점수로 가림) | `true` / `false`(무승부 처리) | `true` |
| `bikjang` | 빅장 규칙 | `off`(궁이 마주봐도 아무 일 없음) / `kjf`(기물이 하나라도 잡힌 다음 수부터 빅장 가능) / `kja30`(양쪽 기물 점수가 모두 30점 미만일 때만 가능) | `kjf` |
| `repetition` | 반복수 처리 (같은 국면 4번째) | `forbid`(4번째를 만드는 수를 둘 수 없음) / `lose`(KJF: 4회 반복한 쪽 실격패) / `scoreEnd`(4번째 국면 발생 시 점수 판정 종료) | `forbid` |
| `allowPass` | 한수쉼 허용 | `true` / `false` | `true` |
| `setupOrder` | 상차림 순서 | `hanFirst`(KJF: 후수 한이 먼저 공개적으로 차리고 초가 나중) / `simultaneous`(동시에 비공개로 고른 뒤 공개) | `hanFirst` |
| `sideAssignment` | 초·한 결정 | `random` / `hostCho` / `hostHan` / `alternate` | `random` |
| `moveLimit` | 수 제한(도달 시 점수 판정) | `0`(없음) / 150 / 200 / 250 (양쪽 합산 수) | `0` |
| `timeControl.kind` | 시간 방식 | `none` / `byoyomi` | `byoyomi` |
| `timeControl.mainSec` / `periodSec` / `periods` | 기본 시간 / 초읽기 1회 / 횟수 | 5분·10분·20분 / 20·30·60초 / 1~5회 | 10분 / 30초 / 3회 |
| `idleLimitSec` | `none`일 때 무응답 한도 | 300 / 600 | 600 |
| `undo` | 무르기 허용 횟수(상대 동의) | `off` / `1` / `3` / `unlimited` | `3` |
| `drawOffer` | 무승부 제안 | `true` / `false` | `true` |
| `hints` | 이동 가능 칸 표시, 장군 위험 경고 | `off` / `moves` / `movesAndThreats` | `moves` |
| `disconnectGraceSec` | 연결 끊김 유예 | 60 / 120 | 60 |
## 3. 구성물
- 판: 가로 9줄 × 세로 10줄, 교차점 90개. 기물은 교차점에 놓는다. 강(river) 없음.
- 궁성: 양쪽 끝 가운데 3×3 영역. 궁성 안에는 X자 대각선이 그려져 있다.
- 기물(각 진영 16개):
| 기물 | 초 / 한 | 수 | 점수 |
|---|---|---|---|
| 궁(장) | 楚 / 漢 | 1 | — |
| 차 | 車 | 2 | 13 |
| 포 | 包 | 2 | 7 |
| 마 | 馬 | 2 | 5 |
| 상 | 象 | 2 | 3 |
| 사 | 士 | 2 | 3 |
| 졸 / 병 | 卒 / 兵 | 5 | 2 |
기물 점수 합 72점. 후수(한)에게 덤 1.5점 → 초 72, 한 73.5로 시작.
- 내부 좌표: `(f, r)`, `f` 0~8(초 기준 왼→오), `r` 0~9(초의 뒷줄 0, 한의 뒷줄 9).
- 초 궁성: `f` 3~5, `r` 0~2, 중심 `(4,1)`. 대각선: `(3,0)–(4,1)–(5,2)`, `(5,0)–(4,1)–(3,2)`.
- 한 궁성: `f` 3~5, `r` 7~9, 중심 `(4,8)`. 대각선: `(3,7)–(4,8)–(5,9)`, `(5,7)–(4,8)–(3,9)`.
## 4. 준비(셋업)
1. `sideAssignment`로 초(선수)·한(후수) 결정. 초가 먼저 둔다.
2. 고정 배치(초 기준, 한은 `r → 9 - r`로 대칭):
- 궁 `(4,1)` (궁성 중심), 사 `(3,0)`, `(5,0)`, 차 `(0,0)`, `(8,0)`, 포 `(1,2)`, `(7,2)`, 졸 `(0,3) (2,3) (4,3) (6,3) (8,3)`.
- 한: 궁 `(4,8)`, 사 `(3,9)` `(5,9)`, 차 `(0,9)` `(8,9)`, 포 `(1,7)` `(7,7)`, 병 `(0,6) (2,6) (4,6) (6,6) (8,6)`.
3. 상차림: 마·상 4개를 뒷줄의 차와 사 사이 두 칸씩(왼쪽 2칸, 오른쪽 2칸)에 놓는다. 각 플레이어가 자기 시점에서 왼쪽→오른쪽으로 4가지 중 선택:
| 선택 | 자기 시점 왼→오 | 별칭 |
|---|---|---|
| `MSSM` | 마 상 · 상 마 | 안상차림, 원앙마 |
| `SMMS` | 상 마 · 마 상 | 바깥상차림, 양귀마 |
| `SMSM` | 상 마 · 상 마 | 왼상차림(귀마) |
| `MSMS` | 마 상 · 마 상 | 오른상차림(귀마) |
- 초의 왼쪽은 `f` 작은 쪽 → 칸 `(1,0) (2,0) (6,0) (7,0)`. 한은 판 반대편에 앉아 있으므로 한의 "왼쪽"은 `f` 큰 쪽 → 칸 `(7,9) (6,9) (2,9) (1,9)` 순서로 대응.
- `setupOrder=hanFirst`: 한이 먼저 선택(즉시 공개) → 초가 보고 선택. `simultaneous`: 둘 다 비공개로 선택, 둘 다 확정되면 동시에 공개.
4. 시계 초기화, 반복 국면 카운트에 초기 국면(초 차례) 1회 기록.
## 5. 진행 규칙
### 5.1 턴
- 초부터 번갈아 한 수씩: 기물 하나 이동(이동 칸에 상대 기물이 있으면 잡음) 또는 한수쉼(`allowPass`, 장군 상태가 아닐 때만).
- 자기 기물이 있는 칸으로는 이동 불가. 판 밖 이동 불가.
- 이동 후 자기 궁이 상대 기물의 공격을 받게 되는 수는 불법(장군을 피하지 않는 수, 스스로 장군에 들어가는 수 모두 포함).
### 5.2 기물별 행마
- 궁, 사: 궁성 안에서만, 궁성의 선을 따라 1칸. 가로·세로 1칸, 그리고 대각선이 그려진 곳(모서리↔중심)에서만 대각 1칸. 즉 모서리 4곳·중심은 대각 가능, 변의 가운데 4곳(`(4,0) (3,1) (5,1) (4,2)` 등)에서는 대각 불가.
- 차: 가로·세로로 막히지 않는 한 몇 칸이든. 궁성 안(양쪽 궁성 모두)에서는 대각선을 따라서도 이동: 모서리→중심→반대 모서리까지(중심이 비어 있어야 반대 모서리로 감), 중심→모서리 1칸.
- 포: 차와 같은 선(가로·세로, 궁성 대각선)을 따라 움직이되, 반드시 정확히 1개의 기물(포다리)을 뛰어넘어야 한다.
- 포다리는 피아 구분 없이 아무 기물이나 되지만 포(어느 편이든)는 포다리가 될 수 없다.
- 포다리 너머 첫 기물 앞까지의 빈칸 어디든 이동 가능, 또는 포다리 너머 첫 기물이 상대 기물이면 잡을 수 있다. 단 포는 포를 잡을 수 없다.
- 포다리 없이는 한 칸도 움직일 수 없다.
- 궁성 대각선: 모서리에 있는 포는 중심에 포가 아닌 기물이 있으면 그것을 넘어 반대 모서리로 갈 수 있다(빈칸이면 이동, 상대 비-포 기물이면 잡기).
- 마: 가로 또는 세로 1칸 간 뒤 그 방향으로 대각선 1칸(목적지 8곳: `(±1,±2) (±2,±1)`). 첫 1칸(멱)에 기물이 있으면 그 방향으로는 갈 수 없다. 궁성 대각선과 무관.
- 상: 가로 또는 세로 1칸 간 뒤 그 방향으로 대각선 2칸(목적지 8곳: `(±2,±3) (±3,±2)`). 경로의 첫 칸(직선 1칸)과 둘째 칸(대각 1칸째)이 모두 비어 있어야 한다. 예: `(f,r)`에서 위로 가는 상 → 멱 `(f,r+1)`, `(f±1,r+2)`, 목적지 `(f±2,r+3)`.
- 졸(초)/병(한): 앞 또는 옆으로 1칸. 뒤로는 못 간다. 상대 궁성 안에서는 앞쪽 대각선을 따라 1칸 추가 가능: 초 졸은 `(3,7)→(4,8)`, `(5,7)→(4,8)`, `(4,8)→(3,9)`, `(4,8)→(5,9)`; 한 병은 `(3,2)→(4,1)`, `(5,2)→(4,1)`, `(4,1)→(3,0)`, `(4,1)→(5,0)`. 마지막 줄에서는 옆으로만 이동.
- 모든 기물은 승급 없음.
### 5.3 장군·멍군·외통
- 장군: 다음 수에 상대 궁을 잡을 수 있는 상태(포다리를 이용한 포의 장군 포함). 장군을 받은 쪽은 반드시 피해야 하며 한수쉼 불가.
- 멍군: 장군을 막아낸 응수(가리기, 공격 기물 잡기, 궁 피하기). UI 표시용 용어이며 규칙상 별도 효과 없음.
- 외통(체크메이트): 장군 상태에서 합법 수가 없음 → 패배. 궁을 실제로 잡지는 않는다.
- 장군 상태가 아닌데 합법 이동이 없으면 한수쉼(옵션이 꺼져 있어도 이 경우 자동 한수쉼 처리).
### 5.4 빅장
- 정의: 두 궁이 같은 세로줄(`f` 같음)에 있고 그 사이에 기물이 하나도 없는 상태. 장군이 아니다.
- 빅장 성립(부르기): 어떤 수의 결과로 빅장 상태가 되고, 그 시점에 `bikjang` 조건이 충족되면 그 수를 둔 쪽이 빅장을 부른 것이다.
- `kjf`: 이전 수들 중 잡기가 한 번 이상 있었다(첫 잡기가 일어난 그 수 자체로는 부를 수 없고, 그 다음 수부터).
- `kja30`: 그 수를 둔 직후 양쪽 기물 점수(덤 제외)가 모두 30점 미만.
- 조건이 충족되지 않으면 궁이 마주봐도 아무 일도 없다(합법이며 빅장 아님).
- 응수: 빅장을 받은 쪽은 한 수를 둔다. 그 수 후에도 두 궁이 여전히 마주보고 있으면(한수쉼, 무관한 수, 잡기만 하고 막지 않은 수 포함) 빅 → 즉시 종료: `scoring=true`면 남은 기물 점수로 판정, `false`면 무승부. 마주보지 않게 만들면(사이에 기물을 넣거나 궁을 옆으로) 대국 계속.
- 빅장을 부르는 수가 동시에 장군이 될 수는 없다(궁끼리는 장군 관계가 아님). 다른 기물로 장군을 건 상태라면 상대는 장군을 먼저 해결해야 하며, 그 응수 후에도 마주보면 빅.
- `bikjang=off`: 마주보기는 아무 효과 없음(카카오 장기 등 온라인 일부 방식).
### 5.5 반복수
- 국면 = 판 배치 + 둘 차례. 같은 국면이 3번까지는 허용, 4번째로 나타나는 경우:
- `forbid`(기본): 4번째 국면을 만드는 수는 둘 수 없다("같은 수를 너무 많이 반복해서 둘 수 없어요"). 예외: 장군을 받았는데 그 수 말고 합법 수가 없으면 허용하고 즉시 점수 판정(`scoring=false`면 무승부).
- `lose`: 4번째 국면을 만든 쪽 실격패(KJF 규정 "4회 반복 시 실격패").
- `scoreEnd`: 4번째 국면이 나오면 점수 판정 종료.
- 한수쉼으로 생긴 국면도 같은 방식으로 센다(차례만 바뀌므로 별도 국면).
### 5.6 한수쉼 연속
- 양쪽이 연속으로 한수쉼 → 종료: `scoring=true`면 점수 판정, 아니면 무승부.
## 6. 승패와 점수 계산
- 완승: 외통, 상대 기권, 상대 시간패, 상대 실격(`repetition=lose`).
- 점수 판정이 일어나는 경우(`scoring=true`): 빅장 성립, 양쪽 연속 한수쉼, 수 제한 도달, `repetition=scoreEnd`, `forbid`의 예외 상황.
- 점수 = 판에 남은 자기 기물 점수 합(차 13, 포 7, 마 5, 상 3, 사 3, 졸·병 2, 궁 0) + 한에게 덤 1.5.
- 덤이 0.5 단위라 동점은 나오지 않는다. 높은 쪽 승.
- 예시: 빅 성립 시 초에 차2·포1·마1·사2·졸3 = 26+7+5+6+6 = 50, 한에 차1·포2·마2·상1·사2·병4 = 13+14+10+3+6+8 = 54 + 1.5 = 55.5 → 한 5.5점 승("한 판정승 55.5 : 50").
- `scoring=false`면 위 판정 상황은 모두 무승부.
- 무승부 합의도 가능.
## 7. 지역 룰 / 하우스 룰 (옵션으로 켜고 끄기)
- 점수제/비점수제 × 빅장 있음/없음의 4가지 대국 방식이 실제로 공존한다. 기본은 정식 대회에서 주로 쓰는 "점수제 + 빅장".
- 빅장 조건: KJF(기물 1개 이상 잡힌 뒤) / 대한장기협회(KJA, 양쪽 30점 미만) / 없음.
- 반복수: 기본은 "막기"(어린이에게 실격패보다 친절), 공식처럼 실격패도 선택 가능.
- 상차림 순서: KJF는 후수(한)가 먼저 차린다. 친선에서는 각자 동시에 정하는 경우가 많아 옵션 제공.
- 접장기(실력 차 보정으로 차·포 등을 떼고 두기), 기물 점수 변형은 v1 미지원(추후 `handicapRemove` 옵션 후보).
## 8. 엔진 설계
### 8.1 상태(State)
```ts
type Side = 'cho' | 'han';
type PieceKind = 'king' | 'cha' | 'po' | 'ma' | 'sang' | 'sa' | 'jol';
type Piece = { side: Side; kind: PieceKind };
type Sq = { f: number; r: number };
type Arrangement = 'MSSM' | 'SMMS' | 'SMSM' | 'MSMS';
interface JanggiState {
options: JanggiOptions;
players: Record<Side, PlayerId>;
phase: 'setup' | 'playing' | 'finished';
setup: { choice: Record<Side, Arrangement | null> }; // simultaneous면 상대 값은 view에서 숨김
board: (Piece | null)[]; // index = r*9 + f
turn: Side;
moves: ({ kind: 'move'; side: Side; from: Sq; to: Sq; captured: PieceKind | null }
| { kind: 'pass'; side: Side })[];
firstCapturePly: number | null; // 첫 잡기가 일어난 수 번호
positionCounts: Record<string, number>; // 배치+차례 -> 횟수
bikjang: { calledBy: Side; ply: number } | null; // 응수 대기 중인 빅장
consecutivePasses: number;
inCheck: Side | null;
clock: { mainMs: Record<Side, number>; periodsLeft: Record<Side, number>; turnStartedAt: number };
undo: { usedBy: Record<Side, number>; pending: { by: Side; plies: 1 | 2 } | null };
draw: { pendingBy: Side | null; lastOfferPly: Record<Side, number> };
result: JanggiResult | null;
rng: RngState;
}
```
### 8.2 액션
| type | payload | 누가 / 언제 | 검증 조건 |
|---|---|---|---|
| `chooseSetup` | `{ arrangement }` | `setup` 단계. `hanFirst`: 한이 먼저, 그다음 초 / `simultaneous`: 둘 다 | 아직 선택 안 함, 순서 맞음. 둘 다 정해지면 배치 확정, `playing` 진입, 초 차례 |
| `move` | `{ from: Sq, to: Sq }` | 차례인 플레이어, `playing` | `from`에 자기 기물, `to`가 5.2 행마로 도달 가능, 자기 기물 없음, 포는 포다리 조건(포 넘기·포 잡기 금지), 이동 후 자기 궁이 공격받지 않음, 반복수 `forbid` 위반 아님. 사유 예: "포는 포를 넘을 수 없어요", "마의 길(멱)이 막혀 있어요", "장군을 피해야 해요", "궁은 궁성 밖으로 나갈 수 없어요" |
| `pass` | `{}` | 차례인 플레이어 | `allowPass` 또는 합법 이동 없음, 장군 상태 아님("장군을 받은 상태에서는 쉴 수 없어요"), 반복수 위반 아님 |
| `requestUndo` / `respondUndo` | `{}` / `{ accept }` | 플레이어 / 상대 | 횟수 남음, 대기 없음. 수락 시 `moves` 재생으로 판·`positionCounts`·`firstCapturePly`·`bikjang` 재구성, 시계 유지 |
| `offerDraw` / `respondDraw` / `cancelOffer` | | 플레이어 | 거절 후 본인 5수 이후 재제안 |
| `resign` | `{}` | 플레이어 | `setup` 중이면 결과 없이 취소 |
| `timeout` | `{}` | 시스템 | 8.5 |
- `move`/`pass` 적용 후 처리 순서:
1. 시계 차감(초읽기 규칙은 바둑과 동일: 기본 시간 → 초읽기 회차, 회차 초과 시 소진, 0이면 시간패로 액션 무효).
2. 판 갱신, 잡기면 `firstCapturePly` 설정(처음일 때).
3. 응수 대기 중인 빅장이 있고 지금 둔 사람이 응수자라면: 여전히 마주봄 → 빅(종료). 아니면 `bikjang = null`.
4. 상대 외통 판정 → 승리.
5. 새로 빅장 상태가 되었고 조건 충족 → `bikjang = { calledBy: 둔 사람 }`, 이벤트 "빅장".
6. 반복 카운트 증가, `lose`/`scoreEnd` 처리, 연속 한수쉼 처리, `moveLimit` 처리.
7. 장군 여부 이벤트(`janggun`), 직전 장군 해소면 `meonggun` 이벤트.
- 공격 판정 함수 `attacks(board, side, sq)`: 차(직선·궁성 대각), 포(포다리 1개, 포 제외), 마·상(멱 확인), 졸(앞·옆·궁성 대각 앞), 사·궁(궁성 안 인접 선) 모두 포함. 궁끼리 마주봄은 공격이 아니다.
### 8.3 공개/비공개 정보 (view)
- 공개: 판, 차례, 기보, 잡은 기물, 현재 점수(덤 포함), 장군 상태, 빅장 응수 대기 여부, 반복 경고(같은 국면 3회째 도달 시 "한 번 더 반복하면 둘 수 없어요"), 시계, `deadline`, 대기 중인 요청, 결과.
- `setup` 단계 + `simultaneous`: 상대 선택값은 "선택 완료"(true/false)만 보이고 배치는 둘 다 확정될 때까지 숨긴다(플레이어·관전자 모두). `hanFirst`에서는 한의 선택이 즉시 공개.
- 차례인 플레이어에게만: `legalMoves`(하이라이트용), 힌트 옵션 시 `threatened`(잡힐 위험 기물).
- 제외: `rng`, 상대의 미공개 상차림.
### 8.4 랜덤 요소 (시드 RNG 사용 지점)
- `sideAssignment=random`의 초·한 결정.
- 상차림 시간 초과 시 자동 선택(4가지 중 균등 무작위).
- 대국 중에는 사용하지 않는다.
### 8.5 타이머·시간 초과·연결 끊김 시 자동 행동
- `deadline(state)`:
- `setup`: 단계 시작 + 60초(선택해야 할 사람 기준).
- `playing` + `byoyomi`: `turnStartedAt + mainMs[turn] + periodsLeft[turn]*periodSec*1000`.
- `playing` + `none`: `turnStartedAt + idleLimitSec*1000`.
- 종료: `null`.
- `activePlayers`: `setup`에서 `hanFirst`면 순서에 맞는 1명, `simultaneous`면 아직 선택 안 한 전원. `playing`이면 차례인 1명.
- `onTimeout(state, player)`:
- `setup`: `{ type: 'timeout' }` → `apply`가 시드 RNG로 4가지 중 하나를 골라 `chooseSetup`과 같은 처리(`onTimeout`은 RNG를 쓰지 않는 순수 함수로 유지).
- `playing`: `{ type: 'timeout' }` → 시간패. 장기는 자동 수를 두지 않는다.
- 연결 끊김: 끊긴 플레이어가 행동해야 할 때 `min(deadline, 끊김 + disconnectGraceSec)`에 `onTimeout`.
### 8.6 종료 조건과 결과(GameResult)
```ts
interface JanggiResult {
ranking: { playerId: PlayerId; rank: 1 | 2 }[]; // 무승부면 둘 다 1, 셋업 중 취소면 빈 배열
winner: Side | null;
reason: 'checkmate' | 'resign' | 'timeLoss' | 'repetitionLoss'
| 'scoreBikjang' | 'scorePasses' | 'scoreMoveLimit' | 'scoreRepetition'
| 'drawBikjang' | 'drawPasses' | 'drawRepetition' | 'drawMoveLimit' | 'drawAgreed' | 'aborted';
scores: { cho: number; han: number }; // 항상 기록(한은 덤 포함)
moveCount: number;
summaryKo: string; // "초 외통승 (63수)", "한 판정승 55.5 : 50 (빅장)"
}
```
## 9. UI/UX
- 판 방향: 내 진영이 아래. 초는 파랑(또는 초록), 한은 빨강. 기물은 팔각형(차·포·마·상 큰 것, 사·졸 작은 것) 한자 + 작은 한글 라벨 옵션("車 차")으로 한자에 익숙하지 않은 어린이 배려.
- 모바일 세로: 판(9×10)이 세로로 길어 세로 화면에 잘 맞는다. 폰 360px 폭에서 교차점 간격 약 38px이므로 기물 터치 영역을 교차점 간격 전체(정사각형)로 잡고, 선택 시 확대 강조.
- 조작: 기물 탭 → 이동 가능 점 표시(잡기 가능 칸은 빨간 링) → 탭. 포는 포다리 기물을 살짝 강조해 이유가 보이게.
- 상차림 화면: 4개 큰 카드(그림 + 이름 "안상차림 / 원앙마"), 한 화면에 미리보기.
- "장군!" "멍군!" "빅장!" 큰 말풍선과 효과음(목소리 옵션). 외통 시 결과 모달.
- 상단에 양측 점수 바(초 72 : 한 73.5) 상시 표시 → 판정 상황 이해 도움.
- 한수쉼 버튼은 확인 모달. 반복 경고 배너.
- 규칙 보기: 기물별 행마 애니메이션, 멱(막힘) 예시, 포다리 예시, 빅장 설명.
## 10. 테스트 체크리스트
- [ ] 초기 배치 4가지 상차림 × 초/한 시점 매핑이 정확(비대칭인 `SMSM` 기준: 초 → `(1,0)` 상, `(2,0)` 마, `(6,0)` 상, `(7,0)` 마 / 한 → `(7,9)` 상, `(6,9)` 마, `(2,9)` 상, `(1,9)` 마).
- [ ] 마·상 멱: 첫 칸이 막히면 해당 방향 2곳 불가, 상은 둘째 칸 막힘도 확인.
- [ ] 포: 포다리 없이 이동 불가, 포를 포다리로 사용 불가, 포를 잡기 불가, 포다리 너머 여러 빈칸 이동 가능, 궁성 대각선 넘기.
- [ ] 차의 궁성 대각 이동: 모서리→반대 모서리(중심 비었을 때), 중심 막혔을 때 불가.
- [ ] 궁·사가 변 가운데 점에서 대각 이동 불가, 궁성 밖 이동 불가.
- [ ] 졸: 뒤로 불가, 상대 궁성 대각 앞 이동 가능, 자기 궁성에서 대각 불가.
- [ ] 스스로 장군에 들어가는 수 거부, 포다리 역할 기물을 치워 포 장군에 노출되는 수 거부(핀).
- [ ] 장군 중 한수쉼 거부, 외통 판정.
- [ ] 빅장(`kjf`): 잡기 전엔 마주봐도 무효, 첫 잡기 수 자체로는 불가, 그 이후 성립. 응수로 막으면 계속, 못 막으면 점수 판정(덤으로 동점 없음).
- [ ] 빅장(`kja30`): 한쪽이라도 30점 이상이면 무효.
- [ ] 반복수 `forbid`: 4번째 국면을 만드는 수 거부, 장군 중 유일한 수면 허용 후 판정 종료. `lose`: 실격패.
- [ ] 연속 한수쉼 → 점수 판정 / 무승부.
- [ ] 초읽기 시간패, 상차림 시간 초과 시 RNG 자동 선택이 같은 시드에서 동일.
- [ ] `simultaneous` 상차림 중 상대·관전자 view에 선택값 누출 없음(선택 완료 여부만).
- [ ] 무르기 수락 시 `firstCapturePly`, `bikjang`, 반복 카운트 복원.
- [ ] view에 `rng` 없음, 관전자에게 `legalMoves` 없음.
## 11. 참고 자료
- (사)대한장기연맹 장기규정/규칙: https://kojf.net/theme/sample30/html/content03.php (점수·덤 1.5, 판정, 빅장은 기물을 하나라도 취한 후 다음 수부터, 반복수 3회 허용·4회 실격패, 후수가 먼저 상차림, 한수쉼)
- 대한장기연맹 장기규칙 개정 공지(2019.12.31): http://kojf.net/post/478
- 대한장기협회(KJA): http://www.kja.or.kr/business/business5.php (점수제 빅장은 양측 30점 미만일 때)
- 위키백과 「장기」: https://ko.wikipedia.org/wiki/%EC%9E%A5%EA%B8%B0
- 나무위키 「장기/기물 및 행마법」: https://namu.wiki/w/%EC%9E%A5%EA%B8%B0/%EA%B8%B0%EB%AC%BC%20%EB%B0%8F%20%ED%96%89%EB%A7%88%EB%B2%95 , 「장기/용어」: https://namu.wiki/w/%EC%9E%A5%EA%B8%B0/%EC%9A%A9%EC%96%B4
- 상차림 명칭(원앙마·양귀마·귀마): https://brunch.co.kr/@1305b8c2c18141c/529 , https://ko.wikibooks.org/wiki/%EC%9E%A5%EA%B8%B0/%EC%B4%88%EB%B0%98_%ED%8F%AC%EC%A7%84%EB%B2%95
- 점수제/빅장 대국 방식 4종 정리: https://gall.dcinside.com/mgallery/board/view/?id=janggi&no=10449
- Janggi 영문 규칙 요약: https://janggionline.com/how-to-play/ , https://en.wikipedia.org/wiki/Janggi
## 12. 메모 (상표·법적 주의 등)
- "장기"는 전통 게임 명칭으로 상표 문제 없음. 기물 그림·폰트는 자체 제작 또는 무료 라이선스 폰트 사용.
- 단체마다 세부 규칙이 달라 "대한장기연맹 규정 기반, 일부 온라인 방식 적용"이라고 규칙 보기에 명시.
- 금전·베팅 요소 없음.