- 개요, 조사(BGA·한국 인기 보드게임·UI/UX), 아키텍처, 실시간 프로토콜, 안정성·보안, 계정, 로비·방, UI/UX, 데이터 모델, 게임 엔진, 테스트, 배포·운영, 로드맵(M0~M9), 사용자 결정 항목 - 게임별 규칙·엔진 설계·UI·테스트 체크리스트 26종 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
18 KiB
18 KiB
다빈치 코드 (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개(011). - 조커 2개(검정 1, 흰색 1). 숫자 대신 "-"가 그려져 있다.
jokers = on일 때만 쓴다. - 합계 24개(조커 사용 시 26개).
- 타일 뒷면은 그 타일의 색이다. 그래서 숨은 타일도 색은 모두에게 보이고 숫자만 숨겨진다.
4. 준비(셋업)
- 모든 타일을 뒤집어 가운데에 섞어 둔다(더미).
- 선 플레이어를 RNG로 정하고, 진행은 시계 방향이다.
- 시작 타일 가져오기
- 인원별 개수: 2·3인 4개, 4인 3개(
startTiles). chooseColors = on: 각자 "검은색 몇 개, 흰색 몇 개"를 동시에 고른다(예: 검2 흰2). 고른 색의 타일 중에서 서버가 RNG로 뽑는다. 한 색이 모자라면 남은 수까지만 고를 수 있다. 선택 마감은 15초이고, 넘기면 가능한 한 반반으로 자동 선택한다.chooseColors = off: 서버가 무작위로 나눠 준다.- 시작 타일에는 조커가 들어가지 않는다. 시작 분배는 조커를 뺀 타일에서만 하고, 조커는 분배가 끝난 뒤 더미에 섞는다.
- 인원별 개수: 2·3인 4개, 4인 3개(
- 각자 받은 타일을 정렬 규칙(5.1)에 따라 자기 앞에 세운다. 서버가 자동으로 정렬한다.
5. 진행 규칙
5.1 정렬 규칙
- 자기 줄은 왼쪽에서 오른쪽으로 숫자가 커진다.
- 같은 숫자의 검정과 흰색이 함께 있으면
tieOrder에 따라 놓는다(기본: 검정이 왼쪽). - 조커는 숫자가 없으므로 가져온 사람이 원하는 위치(아무 틈)에 끼운다. 한 번 놓은 뒤에는 옮길 수 없다.
- 줄의 타일 위치는 바뀌지 않고, 새 타일만 정렬 위치에 끼워진다. 상대 입장에서 위치가 추리의 중요한 단서다.
5.2 턴 순서
- 가져오기: 더미에서 타일 1개를 가져온다(
chooseColors = on이면 색을 골라서). 이 타일을 "이번 턴 타일"이라 하고, 아직 줄에 끼우지 않고 따로 둔다. 숫자는 본인만 본다.- 더미가 비었으면 이 단계는 건너뛴다.
- 추리: 아직 탈락하지 않은 상대 한 명의 숨은 타일 하나를 고르고 숫자를 말한다.
- 말할 수 있는 값: 0~11, 그리고
jokers = on이면 "조커". - 그 타일의 색은 이미 보이므로 숫자만 말한다.
- 말할 수 있는 값: 0~11, 그리고
- 결과
- 맞힘: 그 타일이 앞면으로 공개된다(자리는 그대로). 그 상대의 숨은 타일이 0개가 되면 그 상대는 즉시 탈락한다. 이어서 둘 중 하나를 고른다.
- 계속: 2단계로 돌아가 다시 추리한다(대상과 타일은 자유).
- 멈춤: 이번 턴 타일을 숨긴 채 정렬 위치에 끼우고 턴을 끝낸다.
- 틀림: 이번 턴 타일을 앞면으로 공개한 채 정렬 위치에 끼우고 턴을 끝낸다.
- 더미가 비어 이번 턴 타일이 없으면
emptyPoolPenalty = reveal-own일 때 추리한 사람이 자기 숨은 타일 중 하나를 골라 공개한다. 그래서 자기 숨은 타일이 0개가 되면 본인이 탈락한다.
- 더미가 비어 이번 턴 타일이 없으면
- 맞힘: 그 타일이 앞면으로 공개된다(자리는 그대로). 그 상대의 숨은 타일이 0개가 되면 그 상대는 즉시 탈락한다. 이어서 둘 중 하나를 고른다.
- 마지막 상대를 탈락시키면 그 즉시 게임이 끝난다(멈춤/계속 선택 없음).
- 조커가 이번 턴 타일이면 끼울 때(멈춤 또는 틀림) 위치를 직접 고른다.
- 이미 공개된 타일은 추리 대상이 아니다. 자기 타일도 대상이 아니다.
- 이미 공개된 같은 색·같은 숫자 조합을 말하는 것처럼 논리적으로 불가능한 추리도 규칙상 허용한다(실물과 같음). 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)
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에 숫자가 들어가면 유출). 위치 인덱스로만 가리킨다.
- 숨은 타일은 view에서
deductionHelper = on이면 후보 계산은 클라이언트에서 view 정보(공개 숫자 + 자기 숫자 + 위치·색)만으로 한다. 서버 비공개 정보는 쓰지 않는다.- 후보 계산: 각 숨은 타일에 대해 "같은 색의 미공개 숫자 ∖ 내 손의 같은 색 숫자" 중 좌우 공개 타일 사이 범위를 만족하는 값. 조커가 있을 수 있으면 범위 제한을 완화한다.
8.4 랜덤 요소 (시드 RNG 사용 지점)
setup: 선 플레이어, 색별 더미 섞기. 조커는 시작 분배가 끝난 뒤 해당 색 더미의 무작위 위치에 끼운다.chooseColors = off일 때 무작위 분배.- 그 외에는 RNG를 쓰지 않는다(가져오기는 색별 더미의 맨 앞).
8.5 타이머·시간 초과·연결 끊김 시 자동 행동
- 마감:
chooseStart15초,draw10초,guessturnSeconds,afterCorrect10초,placeJoker/revealOwn10초. onTimeoutchooseStart: 가능한 한 반반(홀수면 검정 하나 더).draw: 더 많이 남은 색(같으면 검정).guess: 숨은 타일이 가장 적은 생존 상대의 가장 왼쪽 숨은 타일을 고르고, 그 타일의 가능한 후보(8.3 후보 계산을 서버에서 공개 정보와 본인 정보만으로 수행) 중 가장 작은 값을 말한다. 결정적이라 재현할 수 있다.afterCorrect:stop.placeJoker: 맨 오른쪽.revealOwn: 가장 왼쪽 숨은 타일.
- 연결이 끊긴 플레이어도 탈락시키지 않고 위 자동 행동으로 진행한다. 연속 3턴 타임아웃이면 서버가 마감을 5초로 줄인다.
8.6 종료 조건과 결과(GameResult)
- 생존자가 1명이면
finished.
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개 두 무더기). 이번 턴 타일 미리보기.
- 아래: 내 줄. 내 숨은 타일 숫자는 나만 보이고, 테두리 점선으로 "상대에게 숨김"을 표시한다.
- 조작
- 내 차례에 검정/흰 무더기 중 하나를 탭해서 가져온다.
- 상대 타일을 탭하면 그 타일 위에 0~11 숫자 패드(3×4, 버튼 56px)와 "조커" 버튼이 뜬다.
- 숫자를 누르고 "이 숫자!" 확인 버튼을 누른다.
- 결과는 뒤집기 애니메이션으로 보여 준다(맞힘: 초록 반짝임 / 틀림: 내 타일이 뒤집혀 공개되는 애니메이션).
- 맞혔으면 "한 번 더 맞히기"와 "여기서 멈추기" 큰 버튼 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는 내부용이다. - 타일 디자인은 단순한 검정·흰색 직사각형으로 직접 그린다.
- 돈, 칩을 쓰지 않는다.