docs: 온라인 보드게임 사이트 계획서 (공통 14편 + 게임 26종)
- 개요, 조사(BGA·한국 인기 보드게임·UI/UX), 아키텍처, 실시간 프로토콜, 안정성·보안, 계정, 로비·방, UI/UX, 데이터 모델, 게임 엔진, 테스트, 배포·운영, 로드맵(M0~M9), 사용자 결정 항목 - 게임별 규칙·엔진 설계·UI·테스트 체크리스트 26종 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
209
docs/games/davinci-code.md
Normal file
209
docs/games/davinci-code.md
Normal file
@@ -0,0 +1,209 @@
|
||||
# 다빈치 코드 (`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`는 내부용이다.
|
||||
- 타일 디자인은 단순한 검정·흰색 직사각형으로 직접 그린다.
|
||||
- 돈, 칩을 쓰지 않는다.
|
||||
Reference in New Issue
Block a user