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

19 KiB
Raw Permalink Blame History

달무티 (dalmuti)

마일스톤: M7 · 인원: 최소 4 ~ 최대 8명 · 예상 시간: 한 라운드 약 10분 (기본 3라운드 약 30분) · 난이도: 쉬움

1. 개요

  • 신분 계급이 있는 버리기형 카드 게임이다. 숫자가 낮을수록 좋은 카드다. 앞사람과 같은 장수를 더 낮은 숫자로 내고, 손패를 먼저 다 털수록 다음 라운드에서 높은 신분이 된다. 높은 신분은 세금으로 낮은 신분의 좋은 카드를 받는다.
  • 원작은 리처드 가필드의 "The Great Dalmuti"(1995)다. 한국에서는 "위대한 달무티"로 학교와 모임 파티 게임으로 많이 알려졌다. 같은 계열 민속 게임으로 "대통령(President)", "대부호(大富豪)"가 있다.
  • 인원 근거: 원작 박스 기준 48명. 나무위키도 48명(57명 권장)이다. 23명이나 9명 이상은 비공식이라 지원하지 않는다.

2. 모드와 옵션

옵션 키 설명 선택지 기본값
rounds 라운드 수 1 / 3 / 5 / 7 3
scoring 최종 순위 방식 rank-points(라운드별 순위 점수 합) / last-round(마지막 라운드 순위) rank-points
firstRanking 첫 라운드 신분 정하기 draw(카드 뽑기, 원작) / random draw
revolution 혁명 규칙 사용 on / off on
jesterTaxable 세금 낼 때 광대도 "좋은 카드"로 내야 하나 on / off off
passLocksOut 한 번 패스하면 그 트릭에서 다시 못 냄(하우스 룰) on / off(원작) off
removeHighCards 9~12 카드를 빼고 하기(손패가 많을 때, 나무위키 언급) on / off off
turnSeconds 턴 제한 시간 15 / 20 / 30 / 45 20

3. 구성물

  • 숫자 카드 78장: 숫자 n인 카드가 n장이다(1이 1장, 2가 2장, …, 12가 12장).
  • 광대(Jester) 2장. 단독으로 내면 13이고, 다른 카드와 함께 내면 그 숫자로 변한다(와일드).
  • 합계 80장. removeHighCards = on이면 9~12(42장)를 빼고 38장으로 한다.
  • 카드 이름(원작 계급 이름, 화면 표시용)
숫자 이름 장수
1 달무티 1
2 대주교 2
3 시종장 3
4 남작부인 4
5 수녀원장 5
6 기사 6
7 재봉사 7
8 석공 8
9 요리사 9
10 양치기 10
11 광부 11
12 농노 12
13(광대) 광대 2
  • 플레이어 신분(라운드 순위)
    • 1위: 대달무티
    • 2위: 소달무티
    • 3위 ~ N-2위: 평민
    • N-1위: 소농노
    • N위: 대농노
    • 4인이면 평민이 없다.

4. 준비(셋업)

  1. 첫 라운드 신분(firstRanking = draw): 섞은 덱에서 각자 1장씩 뽑는다. 숫자가 낮을수록 높은 신분이고, 광대는 13으로 본다. 같은 숫자끼리는 그 사람들만 다시 뽑아 순서를 정한다. 엔진은 RNG로 같은 절차를 시뮬레이션하고 결과를 애니메이션으로 보여 준다.
  2. 자리와 순서: 신분 순서대로 시계 방향으로 앉는다(대달무티 → 소달무티 → 평민들 → 소농노 → 대농노 → 다시 대달무티). 턴 순서도 이 순서다.
  3. 분배: 덱을 섞어 대달무티부터 신분 순서대로 1장씩 전부 나눠 준다. 나누어떨어지지 않으면 앞 신분이 1장씩 더 받는다.
인원 손패(80장)
4 20 × 4
5 16 × 5
6 14, 14, 13, 13, 13, 13
7 12, 12, 12, 11, 11, 11, 11
8 10 × 8
  1. 혁명 확인 단계(revolution = on): 모든 플레이어가 손패를 확인하고 "확인"을 누른다. 광대 2장을 모두 가진 사람에게만 "혁명 선언" 버튼이 함께 보인다. 모든 사람이 같은 단계를 거치므로 단계가 생긴 것만으로 광대 소유자가 드러나지 않는다.
    • 혁명: 광대 2장을 가진 사람(대농노 제외)이 선언하면 광대 2장을 공개한다. 이번 라운드는 세금을 걷지 않고, 신분은 그대로다.
    • 대혁명: 대농노가 광대 2장을 가지고 선언하면 신분 순서가 완전히 뒤집힌다. 대농노가 대달무티, 대달무티가 대농노가 되고 평민 순서도 뒤집힌다. 자리와 턴 순서도 뒤집히고 세금은 없다.
    • 선언은 선택이다. 선언하지 않으면 평소대로 세금을 걷는다.
  2. 세금(혁명이 없을 때)
    • 대농노는 손패에서 가장 좋은(숫자가 가장 낮은) 카드 2장을 대달무티에게 준다. 소농노는 가장 좋은 카드 1장을 소달무티에게 준다. 선택권이 없으므로 서버가 자동으로 옮긴다.
    • jesterTaxable = off(기본)이면 광대는 "가장 좋은 카드" 계산에서 빼고 숫자 카드 중 가장 낮은 것을 준다. 같은 숫자가 여럿이면 어느 것을 줘도 같으므로 카드 ID 순으로 정한다.
    • 받은 대달무티는 아무 카드 2장(방금 받은 카드 포함 가능), 소달무티는 아무 카드 1장을 골라 돌려준다. 두 사람은 동시에 고른다.
    • 4인이면 소달무티 ↔ 소농노, 대달무티 ↔ 대농노만 있다.
  3. 첫 트릭은 대달무티가 시작한다(대혁명이면 새 대달무티).

5. 진행 규칙

5.1 트릭 진행

  • 선(리드): 트릭을 시작하는 사람은 같은 숫자 카드를 원하는 장수(1장 이상)만큼 낸다. 광대를 섞어도 된다.
  • 따라 내기: 다음 사람부터 순서대로, 바로 앞에 나온 묶음과 같은 장수이면서 숫자가 더 낮은(더 좋은) 묶음을 내거나 패스한다.
    • 예: 9 세 장이 나왔으면 1~8 중 같은 숫자 세 장만 낼 수 있다(1은 1장뿐이라 광대를 섞어야 3장이 된다).
  • 광대
    • 광대만 내면 1장당 13이다. 광대 1장은 13 한 장, 광대 2장은 13 두 장이다.
    • 숫자 카드와 함께 내면 그 숫자로 간주한다. 예: 4 + 광대 = 4 두 장, 1 + 광대 = 1 두 장(원작에 1과 광대를 같이 내는 것을 막는 문구가 없다).
    • 한 묶음에 숫자 카드가 섞일 때 숫자 카드는 모두 같은 숫자여야 한다.
  • 패스: 낼 수 있어도 언제든 패스할 수 있다. 원작 기본(passLocksOut = off)에서는 패스해도 같은 트릭에서 차례가 다시 오면 낼 수 있다.
  • 트릭 종료: 마지막으로 낸 사람을 뺀, 아직 카드가 있는 나머지 모두가 연속으로 패스하면 트릭이 끝난다. 바닥 카드를 치우고 마지막으로 낸 사람이 새 트릭을 시작한다.
    • 마지막으로 낸 사람이 그 묶음으로 손패를 다 털었으면, 턴 순서상 그 사람의 다음이면서 아직 카드가 있는 사람이 새 트릭을 시작한다.
    • passLocksOut = on이면 패스한 사람은 그 트릭의 나머지 차례가 자동으로 건너뛰어진다. 트릭 종료 조건은 같다.

5.2 손패를 다 턴 경우와 라운드 종료

  • 손패를 모두 낸 순서대로 다음 라운드 신분이 정해진다. 처음 턴 사람이 다음 대달무티, 두 번째가 소달무티, …
  • 카드가 남은 사람이 1명이 되면 라운드가 끝나고 그 사람이 다음 대농노다. 그 사람의 남은 카드는 따로 내지 않는다.
  • 다음 라운드는 새 신분으로 자리를 다시 잡고 4번 셋업 3단계(분배)부터 반복한다.

5.3 예시(5인)

  • 대달무티 A가 7 두 장을 낸다. B는 5 두 장, C는 패스, D는 2 + 광대(2 두 장), E는 패스, A는 패스(1은 1장뿐이고 광대가 없음), B는 패스, C는 패스.
  • D를 뺀 모두가 연속으로 패스했으므로 트릭이 끝나고 D가 새 트릭을 시작한다.

6. 승패와 점수 계산

  • 원작에는 정해진 승리 조건이 없다(합의할 때까지 계속). 이 사이트에서는 rounds만큼 하고 끝낸다.
  • rank-points(기본): 각 라운드에서 순위 r(1~N)인 사람이 N - r점을 받는다(1위 N-1점, 꼴찌 0점).
    • 예(5인): 1위 4, 2위 3, 3위 2, 4위 1, 5위 0점.
    • 최종 순위는 총점이 높은 순이고, 같으면 마지막 라운드 순위가 높은 사람이 앞선다.
  • last-round: 마지막 라운드 순위가 최종 순위다.
  • 점수는 방 안 결과 표시용이다. 칩이나 돈이 아니다.

7. 지역 룰 / 하우스 룰 (옵션으로 켜고 끄기)

  • 혁명 끄기(revolution).
  • 세금에 광대 포함(jesterTaxable). 일부 모임은 광대도 좋은 카드로 내야 한다고 한다.
  • 한 번 패스하면 트릭에서 빠짐(passLocksOut). 대부호/대통령 계열에서 흔한 규칙이다.
  • 9~12 빼기(removeHighCards). 인원이 적어 손패가 많을 때 쓴다.
  • 지원하지 않는 변형(문서화만): 혁명 시 숫자 강약 반전(대부호식 "혁명"), 8 끊기, 같은 숫자 연속 금지(시바리), 낙오(도시락) 규칙.

8. 엔진 설계

8.1 상태(State)

interface DCard { id: string; n: number } // 1..12, 광대는 13
type Play = { cardIds: string[]; count: number; rank: number }; // rank: 광대 섞이면 숫자 카드의 숫자, 광대만이면 13

interface DalmutiState {
  options: DalmutiOptions;
  rng: RngState;
  players: PlayerId[];                  // 참가자(불변)
  order: PlayerId[];                    // 이번 라운드 신분 순서 = 턴 순서. [0]=대달무티
  round: number;                        // 1부터
  hands: Record<PlayerId, DCard[]>;     // 비공개
  phase:
    | { kind: 'firstRankDraw'; draws: Record<PlayerId, number[]> } // 연출용, 즉시 다음 단계
    | { kind: 'revolutionCheck'; confirmed: Record<PlayerId, boolean> }
    | { kind: 'taxReturn'; owed: Record<PlayerId, { to: PlayerId; count: number; done: boolean }> }
    | { kind: 'playing' }
    | { kind: 'roundOver' }
    | { kind: 'finished' };
  revolution: 'none' | 'revolution' | 'great' ;
  taxLog: { from: PlayerId; to: PlayerId; cardIds: string[] }[]; // 당사자만 열람
  trick: {
    leader: PlayerId;                   // 이 트릭을 시작한 사람
    top: (Play & { by: PlayerId }) | null; // 바닥 맨 위 묶음
    passesSinceTop: PlayerId[];         // top 이후 패스한 사람
    lockedOut: PlayerId[];              // passLocksOut용
    pile: Play[];                       // 이 트릭에 나온 묶음(공개)
  };
  current: PlayerId;
  finishOrder: PlayerId[];              // 이번 라운드 나간 순서
  roundResults: { order: PlayerId[]; revolution: string }[];
  totals: Record<PlayerId, number>;
  turnStartedAt: number;
  seq: number;
}

8.2 액션

type payload 누가 / 언제 검증 조건
confirmHand { declareRevolution?: boolean } 모든 플레이어, phase=revolutionCheck 한 번만. declareRevolution=true는 광대 2장을 모두 가진 사람만(아니면 "광대 2장이 있어야 혁명할 수 있어요")
returnTax { cardIds: string[] } 대달무티(2장), 소달무티(1장), phase=taxReturn 자기 손에 있는 카드이고 장수가 정확함
play { cardIds: string[] } current, phase=playing 카드가 자기 손에 있음. 숫자 카드(광대 제외)는 모두 같은 숫자. 리드면 1장 이상. 따라 내기면 장수 = top.count이고 rank < top.rank. passLocksOut on이면 lockedOut이 아님
pass {} current, phase=playing 리드가 아닐 때만(리드는 반드시 내야 함)
  • revolutionCheck는 모두가 확인하면 끝난다.
    • 선언자가 있으면 혁명 또는 대혁명을 적용하고 playing으로 간다.
    • 없으면 세금을 자동으로 옮기고 taxReturn으로 간다. 두 사람이 모두 돌려주면 playing.
  • play 처리: 묶음의 rank를 정하고 top을 바꾼 뒤 passesSinceTop = []. 손패가 0이 되면 finishOrder에 넣는다. 남은 사람이 1명이면 roundOver.
  • 다음 차례 계산: 턴 순서에서 다음 사람 중 카드가 있는(그리고 lockedOut이 아닌) 사람.
  • 트릭 종료 판정: (카드가 남은 사람 중 top.by가 아닌 모두)가 passesSinceTop에 있으면 종료한다. top.by가 이미 나간 경우에는 카드가 남은 모두가 패스하면 종료한다.
  • roundOver 처리: 점수를 반영한다. 라운드가 남았으면 order = finishOrder + [남은 1명]으로 새 라운드 분배를 하고 revolutionCheck로 간다. 다 했으면 finished.
  • reason 예: "앞사람과 같은 장수(3장)로 내야 해요", "더 낮은 숫자만 낼 수 있어요", "같은 숫자끼리만 낼 수 있어요", "새로 시작할 때는 패스할 수 없어요".

8.3 공개/비공개 정보 (view)

  • 모두(관전자 포함)
    • 신분 순서, 각자 손패 장수, 현재 트릭의 모든 묶음(누가 무엇을 냈는지), 현재 차례, 패스 표시, 나간 순서.
    • 라운드와 총점, 혁명 여부. 혁명이 선언되면 선언자와 "광대 2장"이 공개된다.
  • 본인만: 자기 손패.
  • 세금: 주고받은 카드 내용은 준 사람과 받은 사람에게만 보인다. 다른 사람과 관전자는 "대농노 → 대달무티 2장" 같은 장수만 받는다.
  • revolutionCheck: 각자에게 canDeclareRevolution이 본인 view에만 있다. 다른 사람은 확인 완료 여부만 본다.
  • 절대 보내지 않는 것: 남의 손패, 덱 섞인 순서, RNG 상태.

8.4 랜덤 요소 (시드 RNG 사용 지점)

  • 첫 라운드 신분 뽑기(동점 재뽑기 포함).
  • 매 라운드 덱 섞기.
  • 그 외 없음(세금 자동 선택은 숫자 → 카드 ID 순으로 결정적).

8.5 타이머·시간 초과·연결 끊김 시 자동 행동

  • 마감: revolutionCheck 20초, taxReturn 30초, 플레이 turnSeconds.
  • onTimeout
    • revolutionCheck: confirmHand { declareRevolution: false }. 자동으로 혁명을 선언하지 않는다.
    • taxReturn: 숫자가 가장 큰(가장 나쁜) 숫자 카드부터 필요한 장수만큼 돌려준다(광대 제외).
    • playing 리드: 광대를 빼고 가장 큰 숫자 카드를 그 숫자 전부 낸다. 광대만 남았으면 광대를 모두 낸다.
    • playing 따라 내기: pass.
  • 연결이 끊겨도 탈락시키지 않는다. 위 자동 행동으로 라운드가 끝까지 진행된다. 연속 3턴 타임아웃이면 서버가 마감을 5초로 줄인다.

8.6 종료 조건과 결과(GameResult)

  • round == rounds의 라운드가 끝나면 종료한다.
interface GameResult {
  ranking: { player: PlayerId; rank: number; total: number; roundRanks: number[] }[];
  winner: PlayerId[];
  summary: string; // "3라운드 동안 지수 님이 가장 높은 신분을 지켰어요!"
}

9. UI/UX

  • 모바일 세로 화면
    • 위: 신분 순서대로 상대를 원형으로 배치한다. 각자 왕관/모자 아이콘(신분), 손패 장수, 패스 말풍선을 표시한다.
    • 가운데: 현재 트릭 바닥 묶음(마지막 묶음을 크게, 이전 묶음은 겹쳐서 작게). "지금 기준: 5 × 2장"을 큰 글자로 보여 준다.
    • 아래: 내 손패를 숫자별로 묶어서 보여 준다(같은 숫자 겹치기 + 장수 배지). 탭 영역은 48px 이상이다.
  • 조작
    • 숫자 묶음을 탭하면 필요한 장수만큼 자동으로 선택된다(따라 내기일 때 기준 장수만큼). +/-로 장수를 조절한다. 광대는 "광대 섞기" 토글로 추가한다.
    • "내기"와 "패스" 큰 버튼을 둔다.
    • 낼 수 있는 숫자 묶음만 밝게 표시하고, 나머지는 흐리게 한다. 광대를 섞어야만 낼 수 있는 묶음은 "광대 필요" 표시를 붙인다.
  • 세금 화면: 받은 카드를 강조하고 "돌려줄 카드 2장을 고르세요"를 안내한다. 기본 추천은 가장 큰 숫자 카드다.
  • 혁명 확인 화면: 모두에게 "손패 확인" 버튼이 있고, 광대 2장 보유자에게만 "혁명!" 버튼이 추가로 보인다.
  • 애니메이션: 신분 바뀔 때 자리 이동, 대혁명 때 자리가 뒤집히는 연출, 세금 카드 이동.
  • 초보 도움말: "숫자가 낮을수록 강해요", "앞사람과 같은 장수로만 낼 수 있어요"를 상단 고정 팁으로 둔다(끌 수 있음). "규칙 보기"에 카드 장수 표와 신분 그림을 넣는다.

10. 테스트 체크리스트

  • 덱: 80장(숫자 n이 n장, 광대 2장). 6인 분배가 14, 14, 13, 13, 13, 13이고, 앞 신분이 더 받는다.
  • 첫 신분 뽑기: 같은 시드면 같은 결과다. 동점자 재뽑기 후 순서가 확정된다.
  • 세금: 대농노 손패 [광대, 광대, 3, 3, 7…]이고 jesterTaxable=off이면 3, 3이 대달무티에게 간다. on이면 광대 2장이 간다.
  • 세금 돌려주기: 대달무티가 방금 받은 카드를 돌려줄 수 있다. 장수가 틀리면 거부된다.
  • 혁명: 평민이 광대 2장으로 선언하면 세금 없이 신분 그대로 시작한다. 대농노가 선언하면 순서가 완전히 뒤집히고 새 대달무티가 첫 트릭을 시작한다.
  • 혁명 정보 유출: 광대 2장 보유자가 있든 없든 revolutionCheck 단계가 똑같이 생긴다. 다른 사람 view에 canDeclareRevolution이 없다.
  • 따라 내기: 9×3 위에 8×2는 거부(장수 다름), 9×3은 거부(같은 숫자), 4 + 4 + 광대는 허용, 광대×2 + 1은 허용(1×3).
  • 광대 단독: 바닥 12×1 위에 광대 1장은 거부된다(13은 12보다 나쁨). 리드로 광대 1장은 허용(13×1)되고, 그 위에 아무 숫자 1장이 허용된다.
  • 트릭 종료: 마지막으로 낸 사람을 뺀 전원이 패스하면 그 사람이 리드한다. 마지막 묶음으로 손패를 턴 경우 다음 사람이 리드한다.
  • passLocksOut=off: 패스한 사람이 차례가 다시 오면 낼 수 있다. on이면 차례가 건너뛰어진다.
  • 라운드 종료: 1명만 남으면 끝나고 그 사람이 대농노가 된다. 다음 라운드 순서 = 나간 순서.
  • 점수: 5인 3라운드에서 순위 점수 합과 동점 처리(마지막 라운드 순위)가 맞다.
  • 시간 초과: 리드 시간 초과 때 가장 큰 숫자 묶음을 전부 낸다. 따라 내기 시간 초과는 패스다. 세금 돌려주기 시간 초과는 가장 큰 숫자 카드다.
  • 정보 유출: 관전자와 다른 플레이어 view에 남의 손패와 세금 카드 내용이 없다(장수만 있음).
  • 차례가 아닌 play, 손에 없는 카드, 중복 패킷은 거부되고 상태가 바뀌지 않는다.

11. 참고 자료

12. 메모 (상표·법적 주의 등)

  • "The Great Dalmuti"와 "달무티"는 원작사(Wizards of the Coast 등)와 국내 유통사의 상품명이다. 화면과 홍보에는 일반 이름을 쓴다. 후보: "신분 카드 게임", "왕과 농노", "계급 놀이"(사용자 선택).
  • 카드 계급 이름(대주교, 남작부인 등)은 원작 고유 표현이다. 상표 우려를 줄이려면 숫자와 일반 이름(1 왕, 2 왕비… 등)으로 바꾸는 것도 고려한다. 일러스트는 직접 만든다.
  • "농노" 같은 계급 표현은 게임 설정이므로 문제 없지만, 어린이 방에서는 "꼴찌" 대신 "다음엔 역전!" 같은 부드러운 문구를 쓴다.
  • 돈, 칩을 쓰지 않는다. 점수는 방 안 결과 표시용이다.