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

210 lines
18 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.
# 다빈치 코드 (`davinci-code`)
> 마일스톤: M4 · 인원: 최소 2 ~ 최대 4명 · 예상 시간: 약 10~15분 · 난이도: 쉬움~보통
## 1. 개요
- 검은색과 흰색 숫자 타일(0~11)을 작은 수부터 큰 수 순서로 세워 두고, 상대 타일의 숫자를 추리해 맞히는 게임이다. 상대의 숨은 타일을 모두 드러내면 그 상대는 탈락하고, 마지막까지 숨은 타일이 남은 사람이 이긴다.
- 한국에서는 코리아보드게임즈 판으로 널리 알려졌고, 학교 수업과 방과후 교실의 추리 게임으로 많이 쓰인다. 예능 "더 지니어스"의 "흑과 백"으로도 유명하다.
- 인원 근거: 원작 박스와 나무위키 모두 2~4명이다.
## 2. 모드와 옵션
| 옵션 키 | 설명 | 선택지 | 기본값 |
|---|---|---|---|
| `jokers` | 조커(줄 표시 타일, 검정·흰색 각 1개) 사용 | on / off | off |
| `tieOrder` | 같은 숫자일 때 색 순서 | `black-left`(검정이 왼쪽) / `white-left` | `black-left` |
| `startTiles` | 시작 타일 수 | `auto`(2·3인 4개, 4인 3개) / 3 / 4 / 5 | `auto` |
| `chooseColors` | 타일을 가져올 때 색을 고르게 하기(실물 규칙, 뒷면 색이 보임) | on / off(무작위) | on |
| `emptyPoolPenalty` | 더미가 비었을 때 틀리면 | `reveal-own`(자기 숨은 타일 1개 공개) / `none`(벌칙 없음) | `reveal-own` |
| `turnSeconds` | 추리 제한 시간 | 20 / 30 / 45 / 60 | 30 |
| `deductionHelper` | 공개 정보로 가능한 숫자 후보 표시(초보 도움) | on / off | off |
## 3. 구성물
- 검은 타일 12개(0~11), 흰 타일 12개(0~11).
- 조커 2개(검정 1, 흰색 1). 숫자 대신 "-"가 그려져 있다. `jokers = on`일 때만 쓴다.
- 합계 24개(조커 사용 시 26개).
- 타일 뒷면은 그 타일의 색이다. 그래서 숨은 타일도 색은 모두에게 보이고 숫자만 숨겨진다.
## 4. 준비(셋업)
1. 모든 타일을 뒤집어 가운데에 섞어 둔다(더미).
2. 선 플레이어를 RNG로 정하고, 진행은 시계 방향이다.
3. 시작 타일 가져오기
- 인원별 개수: 2·3인 4개, 4인 3개(`startTiles`).
- `chooseColors = on`: 각자 "검은색 몇 개, 흰색 몇 개"를 동시에 고른다(예: 검2 흰2). 고른 색의 타일 중에서 서버가 RNG로 뽑는다. 한 색이 모자라면 남은 수까지만 고를 수 있다. 선택 마감은 15초이고, 넘기면 가능한 한 반반으로 자동 선택한다.
- `chooseColors = off`: 서버가 무작위로 나눠 준다.
- 시작 타일에는 조커가 들어가지 않는다. 시작 분배는 조커를 뺀 타일에서만 하고, 조커는 분배가 끝난 뒤 더미에 섞는다.
4. 각자 받은 타일을 정렬 규칙(5.1)에 따라 자기 앞에 세운다. 서버가 자동으로 정렬한다.
## 5. 진행 규칙
### 5.1 정렬 규칙
- 자기 줄은 왼쪽에서 오른쪽으로 숫자가 커진다.
- 같은 숫자의 검정과 흰색이 함께 있으면 `tieOrder`에 따라 놓는다(기본: 검정이 왼쪽).
- 조커는 숫자가 없으므로 가져온 사람이 원하는 위치(아무 틈)에 끼운다. 한 번 놓은 뒤에는 옮길 수 없다.
- 줄의 타일 위치는 바뀌지 않고, 새 타일만 정렬 위치에 끼워진다. 상대 입장에서 위치가 추리의 중요한 단서다.
### 5.2 턴 순서
1. **가져오기**: 더미에서 타일 1개를 가져온다(`chooseColors = on`이면 색을 골라서). 이 타일을 "이번 턴 타일"이라 하고, 아직 줄에 끼우지 않고 따로 둔다. 숫자는 본인만 본다.
- 더미가 비었으면 이 단계는 건너뛴다.
2. **추리**: 아직 탈락하지 않은 상대 한 명의 숨은 타일 하나를 고르고 숫자를 말한다.
- 말할 수 있는 값: 0~11, 그리고 `jokers = on`이면 "조커".
- 그 타일의 색은 이미 보이므로 숫자만 말한다.
3. **결과**
- **맞힘**: 그 타일이 앞면으로 공개된다(자리는 그대로). 그 상대의 숨은 타일이 0개가 되면 그 상대는 즉시 탈락한다. 이어서 둘 중 하나를 고른다.
- **계속**: 2단계로 돌아가 다시 추리한다(대상과 타일은 자유).
- **멈춤**: 이번 턴 타일을 숨긴 채 정렬 위치에 끼우고 턴을 끝낸다.
- **틀림**: 이번 턴 타일을 앞면으로 공개한 채 정렬 위치에 끼우고 턴을 끝낸다.
- 더미가 비어 이번 턴 타일이 없으면 `emptyPoolPenalty = reveal-own`일 때 추리한 사람이 자기 숨은 타일 중 하나를 골라 공개한다. 그래서 자기 숨은 타일이 0개가 되면 본인이 탈락한다.
4. 마지막 상대를 탈락시키면 그 즉시 게임이 끝난다(멈춤/계속 선택 없음).
- 조커가 이번 턴 타일이면 끼울 때(멈춤 또는 틀림) 위치를 직접 고른다.
- 이미 공개된 타일은 추리 대상이 아니다. 자기 타일도 대상이 아니다.
- 이미 공개된 같은 색·같은 숫자 조합을 말하는 것처럼 논리적으로 불가능한 추리도 규칙상 허용한다(실물과 같음). UI에서만 경고한다.
### 5.3 예시
- A의 줄(왼→오): [검?][흰?][검?][흰?]. 공개된 것은 없다.
- B가 흰색 9를 가져온다. A의 두 번째 타일(흰)을 "3"이라고 말한다. 맞힘 → A의 흰3 공개.
- B가 계속을 고른다. A의 네 번째(흰)를 "10"이라고 말한다. 틀림 → B의 흰9가 앞면으로 B의 줄 정렬 위치에 들어간다. 턴 종료.
## 6. 승패와 점수 계산
- 숨은 타일이 1개라도 남은 플레이어가 1명만 남으면 그 사람이 승자다.
- 순위: 승자 1위, 그다음은 늦게 탈락한 순이다.
- 한 턴에 여러 명이 탈락할 수 있는데, 탈락 순서는 액션이 적용된 순서이므로 동시 탈락은 없다.
- 점수는 없다. 결과 화면에 각자 "맞힌 횟수 / 추리 횟수"를 통계로 보여 준다.
## 7. 지역 룰 / 하우스 룰 (옵션으로 켜고 끄기)
- 조커 사용(`jokers`). 원작 상급 규칙이고, 기본은 끔이다.
- 같은 숫자 색 순서(`tieOrder`). 나무위키에 따르면 보통 검정이 왼쪽이지만 흰색 왼쪽이나 자유로 하는 경우도 있다. "자유"는 상대가 순서를 알 수 없어 추리가 달라지므로 지원하지 않는다.
- 시작 타일 수 조정(`startTiles`). 어린이용 3개, 고수용 5개.
- 더미가 빈 뒤 틀렸을 때 벌칙(`emptyPoolPenalty`). 원작 영문 규칙은 "타일 없이 계속 진행"만 적고 있다. 국내에서는 자기 타일 1개 공개가 널리 쓰여 기본값으로 한다(오너 확인 필요).
- 2인용 변형(단서 타일을 공개로 하는 등)은 지원하지 않는다.
## 8. 엔진 설계
### 8.1 상태(State)
```ts
type TColor = 'black' | 'white';
interface DTile { id: string; color: TColor; n: number | 'joker' } // n: 0..11
interface RowTile { tileId: string; revealed: boolean }
interface DavinciState {
options: DavinciOptions;
rng: RngState;
seats: PlayerId[];
tiles: Record<string, DTile>; // 정의(불변)
pool: { black: string[]; white: string[] }; // 비공개, 색별로 섞인 더미
rows: Record<PlayerId, RowTile[]>; // 왼→오 순서. 숫자는 비공개
phase:
| { kind: 'chooseStart'; picks: Record<PlayerId, { black: number; white: number } | null> }
| { kind: 'draw' } // current가 색 선택
| { kind: 'guess' } // current가 추리
| { kind: 'afterCorrect' } // 계속 / 멈춤
| { kind: 'placeJoker'; reveal: boolean } // 조커 위치 선택
| { kind: 'revealOwn' } // 더미 빈 뒤 틀림 → 자기 타일 공개
| { kind: 'finished' };
current: PlayerId;
pending: string | null; // 이번 턴 타일(본인만 앎)
alive: Record<PlayerId, boolean>;
eliminationOrder: PlayerId[];
stats: Record<PlayerId, { guesses: number; correct: number }>;
history: { by: PlayerId; target: PlayerId; index: number; said: number | 'joker'; correct: boolean }[]; // 공개 기록
phaseStartedAt: number;
seq: number;
}
```
### 8.2 액션
| type | payload | 누가 / 언제 | 검증 조건 |
|---|---|---|---|
| `chooseStart` | `{ black: number; white: number }` | 모든 플레이어, phase=`chooseStart` | 합 = 시작 타일 수. 각 색의 남은 수 이하(먼저 제출한 사람의 선택을 빼고 계산하므로 서버 도착 순서). 한 번만 제출 |
| `draw` | `{ color?: TColor }` | `current`, phase=`draw` | `chooseColors=on`이면 color 필수이고 그 색이 남아 있어야 함. 더미가 비었으면 이 phase 자체가 생략됨 |
| `guess` | `{ target: PlayerId; index: number; value: number \| 'joker' }` | `current`, phase=`guess` | target ≠ 본인, target 생존, `rows[target][index]`가 숨김 상태, value는 0~11 정수(또는 `jokers=on`이면 'joker') |
| `continue` | `{}` | `current`, phase=`afterCorrect` | 생존한 상대가 남아 있음 |
| `stop` | `{}` | `current`, phase=`afterCorrect` | 언제나. 이번 턴 타일이 조커면 `placeJoker`로 넘어감 |
| `placeJoker` | `{ index: number }` | `current`, phase=`placeJoker` | 0 ≤ index ≤ 줄 길이 |
| `revealOwn` | `{ index: number }` | `current`, phase=`revealOwn` | 자기 줄의 숨은 타일 |
- 일반 타일을 끼울 위치는 서버가 계산한다: 숫자 오름차순, 같은 숫자면 `tieOrder`. 조커는 정렬 계산에서 건너뛰고 자기 자리를 지킨다.
- 끼울 위치 = 새 타일보다 "앞서야 하는" 비조커 타일 중 가장 오른쪽 타일의 바로 다음 인덱스. 그런 타일이 없으면 인덱스 0.
- 비조커 타일은 항상 정렬되어 있으므로, 이 위치는 "뒤에 와야 하는" 타일들보다 반드시 앞이다. 조커가 그 사이에 있으면 새 타일은 조커의 왼쪽에 들어간다.
- 실물에서는 조커 주변 배치를 본인이 고를 수 있지만, 엔진은 위 규칙으로 고정해 결정적으로 만든다.
- `reason` 예: "이미 공개된 타일이에요", "자기 타일은 고를 수 없어요", "흰색 타일이 더 이상 없어요", "0부터 11 사이 숫자를 골라 주세요".
### 8.3 공개/비공개 정보 (view)
- 모두(관전자 포함)
- 모든 줄의 타일 색과 위치, 공개 여부, 공개된 타일의 숫자.
- 더미 색별 남은 개수, 추리 기록(`history`), 생존 여부, 현재 phase, 마감 시각.
- 이번 턴 타일의 색(실물에서도 보임). 숫자는 보이지 않는다.
- 본인만: 자기 줄 숨은 타일의 숫자, 자기 이번 턴 타일의 숫자.
- 시작 선택 단계: 다른 사람의 색 선택은 제출 여부만 보인다. 결과 색 구성은 분배 후 줄에 보이므로 그때 공개된다.
- 절대 보내지 않는 것: 남의 숨은 숫자, 더미 순서, RNG 상태.
- 숨은 타일은 view에서 `{ color, revealed:false }`로만 내보낸다. 타일 ID도 보내지 않는다(ID에 숫자가 들어가면 유출). 위치 인덱스로만 가리킨다.
- `deductionHelper = on`이면 후보 계산은 클라이언트에서 view 정보(공개 숫자 + 자기 숫자 + 위치·색)만으로 한다. 서버 비공개 정보는 쓰지 않는다.
- 후보 계산: 각 숨은 타일에 대해 "같은 색의 미공개 숫자 ∖ 내 손의 같은 색 숫자" 중 좌우 공개 타일 사이 범위를 만족하는 값. 조커가 있을 수 있으면 범위 제한을 완화한다.
### 8.4 랜덤 요소 (시드 RNG 사용 지점)
- `setup`: 선 플레이어, 색별 더미 섞기. 조커는 시작 분배가 끝난 뒤 해당 색 더미의 무작위 위치에 끼운다.
- `chooseColors = off`일 때 무작위 분배.
- 그 외에는 RNG를 쓰지 않는다(가져오기는 색별 더미의 맨 앞).
### 8.5 타이머·시간 초과·연결 끊김 시 자동 행동
- 마감: `chooseStart` 15초, `draw` 10초, `guess` `turnSeconds`, `afterCorrect` 10초, `placeJoker`/`revealOwn` 10초.
- `onTimeout`
- `chooseStart`: 가능한 한 반반(홀수면 검정 하나 더).
- `draw`: 더 많이 남은 색(같으면 검정).
- `guess`: 숨은 타일이 가장 적은 생존 상대의 가장 왼쪽 숨은 타일을 고르고, 그 타일의 가능한 후보(8.3 후보 계산을 서버에서 공개 정보와 본인 정보만으로 수행) 중 가장 작은 값을 말한다. 결정적이라 재현할 수 있다.
- `afterCorrect`: `stop`.
- `placeJoker`: 맨 오른쪽.
- `revealOwn`: 가장 왼쪽 숨은 타일.
- 연결이 끊긴 플레이어도 탈락시키지 않고 위 자동 행동으로 진행한다. 연속 3턴 타임아웃이면 서버가 마감을 5초로 줄인다.
### 8.6 종료 조건과 결과(GameResult)
- 생존자가 1명이면 `finished`.
```ts
interface GameResult {
ranking: { player: PlayerId; rank: number; hiddenLeft: number; guesses: number; correct: number }[];
winner: PlayerId[];
finalRows: Record<PlayerId, { color: TColor; n: number | 'joker' }[]>; // 종료 후 전원 공개
summary: string; // "철수 님의 암호가 끝까지 풀리지 않았어요!"
}
```
- 게임이 끝난 뒤에는 모든 타일을 공개해도 된다(`finalRows`).
## 9. UI/UX
- 모바일 세로 화면
- 위: 상대 줄(2~3명). 타일은 검정·흰색 직사각형이고, 공개된 타일에는 숫자를 크게 쓴다. 각 타일 아래에 위치 번호(1, 2, 3…)를 작게 쓴다.
- 가운데: 더미(검정 n개, 흰 n개 두 무더기). 이번 턴 타일 미리보기.
- 아래: 내 줄. 내 숨은 타일 숫자는 나만 보이고, 테두리 점선으로 "상대에게 숨김"을 표시한다.
- 조작
1. 내 차례에 검정/흰 무더기 중 하나를 탭해서 가져온다.
2. 상대 타일을 탭하면 그 타일 위에 0~11 숫자 패드(3×4, 버튼 56px)와 "조커" 버튼이 뜬다.
3. 숫자를 누르고 "이 숫자!" 확인 버튼을 누른다.
4. 결과는 뒤집기 애니메이션으로 보여 준다(맞힘: 초록 반짝임 / 틀림: 내 타일이 뒤집혀 공개되는 애니메이션).
5. 맞혔으면 "한 번 더 맞히기"와 "여기서 멈추기" 큰 버튼 2개를 보여 준다. 멈출 때 위험도 안내("멈추면 가져온 타일이 숨겨진 채로 들어가요")를 띄운다.
- 합법 수 하이라이트: 고를 수 있는 상대 타일(숨김 상태)만 테두리를 강조하고, 공개된 타일은 비활성이다.
- 초보 도움말: "규칙 보기"에 정렬 규칙 그림(작은 수 왼쪽, 같은 숫자면 검정 왼쪽)을 넣는다. `deductionHelper`를 켜면 숫자 패드에서 불가능한 숫자를 흐리게 표시한다(누를 수는 있음).
- 추리 기록 패널: "민수 → 영희 3번째 타일 = 7 (틀림)" 목록을 둔다. 노년층의 기억 부담을 줄인다.
## 10. 테스트 체크리스트
- [ ] 셋업: 2·3인은 각 4개, 4인은 각 3개. 시작 타일에 조커가 없다(`jokers=on`이어도). 같은 시드면 같은 분배가 나온다.
- [ ] `chooseStart`: 3인이 모두 "흰4"를 고르면 12개로 충분해 성공한다. 4인이 모두 "검3"을 고르는데 검정 12개면 성공하고, 이후 사람은 검정 0개만 남는다. 남은 수를 넘는 선택은 거부된다.
- [ ] 정렬: [흰5, 검5, 검2]를 받으면 [검2, 검5, 흰5]가 된다(`black-left`). `white-left`이면 [검2, 흰5, 검5].
- [ ] 조커: 조커를 가져와 틀렸을 때 `placeJoker` 단계에서 고른 위치에 앞면으로 들어간다. 이후 새 숫자 타일 정렬은 조커 위치를 바꾸지 않는다.
- [ ] 맞힘 → 계속 → 틀림: 처음 맞힌 타일은 공개된 채로 남고, 이번 턴 타일은 앞면으로 내 줄에 들어간다.
- [ ] 맞힘 → 멈춤: 이번 턴 타일이 숨긴 채로 들어간다. 다른 플레이어 view에는 그 타일의 색만 있다.
- [ ] 마지막 숨은 타일을 맞히면 그 상대가 즉시 탈락한다. 2인이면 바로 게임이 끝나고 `afterCorrect` 단계가 생기지 않는다.
- [ ] 더미가 빈 뒤: `draw` 단계가 생략된다. 틀리면 `revealOwn` 단계가 되고, 자기 마지막 숨은 타일을 공개하면 본인이 탈락한다.
- [ ] 이미 공개된 타일이나 탈락자 타일, 자기 타일을 추리하려 하면 거부된다. value 12, -1, 3.5는 거부된다. `jokers=off`에서 'joker'도 거부된다.
- [ ] 시간 초과: `guess` 시간 초과 때 자동 추리가 공개 정보만으로 결정적으로 정해진다(같은 상태 → 같은 추리). 상대 숨은 숫자를 참조하지 않는지 코드 리뷰로 확인한다.
- [ ] 연결 끊김: 끊긴 플레이어 차례가 자동으로 진행되고, 게임이 멈추지 않는다.
- [ ] 정보 유출: 관전자와 상대 view JSON에 남의 숨은 숫자, 숨은 타일 ID, 이번 턴 타일 숫자가 없다. 이벤트(`tileDrawn`)도 본인 외에는 색만 있다.
- [ ] 정보 유출: 종료 후에는 `finalRows`로 전원 공개되는 것이 정상이다.
## 11. 참고 자료
- 나무위키 "다빈치 코드(보드게임)": https://namu.wiki/w/다빈치%20코드(보드게임) (2~4명, 시작 타일 수, 정렬 규칙, 시작 패에 조커 금지, 조커 위치 자유)
- UltraBoardGames "How to play Davinci Code": https://www.ultraboardgames.com/davinci-code/game-rules.php (턴 흐름, 맞힘 후 계속/멈춤, 틀림 시 공개, 조커는 상급 규칙)
- 더미 소진 후 처리: 영문 규칙 요약에는 "타일 없이 계속"만 있고, 자기 타일 공개 벌칙은 국내 통용 규칙을 따른 것이다.
## 12. 메모 (상표·법적 주의 등)
- "다빈치 코드"라는 이름은 국내 유통사가 쓰는 상품명이다(원작은 일본 게임 "Algo"/"Coda" 계열). 소설·영화 제목과도 겹친다. 상표 문제를 피하려면 표시 이름을 바꾸는 것을 권한다. 후보: "숫자 암호", "흑백 암호 추리", "비밀 숫자 맞히기"(사용자 선택). 코드 ID `davinci-code`는 내부용이다.
- 타일 디자인은 단순한 검정·흰색 직사각형으로 직접 그린다.
- 돈, 칩을 쓰지 않는다.