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

252 lines
23 KiB
Markdown
Raw 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.
# 윷놀이 (`yut`)
> 마일스톤: M4 · 인원: 최소 2 ~ 최대 4명(개인전), 팀전 4·6·8명(2팀) · 예상 시간: 약 15~25분 · 난이도: 쉬움
## 1. 개요
- 윷가락 4개를 던져 나온 결과(도·개·걸·윷·모, 빽도)만큼 말을 움직여, 자기 말을 모두 먼저 판 밖으로 내보내면(날) 이기는 한국 전통 놀이. 설날·명절에 남녀노소가 함께 하는 가장 대중적인 놀이로, 규칙 설명 없이도 대부분의 한국인이 안다. 그만큼 "우리 집 규칙"이 다양하므로 기본값을 정하고 나머지는 옵션으로 연다.
- 인원 근거: 전통 윷놀이는 2명이 겨루거나, 여러 명이 두 편으로 나뉘어(인원 제한 없음) 한다. 개인전은 화면·말 색 구분이 가능한 4명까지, 팀전은 2팀 × 최대 4명(총 8명)으로 제한한다. `minPlayers = 2`, `maxPlayers = 8`이며 개인전 5명 이상은 옵션 검증에서 막는다.
## 2. 모드와 옵션
| 옵션 키 | 설명 | 선택지 | 기본값 |
|---|---|---|---|
| `mode` | 개인전 / 팀전 | `individual`(2~4명), `team`(4·6·8명, 2팀) | `individual` |
| `pieces` | 한 편(개인 또는 팀)이 가진 말 수 | 2, 3, 4, 5 | 4 |
| `stickModel` | 윷가락 확률 모델 | `realistic`(가락 하나가 평평한 면(배)이 위로 갈 확률 0.6), `fair`(0.5) | `realistic` |
| `backdo` | 빽도 사용 여부(표시된 가락 하나만 배가 위면 1칸 뒤로) | `true`, `false`(빽도 결과를 그냥 도로 처리) | `true` |
| `backdoFromDo` | "도" 자리(첫 칸)에 있는 말이 빽도를 받으면 | `chammeogi`(참먹이로 가서 한 바퀴 돈 것으로 침), `finish`(즉시 남) | `chammeogi` |
| `finishRule` | 날(완주) 판정 | `pass`(참먹이를 지나야 남, 참먹이에 멈추면 아직 안 남), `reach`(참먹이에 도착만 해도 남) | `pass` |
| `nakPercent` | 낙(윷가락이 판 밖으로 떨어짐) 확률. 0이면 낙 없음 | 0, 3, 5, 10 | 0 |
| `backdoCaptureBonus` | 빽도로 잡아도 한 번 더 던지기를 주는지 | `true`, `false` | `true` |
| `ranking` | 3~4명 개인전에서 1등이 나온 뒤 | `first`(1등 나오면 종료, 나머지는 진행도로 순위), `all`(꼴찌가 정해질 때까지 계속) | `first` |
| `turnSeconds` | 행동(던지기·말 고르기) 1회당 제한 시간 | 15, 30, 60, `null`(무제한, 끊김 처리만) | 30 |
옵션 검증(zod `superRefine`):
- `mode = individual`이면 인원 2~4명.
- `mode = team`이면 인원 4, 6, 8명(짝수). 좌석 순서대로 0,2,4,6번 자리가 청팀, 1,3,5,7번 자리가 홍팀. 방장이 시작 전 좌석을 바꿔 팀을 조정한다.
- 2명은 `individual`만 허용(1:1).
## 3. 구성물
- 윷판 1개: 29개 자리(바깥 20 + 대각선 안쪽 9). 정확한 그래프는 5장 참조.
- 윷가락 4개: 한쪽은 평평한 면(배), 반대쪽은 둥근 면(등). 그중 1개에 빽도 표시(X).
- 말: 편마다 `pieces`개(기본 4개), 편마다 다른 색. 팀전은 팀당 한 세트.
## 4. 준비(셋업)
1. 편 구성: 개인전은 각자 1편, 팀전은 2편(청팀·홍팀).
2. 모든 말은 "대기"(판 밖, 아직 출발 안 함) 상태로 시작.
3. 선 정하기: RNG로 첫 차례 편을 정한다(전통의 "윷 던져 높은 쪽이 선"을 대신함). 개인전 차례는 좌석 시계 방향. 팀전은 청1 → 홍1 → 청2 → 홍2 … 순으로 팀이 번갈아 가며, 팀 안에서는 좌석 순으로 돌아간다(그 팀의 다음 차례 사람이 던지고 말도 고른다).
4. RNG 상태를 State에 저장한다.
## 5. 진행 규칙
### 5.1 윷 던지기 결과
윷가락 4개를 각각 독립적으로 던진다. 각 가락이 배(평평한 면)가 위로 나올 확률을 `p`라 한다(`realistic` 0.6, `fair` 0.5).
| 결과 | 조건(배가 위인 가락 수) | 이동 | 한 번 더 | realistic(p=0.6) | fair(p=0.5) |
|---|---|---|---|---|---|
| 도 | 1개(표시 가락이 아닌 것) | 앞으로 1 | 아니오 | 0.1152 | 0.1875 |
| 빽도 | 1개이고 그것이 표시 가락 | 뒤로 1 | 아니오 | 0.0384 | 0.0625 |
| 개 | 2개 | 앞으로 2 | 아니오 | 0.3456 | 0.375 |
| 걸 | 3개 | 앞으로 3 | 아니오 | 0.3456 | 0.25 |
| 윷 | 4개 | 앞으로 4 | 예 | 0.1296 | 0.0625 |
| 모 | 0개(모두 등) | 앞으로 5 | 예 | 0.0256 | 0.0625 |
- 계산식: 배 k개일 확률 = C(4,k)·p^k·(1−p)^(4−k). 빽도 = p·(1−p)^3, 도 = 3·p·(1−p)^3.
- `backdo = false`이면 빽도 조건도 그냥 "도"로 처리한다(표시 가락 무시).
- `realistic`의 0.6은 나무위키에 소개된 실제 윷 단면 모델(배가 위로 갈 확률 약 0.61, 모 약 2.3%)을 반올림한 값이다. 실제 놀이 감각(개·걸이 많고 모가 드묾)과 맞다.
- 낙: `nakPercent > 0`이면 매 던지기마다 먼저 RNG로 낙 여부를 판정한다. 낙이면 그 던지기는 무효(결과 없음)이며, 그 던지기가 윷·모로 얻은 추가 던지기였더라도 그 전에 모아 둔 결과는 그대로 남는다. 낙 자체는 한 번 더 던지기를 주지 않는다.
### 5.2 윷판 그래프(정확한 정의)
자리 ID와 전통 명칭(명칭은 지역마다 조금 다르며 UI 표기는 선택 사항):
| ID | 명칭 | ID | 명칭 | ID | 명칭 | ID | 명칭 |
|---|---|---|---|---|---|---|---|
| 0 | 참먹이(출발·도착) | 5 | 모(모서리) | 10 | 뒷모(모서리) | 15 | 찌모(모서리) |
| 1 | 도 | 6 | 뒷도 | 11 | 찌도 | 16 | 날도 |
| 2 | 개 | 7 | 뒷개 | 12 | 찌개 | 17 | 날개 |
| 3 | 걸 | 8 | 뒷걸 | 13 | 찌걸 | 18 | 날걸 |
| 4 | 윷 | 9 | 뒷윷 | 14 | 찌윷 | 19 | 날윷 |
| 20 | 앞모도 | 21 | 앞모개 | 22 | 방(중앙) | 23 | 속윷 |
| 24 | 속모 | 25 | 뒷모도 | 26 | 뒷모개 | 27 | 사려 |
| 28 | 안찌 | | | | | | |
배치(말은 반시계 방향으로 돈다): 참먹이(0)는 오른쪽 아래 모서리, 모(5) 오른쪽 위, 뒷모(10) 왼쪽 위, 찌모(15) 왼쪽 아래.
- 대각선 1 (모 → 찌모): 5 → 20 → 21 → 22(방) → 23 → 24 → 15
- 대각선 2 (뒷모 → 참먹이): 10 → 25 → 26 → 22(방) → 27 → 28 → 0
한 칸 전진 함수 `next(node, ctx)` (ctx = 이번 이동의 출발 자리인지, 직전에 지난 자리):
| 현재 자리 | 이번 이동을 여기서 "출발"하는 경우 | 이동 중 "지나가는" 경우 |
|---|---|---|
| 대기(HOME) | 1 | - |
| 1~4, 6~9, 11~14, 16~18 | n+1 | n+1 |
| 19 | 0 | 0 |
| 5 (모) | 20 (지름길) | 6 |
| 10 (뒷모) | 25 (지름길) | 11 |
| 15 (찌모) | 16 | 16 (지름길 없음) |
| 20 → 21, 21 → 22 | 동일 | 동일 |
| 22 (방) | 27 (참먹이 쪽으로 꺾음) | 직전이 21이면 23(직진), 직전이 26이면 27(직진) |
| 23 → 24, 24 → 15 | 동일 | 동일 |
| 25 → 26, 26 → 22 | 동일 | 동일 |
| 27 → 28, 28 → 0 | 동일 | 동일 |
| 0 (참먹이, 한 바퀴 돈 말) | GOAL | GOAL(`pass`) |
- 핵심: 모서리(모·뒷모)와 방은 그 자리에 "멈춰 있다가" 다음 이동을 시작할 때만 꺾는다. 지나가는 중이면 직진한다.
- 방에 멈췄다가 출발하면 항상 참먹이 방향(27)으로 간다(가장 빠른 길).
- 대기 상태의 말이 처음 들어올 때는 0을 지나는 것이 아니라 1부터 센다(도 = 1, 모 = 5).
### 5.3 차례 진행
1. 던지기 단계: 현재 차례 사람이 `THROW`. 결과를 그 차례의 "모아 둔 결과" 목록(`pending`)에 넣는다.
- 윷 또는 모가 나오면 반드시 한 번 더 던진다(던지기 단계 유지). 윷·모가 계속 나오면 계속 던진다(상한 없음).
- 도·개·걸·빽도가 나오면(또는 낙) 던지기를 멈추고 이동 단계로 간다.
2. 이동 단계: `pending`에서 결과 하나와 움직일 말(대기 말 또는 판 위의 말 무더기) 하나를 골라 `MOVE`. 순서는 자유. 모든 결과를 다 쓸 때까지 반복한다.
- 대기 말은 전진 결과(도~모)로만 판에 올릴 수 있다. 빽도로는 못 올린다.
- 어떤 결과도 둘 곳이 없으면(예: 판 위에 말이 없는데 남은 결과가 빽도뿐) 그 결과들은 자동으로 버려진다. 일부 결과만 둘 곳이 없으면, 다른 결과를 먼저 쓰게 하고 매 이동 후 다시 판정한다(예: 빽도 + 개 → 개로 말을 올린 뒤 빽도를 그 말에 쓸 수 있음).
3. 잡기 보너스: 이동이 상대 말을 잡으면 즉시 던지기 단계로 돌아가 1번 더 던진다. 남은 `pending` 결과는 그대로 유지되고 새 결과가 추가된다. 한 번 이동으로 업힌 상대 말 여러 개를 잡아도 보너스는 1번. 한 차례에 잡기를 여러 번 하면 그때마다 1번씩.
4. `pending`이 비고 추가 던지기도 없으면 차례 종료, 다음 편(팀전은 다음 팀의 다음 사람)으로.
### 5.4 이동 규칙
- 전진 n: `next()`를 n번 적용. 중간에 GOAL에 닿으면 남은 칸은 버리고 그 말(무더기)은 "남"(완주).
- 날 판정:
- `pass`(기본): 한 바퀴를 돈 말이 참먹이(0)를 "지나가야" 남. 참먹이에 정확히 멈추면 아직 판 위(잡힐 수 있음). 다음 이동에서 어떤 전진 결과든 1칸만 나가도 남.
- `reach`: 한 바퀴 돈 말이 0에 도착하는 순간 남.
- 빽도(뒤로 1): 그 말이 바로 직전에 있던 자리로 되돌아간다(말마다 지나온 자리 기록 `trail`을 유지하고 마지막 칸을 되돌림). 방(22)처럼 들어오는 길이 둘인 자리에서도 "왔던 길"로 돌아간다.
- 도 자리(1)에서 빽도(이 말의 기록이 [1]뿐): `backdoFromDo = chammeogi`면 참먹이(0)로 가고 "한 바퀴 돈 말"로 취급한다(다음 전진에서 바로 날 수 있음). `finish`면 즉시 남.
- 참먹이(0)에서 빽도: 기록상 직전 자리(19 또는 28)로. 기록이 없으면 19로.
- 빽도로 대기 말을 움직일 수는 없다.
- 업기: 자기 편 말이 있는 자리에 멈추면 무조건 업는다(합쳐서 하나의 무더기). 이후 그 무더기는 함께 움직이고, 함께 잡히고, 함께 난다. 업힌 무더기를 일부만 떼어 움직일 수 없다. 팀전에서는 팀 말끼리 업힌다.
- 잡기: 상대 편 말(무더기)이 있는 자리에 "멈추면" 그 말들을 모두 대기로 돌려보낸다(기록 초기화). 지나가는 것은 잡지 않는다. 참먹이(0)에 서 있는 말도 잡힌다. 대기·남은 말은 잡을 수 없다.
- 한 자리에 서로 다른 편 말이 함께 있을 수 없다(멈추면 반드시 잡으므로).
### 5.5 예시
- 예시 1(한 차례에 완주): 대기 말만 있는 상태에서 윷(4) → 모(5) → 걸(3)이 나와 `pending=[윷,모,걸]`. 모로 새 말을 올림 → 5(모)에 멈춤. 걸: 5에서 출발하므로 지름길 20 → 21 → 22(방)에 멈춤. 윷: 22에서 출발하므로 27 → 28 → 0 → GOAL(4번째 칸). 이 말은 남(`pass` 기준). 한 차례 만에 1개 완주.
- 예시 2(방 직진): 말이 20(앞모도)에 있고 걸(3) → 21, 22를 지나 23(속윷)에 멈춤. 방을 지나갔으므로 직진해 찌모 쪽으로 간다.
- 예시 3(빽도 + 잡기): 내 말 A가 6(뒷도), 상대 말이 5(모)에 있음. 빽도를 A에 쓰면 5로 가서 상대 말을 잡고 한 번 더 던진다(`backdoCaptureBonus=true`). A는 이제 5에 "멈춰" 있으므로 다음 이동은 지름길(20)로 들어간다.
- 예시 4(참먹이 대기): `pass` 기준, 말이 28(안찌)에서 도(1)로 0에 멈춤 → 아직 안 남. 상대가 0에 멈추면 잡힌다. 다음 차례 도 이상이면 남.
## 6. 승패와 점수 계산
- 자기 편 말 `pieces`개를 모두 낸 편이 1등. 팀전은 이긴 팀 전원이 승리.
- 개인전 3~4명:
- `ranking = first`: 1등이 나오는 즉시 종료. 나머지 순위는 (1) 난 말 수 많은 순, (2) "남은 거리 합"이 작은 순, (3) 그래도 같으면 공동 순위.
- `ranking = all`: 1등이 나온 편은 빠지고 남은 편끼리 계속. 마지막 1편이 남으면 그 편이 꼴찌로 확정되며 종료.
- 남은 거리(말 1개 기준, `pass` 규칙에서 GOAL까지 최소 칸 수 = 0까지의 거리 + 1): 대기 12, 1→11, 2→10, 3→9, 4→8, 5→7, 6→11, 7→10, 8→9, 9→8, 10→7, 11→10, 12→9, 13→8, 14→7, 15→6, 16→5, 17→4, 18→3, 19→2, 0(참먹이)→1, 20→6, 21→5, 22→4, 23→8, 24→7, 25→6, 26→5, 27→3, 28→2. 업힌 무더기는 말 수만큼 곱한다. (`reach` 규칙이면 전부 1씩 뺀다.) 이 표는 순위 판정 전용이며 이동 규칙에는 쓰이지 않는다.
- 점수(옵션 없이 고정): 결과 화면에는 "난 말 수"와 순위만 표시한다.
## 7. 지역 룰 / 하우스 룰 (옵션으로 켜고 끄기)
- 빽도 사용 여부(`backdo`), 도 자리 빽도 처리(`backdoFromDo`), 날 판정(`finishRule`), 낙(`nakPercent`), 빽도 잡기 보너스(`backdoCaptureBonus`), 순위 방식(`ranking`): 2장 표 참조.
- 말 수 2~5개(`pieces`): 전통적으로 3~5개, 흔히 4개.
- 지원하지 않는 변형(문서화만): 자리마다 벌칙이 있는 "함정 윷판", 방에 멈추면 어느 방향이든 고를 수 있는 규칙(우리는 항상 참먹이 쪽), 업기를 거부하는 규칙, 빽도를 다음 차례까지 저장하는 규칙. 필요하면 후속 옵션으로 추가한다.
## 8. 엔진 설계
### 8.1 상태(State)
```ts
type NodeId = number; // 0..28, 5.2 표
type SideId = number; // 개인전: 좌석 인덱스, 팀전: 0=청 1=홍
type Throw = 'do' | 'gae' | 'geol' | 'yut' | 'mo' | 'backdo';
interface Piece {
id: number; // 편 내 0..pieces-1
status: 'home' | 'board' | 'done';
node: NodeId | null; // board일 때만
lapped: boolean; // 참먹이(0)에 서 있을 때 "한 바퀴 돈" 말인지 (0에 있는 board 말은 항상 true)
trail: NodeId[]; // 판에 오른 뒤 지나온 자리(빽도용). 업히면 무더기 대표의 trail을 공유 복사
}
interface YutState {
options: YutOptions;
rng: RngState;
players: PlayerId[]; // 좌석 순
sideOf: Record<PlayerId, SideId>;
sides: { id: SideId; members: PlayerId[]; pieces: Piece[]; finishedRank: number | null }[];
turnOrder: PlayerId[]; // 개인전: 좌석 순, 팀전: 청1,홍1,청2,홍2...
turnIndex: number; // turnOrder에서 현재 차례
phase: 'throw' | 'move' | 'finished';
throwsOwed: number; // 앞으로 더 던져야 하는 횟수(윷/모/잡기). throw 단계 진입 시 1
pending: Throw[]; // 모아 둔 결과
lastThrow: { sticks: [boolean, boolean, boolean, boolean] | null; result: Throw | 'nak' } | null;
turnSeq: number; // 행동마다 +1 (타이머·중복 요청 방지)
deadlineAt: number | null;
log: GameEvent[]; // 최근 이벤트(최대 N개)
}
```
- 같은 노드의 같은 편 말들이 곧 "무더기"다. 별도 스택 객체를 두지 않고 노드로 묶는다(불변식: 한 노드에는 한 편만).
### 8.2 액션
| type | payload | 누가 / 언제 | 검증 조건 |
|---|---|---|---|
| `THROW` | 없음 | 현재 차례 사람, `phase='throw'` | `throwsOwed > 0`. 위반 시 "지금은 윷을 던질 차례가 아니에요." |
| `MOVE` | `{ throwIndex: number; from: NodeId \| 'home' }` | 현재 차례 사람, `phase='move'` | `pending[throwIndex]` 존재. `from='home'`이면 자기 편 대기 말이 있고 결과가 빽도가 아님. `from`이 노드면 자기 편 말이 그 노드에 있음. 빽도는 판 위 말에만. 위반 시 "그 말은 움직일 수 없어요." 등 |
| `CONCEDE` | 없음 | 아무 참가자, 언제나 | 개인전: 그 편 탈락(남은 편이 1이면 종료). 팀전: 팀원 전원이 기권해야 팀 패배(또는 방장 설정) |
- 팀전에서는 현재 차례인 팀원만 행동할 수 있다(같은 팀 다른 사람은 채팅·추천만).
- `apply` 이벤트: `thrown{sticks,result}`, `nak`, `moved{side,from,to,path,count}`, `stacked`, `captured{victimSide,count}`, `finishedPieces{count}`, `discarded{throws}`, `turnChanged`, `sideFinished{rank}`, `gameOver`.
### 8.3 공개/비공개 정보 (view)
- 윷놀이에는 숨겨진 정보가 없다. 모든 참가자·관전자에게 같은 View를 준다: 말 위치, `pending`, 직전 던지기의 가락 4개 앞뒷면, 차례, 남은 시간.
- 단, `rng` 상태(시드·내부 카운터)는 절대 View에 넣지 않는다(다음 던지기 예측 가능해짐).
- 현재 차례 사람에게만 `legalMoves: { throwIndex, from, to, captures, finishes }[]`를 추가로 계산해 넣는다(하이라이트용). 다른 사람에게도 넣어도 무방하지만 페이로드 절약을 위해 차례인 사람만.
### 8.4 랜덤 요소 (시드 RNG 사용 지점)
- 첫 차례 편 결정(setup 1회).
- `THROW`마다: (1) `nakPercent > 0`이면 `rng() < nakPercent/100`로 낙 판정, (2) 가락 4개 각각 `rng() < p`로 배/등 결정(인덱스 0이 빽도 표시 가락). 결과는 5.1 표로 매핑. 가락 단위로 뽑아야 애니메이션(어떤 가락이 뒤집혔는지)과 확률이 일치한다.
### 8.5 타이머·시간 초과·연결 끊김 시 자동 행동
- `deadline`: 행동이 필요할 때마다 `now + turnSeconds`. 끊긴 사람은 차례가 오면 5초 유예 후 자동 행동.
- `onTimeout`:
- `phase='throw'` → `THROW`.
- `phase='move'` → 결정적 우선순위로 하나 선택: (1) 상대를 잡는 수(잡히는 말 수 많은 것 우선), (2) 말을 내는 수, (3) 자기 말 업기, (4) 가장 뒤처진(남은 거리 큰) 말을 가장 큰 결과로 전진, (5) 대기 말 올리기. 동률이면 `throwIndex` 작은 것, `from` 노드 번호 작은 것('home' 마지막).
- 같은 사람이 3회 연속 시간 초과되면 "자리 비움" 표시(자동 진행은 계속).
### 8.6 종료 조건과 결과(GameResult)
- 어떤 편의 말이 모두 `done`이 되면 그 편 순위 확정. 6장 규칙에 따라 종료.
```ts
interface YutResult {
ranking: { rank: number; players: PlayerId[]; side: SideId; finishedPieces: number; remainingDistance: number }[];
winners: PlayerId[];
summary: string; // 예: "청팀 승리! (4개 모두 남)"
stats: { throws: Record<Throw | 'nak', number>; captures: Record<SideId, number> };
}
```
## 9. UI/UX
- 모바일 세로: 위쪽 2/3에 정사각형 윷판(자리 원 지름 48px 이상, 모서리·방은 더 크게), 아래에 "모아 둔 결과" 칩(도·개·걸·윷·모·빽도, 각 64px 이상)과 큰 "윷 던지기" 버튼, 맨 아래 대기 말·난 말 표시. PC: 윷판 왼쪽, 정보 패널 오른쪽.
- 조작: 결과 칩을 먼저 누르고 → 움직일 수 있는 말이 반짝이고 → 말을 누르면 도착 자리가 표시되고 → 다시 누르면 확정(오조작 방지 2단계). 결과가 1개이고 움직일 말이 1가지면 "자동 이동" 버튼 강조.
- 합법 수 하이라이트: 도착 자리에 점선 경로 미리보기. 잡을 수 있으면 빨간 테두리 "잡기!", 업기면 "업기", 나면 "날!" 배지.
- 애니메이션: 윷가락 4개가 공중에서 돌다 떨어지는 0.8초 애니메이션(서버 결과를 받은 뒤 재생, 결과 조작 불가). 말은 칸마다 통통 튀며 이동. 잡힌 말은 대기 자리로 날아감.
- 초보자 도움말: "규칙 보기"에 그림으로 도~모, 지름길(모서리·방에 멈추면 꺾임), 업기, 잡기 설명. 힌트 옵션을 켜면 추천 수(8.5 우선순위)를 연하게 표시.
- 접근성: 결과 칩에 글자(도/개/걸/윷/모/빽도)와 이동 칸 수(1~5, −1)를 함께. 색약 대비로 편마다 모양(원·세모·네모·별) 구분.
## 10. 테스트 체크리스트
- [ ] 확률: 시드 고정 10만 회 던지기에서 realistic/fair 각 결과 빈도가 5.1 표 값 ±0.5%p 이내.
- [ ] 윷 → 모 → 걸: 던지기 단계가 걸에서 멈추고 `pending=[yut,mo,geol]`, 순서 자유롭게 사용 가능(예시 1 그대로 재현해 1개 완주).
- [ ] 모(5)를 지나가는 이동(4에서 걸 → 7)은 지름길로 안 꺾인다. 5에 멈췄다 출발하면 20으로 꺾인다.
- [ ] 방(22): 21에서 지나가면 23, 26에서 지나가면 27, 22에서 출발하면 27.
- [ ] 찌모(15)에 멈췄다 출발해도 16으로(지름길 없음).
- [ ] `pass`: 28에서 도 → 0에 멈춤, 미완주, 상대가 0에 오면 잡힘. 다음 도 → 남. `reach`: 28에서 도 → 즉시 남.
- [ ] 빽도는 왔던 길로: 21→22로 온 말이 빽도 → 21. 26→22로 온 말이 빽도 → 26. 빽도로 21에 간 말이 다음에 개 → 22를 지나 23(직진).
- [ ] 도 자리 빽도: `chammeogi`면 0으로 가고 다음 개 → 남. `finish`면 즉시 남.
- [ ] 판 위 말 없음 + `pending=[backdo]` → 결과 버려지고 차례 넘어감. `pending=[backdo, gae]` → 개로 말을 올린 뒤 빽도 사용 가능.
- [ ] 업기: 같은 편 말 위에 멈추면 합쳐지고, 이후 한 번에 2개가 같이 이동·같이 남. 업힌 2개가 잡히면 둘 다 대기로, 잡은 쪽 보너스는 1번.
- [ ] 잡기 보너스: 잡은 즉시 `phase='throw'`, 남은 `pending` 유지. 보너스 던지기에서 윷이 나오면 또 던짐.
- [ ] 지나가는 칸의 상대 말은 잡히지 않는다.
- [ ] 낙 10%: 낙이 나오면 결과 없음, 그 전에 모아 둔 윷 결과는 유지되어 이동 단계로 간다.
- [ ] 팀전 4명: 차례 순서 청1→홍1→청2→홍2, 홍1 차례에 청2가 보낸 `MOVE`는 "지금은 다른 사람 차례예요."로 거부.
- [ ] 개인전 3명 `ranking=all`: 1등 편이 빠진 뒤 차례 순서에서 제외되고 끝까지 진행.
- [ ] 시간 초과: throw 단계 → 자동 던지기. move 단계에서 잡기 가능한 수가 있으면 그것을 고른다.
- [ ] 연결 끊김: 끊긴 사람 차례에 유예 후 자동 진행, 재접속하면 즉시 현재 View 수신.
- [ ] 정보 유출: View JSON에 `rng`, 시드, 다음 결과가 없음(직렬화 스냅샷 테스트).
- [ ] 결정성: 같은 시드 + 같은 액션 열 → 같은 최종 State(리플레이 테스트).
- [ ] 옵션 검증: 개인전 5명, 팀전 5명 거부.
## 11. 참고 자료
- 나무위키 "윷놀이": https://namu.wiki/w/윷놀이 (가락 확률 모델, 빽도·낙·업기·잡기·지름길·참먹이 규칙과 지역 차이)
- 한국민족문화대백과사전 "윷놀이": https://encykorea.aks.ac.kr/Article/E0042794
- 영남일보 "윷놀이판에 담긴 한국어 문화"(윷판 29개 자리 명칭): https://www.yeongnam.com/web/view.php?key=20200129010004667
- 모아모앙 윷놀이 규칙 정리(대각선 자리 명칭, fair 확률): https://moamoang.co.kr/yut-nori/
- Wikipedia "Yunnori": https://en.wikipedia.org/wiki/Yunnori
## 12. 메모 (상표·법적 주의 등)
- 윷놀이는 전통 민속놀이로 상표·저작권 문제가 없다. 윷판 그림·말 디자인만 자체 제작하면 된다.
- 내기(돈 걸기) 기능은 넣지 않는다. 점수·포인트도 방 안에서만 쓰이는 표시용이다.
- 지역마다 규칙이 달라 사용자 항의가 잦을 수 있으므로, 방 만들기 화면에 현재 적용 규칙 요약(빽도 사용, 날 판정 방식 등)을 한 줄로 보여 준다.