# 다빈치 코드 (`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; // 정의(불변) pool: { black: string[]; white: string[] }; // 비공개, 색별로 섞인 더미 rows: Record; // 왼→오 순서. 숫자는 비공개 phase: | { kind: 'chooseStart'; picks: Record } | { kind: 'draw' } // current가 색 선택 | { kind: 'guess' } // current가 추리 | { kind: 'afterCorrect' } // 계속 / 멈춤 | { kind: 'placeJoker'; reveal: boolean } // 조커 위치 선택 | { kind: 'revealOwn' } // 더미 빈 뒤 틀림 → 자기 타일 공개 | { kind: 'finished' }; current: PlayerId; pending: string | null; // 이번 턴 타일(본인만 앎) alive: Record; eliminationOrder: PlayerId[]; stats: Record; 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; // 종료 후 전원 공개 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`는 내부용이다. - 타일 디자인은 단순한 검정·흰색 직사각형으로 직접 그린다. - 돈, 칩을 쓰지 않는다.