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

304 lines
22 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.
# 보석 상인 (`splendor`)
> 마일스톤: M7 · 인원: 최소 2 ~ 최대 4명 · 예상 시간: 약 30분 · 난이도: 보통
## 1. 개요
- 르네상스 시대 보석 상인이 되어 보석 토큰을 모으고, 그 토큰으로 개발 카드(광산·운송·상점)를 사서 영구 보석 할인(보너스)과 명성 점수를 쌓는다. 특정 보너스를 갖추면 귀족이 방문해 추가 점수를 준다. 누군가 15점 이상이 되면 그 라운드를 마치고 최고 점수가 이긴다.
- 원작은 Marc André의 "Splendor"(Space Cowboys, 2014, 한국어판 코리아보드게임즈 "스플렌더"). 한국 보드게임 카페 입문 게임 상위권으로 인지도가 높다. 이 사이트에서는 일반 명칭 "보석 상인"으로 제공한다(12장).
- 인원 근거: 원작 2~4명.
## 2. 모드와 옵션
| 옵션 키 | 설명 | 선택지 | 기본값 |
|---|---|---|---|
| `targetPoints` | 종료 조건 점수 | 15(원작), 10(짧은 판) | 15 |
| `allowFewerTokens` | 남은 색이 3종류 미만일 때 "서로 다른 색 가져오기"로 2개 또는 1개만 가져오기 허용 | `true`, `false` | `true` |
| `turnSeconds` | 한 차례 제한 시간 | 30, 60, 90, `null` | 60 |
| `showHints` | 지금 살 수 있는 카드 표시, 귀족까지 남은 보너스 표시 | `true`, `false` | `true` |
## 3. 구성물
- 보석 토큰 40개: 다이아몬드(흰, W) 7, 사파이어(파랑, U) 7, 에메랄드(초록, G) 7, 루비(빨강, R) 7, 오닉스(검정, K) 7, 황금(조커, 금) 5.
- 개발 카드 90장: 1단계 40장, 2단계 30장, 3단계 20장. 각 카드는 보너스 색 1개(영구 할인 1), 명성 점수 0~5, 비용(색별 토큰 수).
- 귀족 타일 10장: 각 3점, 요구 보너스 조건.
### 3.1 개발 카드 전체 목록
- 출처 검증: 원작 기본판 카드 구성(1/2/3단계 40/30/20장)을 공개 구현체 두 곳(bouk/splendimax의 CSV, seal256/splendor의 cards.csv)에서 받아 프로그램으로 대조해 90장 전부 일치함을 확인했다. 카드 ID는 우리가 붙인 내부 ID다(`<단계><보너스 색><번호>`, 색 코드 W·U·G·R·K).
- 표기: 비용 칸의 `·`은 0. "비용 합"은 참고용.
- 단계별 분포: 1단계 각 색 8장(0점 35장, 1점 5장), 2단계 각 색 6장(1점 10, 2점 15, 3점 5), 3단계 각 색 4장(3점 5, 4점 10, 5점 5).
#### 1단계 카드 (40장)
| ID | 보너스 | 점수 | 흰(W) | 파(U) | 초(G) | 빨(R) | 검(K) | 비용 합 |
|---|---|---|---|---|---|---|---|---|
| 1W01 | 흰 | 0 | · | 1 | 1 | 1 | 1 | 4 |
| 1W02 | 흰 | 0 | · | 1 | 2 | 1 | 1 | 5 |
| 1W03 | 흰 | 0 | · | 2 | 2 | · | 1 | 5 |
| 1W04 | 흰 | 0 | 3 | 1 | · | · | 1 | 5 |
| 1W05 | 흰 | 0 | · | · | · | 2 | 1 | 3 |
| 1W06 | 흰 | 0 | · | 2 | · | · | 2 | 4 |
| 1W07 | 흰 | 0 | · | 3 | · | · | · | 3 |
| 1W08 | 흰 | 1 | · | · | 4 | · | · | 4 |
| 1U01 | 파 | 0 | 1 | · | 1 | 1 | 1 | 4 |
| 1U02 | 파 | 0 | 1 | · | 1 | 2 | 1 | 5 |
| 1U03 | 파 | 0 | 1 | · | 2 | 2 | · | 5 |
| 1U04 | 파 | 0 | · | 1 | 3 | 1 | · | 5 |
| 1U05 | 파 | 0 | 1 | · | · | · | 2 | 3 |
| 1U06 | 파 | 0 | · | · | 2 | · | 2 | 4 |
| 1U07 | 파 | 0 | · | · | · | · | 3 | 3 |
| 1U08 | 파 | 1 | · | · | · | 4 | · | 4 |
| 1G01 | 초 | 0 | 1 | 1 | · | 1 | 1 | 4 |
| 1G02 | 초 | 0 | 1 | 1 | · | 1 | 2 | 5 |
| 1G03 | 초 | 0 | · | 1 | · | 2 | 2 | 5 |
| 1G04 | 초 | 0 | 1 | 3 | 1 | · | · | 5 |
| 1G05 | 초 | 0 | 2 | 1 | · | · | · | 3 |
| 1G06 | 초 | 0 | · | 2 | · | 2 | · | 4 |
| 1G07 | 초 | 0 | · | · | · | 3 | · | 3 |
| 1G08 | 초 | 1 | · | · | · | · | 4 | 4 |
| 1R01 | 빨 | 0 | 1 | 1 | 1 | · | 1 | 4 |
| 1R02 | 빨 | 0 | 2 | 1 | 1 | · | 1 | 5 |
| 1R03 | 빨 | 0 | 2 | · | 1 | · | 2 | 5 |
| 1R04 | 빨 | 0 | 1 | · | · | 1 | 3 | 5 |
| 1R05 | 빨 | 0 | · | 2 | 1 | · | · | 3 |
| 1R06 | 빨 | 0 | 2 | · | · | 2 | · | 4 |
| 1R07 | 빨 | 0 | 3 | · | · | · | · | 3 |
| 1R08 | 빨 | 1 | 4 | · | · | · | · | 4 |
| 1K01 | 검 | 0 | 1 | 1 | 1 | 1 | · | 4 |
| 1K02 | 검 | 0 | 1 | 2 | 1 | 1 | · | 5 |
| 1K03 | 검 | 0 | 2 | 2 | · | 1 | · | 5 |
| 1K04 | 검 | 0 | · | · | 1 | 3 | 1 | 5 |
| 1K05 | 검 | 0 | · | · | 2 | 1 | · | 3 |
| 1K06 | 검 | 0 | 2 | · | 2 | · | · | 4 |
| 1K07 | 검 | 0 | · | · | 3 | · | · | 3 |
| 1K08 | 검 | 1 | · | 4 | · | · | · | 4 |
#### 2단계 카드 (30장)
| ID | 보너스 | 점수 | 흰(W) | 파(U) | 초(G) | 빨(R) | 검(K) | 비용 합 |
|---|---|---|---|---|---|---|---|---|
| 2W01 | 흰 | 1 | · | · | 3 | 2 | 2 | 7 |
| 2W02 | 흰 | 1 | 2 | 3 | · | 3 | · | 8 |
| 2W03 | 흰 | 2 | · | · | 1 | 4 | 2 | 7 |
| 2W04 | 흰 | 2 | · | · | · | 5 | 3 | 8 |
| 2W05 | 흰 | 2 | · | · | · | 5 | · | 5 |
| 2W06 | 흰 | 3 | 6 | · | · | · | · | 6 |
| 2U01 | 파 | 1 | · | 2 | 2 | 3 | · | 7 |
| 2U02 | 파 | 1 | · | 2 | 3 | · | 3 | 8 |
| 2U03 | 파 | 2 | 5 | 3 | · | · | · | 8 |
| 2U04 | 파 | 2 | 2 | · | · | 1 | 4 | 7 |
| 2U05 | 파 | 2 | · | 5 | · | · | · | 5 |
| 2U06 | 파 | 3 | · | 6 | · | · | · | 6 |
| 2G01 | 초 | 1 | 3 | · | 2 | 3 | · | 8 |
| 2G02 | 초 | 1 | 2 | 3 | · | · | 2 | 7 |
| 2G03 | 초 | 2 | 4 | 2 | · | · | 1 | 7 |
| 2G04 | 초 | 2 | · | 5 | 3 | · | · | 8 |
| 2G05 | 초 | 2 | · | · | 5 | · | · | 5 |
| 2G06 | 초 | 3 | · | · | 6 | · | · | 6 |
| 2R01 | 빨 | 1 | 2 | · | · | 2 | 3 | 7 |
| 2R02 | 빨 | 1 | · | 3 | · | 2 | 3 | 8 |
| 2R03 | 빨 | 2 | 1 | 4 | 2 | · | · | 7 |
| 2R04 | 빨 | 2 | 3 | · | · | · | 5 | 8 |
| 2R05 | 빨 | 2 | · | · | · | · | 5 | 5 |
| 2R06 | 빨 | 3 | · | · | · | 6 | · | 6 |
| 2K01 | 검 | 1 | 3 | 2 | 2 | · | · | 7 |
| 2K02 | 검 | 1 | 3 | · | 3 | · | 2 | 8 |
| 2K03 | 검 | 2 | · | 1 | 4 | 2 | · | 7 |
| 2K04 | 검 | 2 | · | · | 5 | 3 | · | 8 |
| 2K05 | 검 | 2 | 5 | · | · | · | · | 5 |
| 2K06 | 검 | 3 | · | · | · | · | 6 | 6 |
#### 3단계 카드 (20장)
| ID | 보너스 | 점수 | 흰(W) | 파(U) | 초(G) | 빨(R) | 검(K) | 비용 합 |
|---|---|---|---|---|---|---|---|---|
| 3W01 | 흰 | 3 | · | 3 | 3 | 5 | 3 | 14 |
| 3W02 | 흰 | 4 | · | · | · | · | 7 | 7 |
| 3W03 | 흰 | 4 | 3 | · | · | 3 | 6 | 12 |
| 3W04 | 흰 | 5 | 3 | · | · | · | 7 | 10 |
| 3U01 | 파 | 3 | 3 | · | 3 | 3 | 5 | 14 |
| 3U02 | 파 | 4 | 7 | · | · | · | · | 7 |
| 3U03 | 파 | 4 | 6 | 3 | · | · | 3 | 12 |
| 3U04 | 파 | 5 | 7 | 3 | · | · | · | 10 |
| 3G01 | 초 | 3 | 5 | 3 | · | 3 | 3 | 14 |
| 3G02 | 초 | 4 | · | 7 | · | · | · | 7 |
| 3G03 | 초 | 4 | 3 | 6 | 3 | · | · | 12 |
| 3G04 | 초 | 5 | · | 7 | 3 | · | · | 10 |
| 3R01 | 빨 | 3 | 3 | 5 | 3 | · | 3 | 14 |
| 3R02 | 빨 | 4 | · | · | 7 | · | · | 7 |
| 3R03 | 빨 | 4 | · | 3 | 6 | 3 | · | 12 |
| 3R04 | 빨 | 5 | · | · | 7 | 3 | · | 10 |
| 3K01 | 검 | 3 | 3 | 3 | 5 | 3 | · | 14 |
| 3K02 | 검 | 4 | · | · | · | 7 | · | 7 |
| 3K03 | 검 | 4 | · | · | 3 | 6 | 3 | 12 |
| 3K04 | 검 | 5 | · | · | · | 7 | 3 | 10 |
### 3.2 귀족 타일 10장 (모두 3점)
| ID | 흰(W) | 파(U) | 초(G) | 빨(R) | 검(K) |
|---|---|---|---|---|---|
| N01 | · | · | 4 | 4 | · |
| N02 | · | 4 | 4 | · | · |
| N03 | 4 | 4 | · | · | · |
| N04 | 4 | · | · | · | 4 |
| N05 | · | · | · | 4 | 4 |
| N06 | · | 3 | 3 | 3 | · |
| N07 | 3 | 3 | 3 | · | · |
| N08 | 3 | 3 | · | · | 3 |
| N09 | 3 | · | · | 3 | 3 |
| N10 | · | · | 3 | 3 | 3 |
- 귀족 요구 조건은 "보너스(산 개발 카드)" 기준이며 토큰은 세지 않는다. 귀족 이름·초상은 쓰지 않고 자체 일러스트("귀족 1"~"귀족 10" 또는 창작 이름)를 쓴다.
## 4. 준비(셋업)
1. 인원별 보석 토큰(황금 제외) 색마다: 2명 4개, 3명 5개, 4명 7개. 황금은 인원과 관계없이 5개.
2. 귀족: 10장을 섞어 (인원 + 1)장 공개. 나머지는 게임에서 제외(비공개, 이후 사용 안 함).
3. 개발 카드: 단계별로 섞어 3개 더미. 각 단계에서 4장씩 공개(공개 카드 12장).
4. 차례 순서: RNG로 섞음(원작: 가장 어린 사람이 선). 선 플레이어를 기록한다(종료 판정에 필요).
## 5. 진행 규칙
- 차례마다 아래 4가지 행동 중 정확히 하나를 한다.
1. 서로 다른 색 보석 3개 가져오기: 황금이 아닌 서로 다른 3색에서 1개씩. 각 색 공급처에 1개 이상 있어야 한다. 공급처에 남은 색이 3종류 미만이고 `allowFewerTokens=true`면, 남은 색 수만큼(2개 또는 1개) 서로 다른 색으로 가져올 수 있다. 3색 이상 남아 있으면 반드시 3개.
2. 같은 색 보석 2개 가져오기: 가져가기 전 그 색이 공급처에 4개 이상 있을 때만. 황금은 불가.
3. 개발 카드 1장 예약: 손에 예약 카드가 3장 미만일 때만. 공개된 12장 중 하나 또는 1/2/3단계 더미 맨 위 카드(보지 않고)를 가져와 손에 둔다. 공급처에 황금이 있으면 1개 받는다(없어도 예약은 가능). 공개 카드를 예약했으면 같은 단계 더미에서 1장 보충(더미가 비면 빈칸 유지).
4. 개발 카드 1장 구매: 공개된 카드 또는 내 예약 카드. 색마다 지불액 = max(0, 비용 − 내 그 색 보너스). 부족분은 황금 1개당 아무 색 1개로 대체. 지불한 토큰(황금 포함)은 공급처로 돌아간다. 공개 카드를 샀으면 보충.
- 황금은 그 색 토큰이 있어도 원하는 만큼 대신 써도 된다(원작 규칙상 조커는 언제든 대체 가능). 지불 내역을 지정하지 않으면 서버가 "색 토큰 먼저, 부족분만 황금"으로 자동 계산한다.
- 보너스만으로 비용을 다 덮으면 무료로 살 수 있다.
- 토큰 한도: 차례 끝에 토큰(황금 포함)이 10개를 넘으면 10개가 되도록 원하는 토큰을 골라 반납한다. 방금 가져온 토큰을 반납해도 된다.
- 귀족 방문: 차례 끝(반납 후)에 내 보너스가 어떤 귀족의 요구 조건을 모두 만족하면 그 귀족이 온다(3점). 한 차례에 최대 1명. 여러 명 만족하면 하나를 고른다. 거부할 수 없고, 행동으로 치지 않는다. 나머지는 다음 차례 이후 다시 판정.
- 행동 불가: 위 4가지 중 어느 것도 할 수 없으면(공급처 토큰이 모두 없거나 조건 불충족, 예약 3장이며 살 수 있는 카드 없음 등) 그 차례는 넘긴다(`PASS`). 할 수 있는 행동이 하나라도 있으면 넘길 수 없다.
- 개발 카드는 산 뒤 되팔거나 버릴 수 없다. 예약 카드는 버릴 수 없다.
### 5.1 예시
- 보너스 W2 U0 G1 R0 K1, 토큰 W0 U2 G1 R0 K0 금1. 카드 2W04(비용 R5 K3)를 사려면: R 5 − 0 = 5, K 3 − 1 = 2 → 필요 7개, 가진 R·K 토큰 0, 황금 1 → 살 수 없음.
- 같은 보너스, 카드 1U05(비용 W1 K2): W 1 − 2 → 0, K 2 − 1 = 1 → K 토큰이 없으므로 황금 1개 사용. 구매 후 황금 0.
- 토큰 9개 상태에서 3개 가져오기 → 12개 → 2개 반납.
## 6. 승패와 점수 계산
- 명성 점수 = 산 개발 카드 점수 합 + 귀족 수 × 3.
- 종료: 어떤 플레이어가 차례를 마쳤을 때(귀족 방문까지 포함) `targetPoints` 이상이면, 그 라운드를 끝까지 진행해 모두가 같은 횟수의 차례를 갖게 한다(선 플레이어 바로 앞 사람의 차례까지). 그 사이 다른 사람도 점수를 더 얻을 수 있다.
- 순위: 점수 높은 순 → 동점이면 산 개발 카드 수가 적은 사람이 위(원작 규칙) → 그래도 같으면 공동 순위.
- 예: A 16점(카드 14장), B 16점(카드 12장), C 15점 → 1위 B, 2위 A, 3위 C.
## 7. 지역 룰 / 하우스 룰 (옵션으로 켜고 끄기)
- 3개 미만 가져오기(`allowFewerTokens`): 원작 규칙서는 명시하지 않지만 일반적으로 통용되는 해석(공식 디지털판도 허용). 엄격하게 하려면 끈다.
- 짧은 판(`targetPoints=10`): 어린이·입문용.
- 지원하지 않는 변형(문서화만): 확장(도시, 동방 무역로 등), 예약 카드 공개 규칙(원작에서도 공개 카드 예약은 이미 모두가 본 카드라 공개, 더미 예약만 비공개).
## 8. 엔진 설계
### 8.1 상태(State)
```ts
type Color = 'W' | 'U' | 'G' | 'R' | 'K';
type TokenColor = Color | 'gold';
type Gems = Record<Color, number>;
type Tokens = Record<TokenColor, number>;
interface DevCard { id: string; tier: 1 | 2 | 3; bonus: Color; points: number; cost: Gems }
interface Noble { id: string; points: 3; req: Gems }
interface SplendorState {
options: SplendorOptions;
rng: RngState;
order: PlayerId[]; // 차례 순서, order[0] = 선
current: number;
phase: 'action' | 'discard' | 'noble' | 'finished';
endTriggered: boolean; // 누군가 목표 점수 도달 → 라운드 끝까지
supply: Tokens;
decks: Record<1 | 2 | 3, string[]>; // 비공개, 0번 = 맨 위
board: Record<1 | 2 | 3, (string | null)[]>; // 공개 4칸, 빈칸 null
nobles: string[]; // 공개된 귀족(남은 것)
players: Record<PlayerId, {
tokens: Tokens;
cards: string[]; // 산 카드
reserved: { cardId: string; blind: boolean }[]; // blind = 더미에서 예약
nobles: string[];
}>;
pendingNobleChoices: string[] | null; // phase='noble'일 때 고를 수 있는 귀족
turnsTaken: Record<PlayerId, number>;
turnSeq: number;
deadlineAt: number | null;
}
// 카드·귀족 정의는 상수 테이블(3.1, 3.2)로 두고 State에는 ID만 저장
```
### 8.2 액션
| type | payload | 누가 / 언제 | 검증 조건 |
|---|---|---|---|
| `TAKE_DIFFERENT` | `{ colors: Color[] }` | 현재 차례, `phase='action'` | 중복 없음, 황금 아님, 각 색 공급 ≥ 1. 길이 3. 단 `allowFewerTokens`이고 공급 > 0인 색 수 n < 3이면 길이 = n. 위반: "서로 다른 색 3개를 골라 주세요." |
| `TAKE_SAME` | `{ color: Color }` | 현재 차례, `action` | 가져가기 전 `supply[color] ≥ 4`. 위반: "같은 색 2개는 4개 이상 남은 색에서만 가져올 수 있어요." |
| `RESERVE` | `{ from: { tier; slot: 0..3 } \| { tier; deck: true } }` | 현재 차례, `action` | 예약 < 3. 공개 칸에 카드 있음 / 더미에 카드 있음. 위반: "예약은 3장까지만 할 수 있어요." |
| `BUY` | `{ from: { tier; slot } \| { reservedIndex }; payment?: Tokens }` | 현재 차례, `action` | 카드 존재. `payment` 지정 시: 각 색 지불 ≤ 보유, 색 토큰 + 황금으로 필요액을 정확히 충족(초과 지불 불가). 미지정 시 자동 계산이 가능해야 함. 위반: "보석이 부족해요." |
| `PASS` | 없음 | 현재 차례, `action` | 위 네 행동이 모두 불가능할 때만. 위반: "할 수 있는 행동이 있어요." |
| `DISCARD` | `{ tokens: Tokens }` | 현재 차례, `phase='discard'` | 반납 후 합계 정확히 10, 각 반납 ≤ 보유 |
| `CHOOSE_NOBLE` | `{ nobleId }` | 현재 차례, `phase='noble'` | `pendingNobleChoices`에 포함 |
| `CONCEDE` | 없음 | 참가자 | 보유 토큰은 공급처로 반납, 예약 카드는 게임에서 제외, 산 카드·점수는 결과용으로 유지(순위는 최하위). 이후 차례에서 제외. 남은 사람이 1명이면 종료 |
- 차례 마무리 순서: 행동 → (토큰 > 10이면 `discard`) → (만족 귀족 1명이면 자동 방문, 2명 이상이면 `noble`) → 종료 판정 → 다음 차례.
- 이벤트: `tookTokens`, `reserved{tier, cardId|null(비공개)}`, `bought{cardId, paid}`, `refilled{tier, slot, cardId}`, `discarded`, `nobleVisited`, `endTriggered`, `passed`, `gameOver`.
### 8.3 공개/비공개 정보 (view)
- 모두 공개: 공급처 토큰, 공개 카드 12칸, 각 더미 남은 장 수, 공개 귀족, 각 플레이어의 토큰·산 카드(보너스)·귀족·점수, 예약 카드 수.
- 예약 카드:
- 공개 칸에서 예약한 카드(`blind=false`): 모두에게 공개(원작에서도 모두 본 카드).
- 더미에서 예약한 카드(`blind=true`): 본인에게만 내용 공개. 다른 플레이어·관전자에게는 `{ hidden: true, tier }`만. 그 카드를 사는 순간 공개(이벤트에 cardId 포함).
- 이벤트도 같은 필터를 거친다: 다른 사람에게 보내는 `reserved` 이벤트에는 블라인드 카드 ID를 넣지 않는다.
- 비공개: 더미 순서와 내용, 제외된 귀족, `rng`.
- 종료 후 결과 화면에서는 모든 예약 카드 공개.
### 8.4 랜덤 요소 (시드 RNG 사용 지점)
- 셋업: 차례 순서 섞기, 귀족 10장 섞어 (인원+1)장 선택, 단계별 더미 섞기(Fisher–Yates). 이후 게임 중 RNG 사용 없음(보충은 더미 맨 위에서).
### 8.5 타이머·시간 초과·연결 끊김 시 자동 행동
- `deadline`: 차례 시작 시 `now + turnSeconds`. `discard`/`noble` 단계는 15초 추가 부여.
- `onTimeout`(결정적):
- `action`: (1) 살 수 있는 카드 중 점수 최대 → 동점이면 지불 토큰 수 최소 → 공개 카드(1→3단계, 왼쪽 칸 우선) 다음 예약 카드 순. (2) 없으면 `TAKE_DIFFERENT`: 공급이 많은 색 3개(동률이면 W,U,G,R,K 순). (3) 불가하면 `TAKE_SAME`(공급 많은 색). (4) 불가하면 1단계 공개 카드 첫 칸 예약. (5) 모두 불가면 `PASS`.
- `discard`: 보유 수가 많은 색부터 1개씩 반납(동률이면 K,R,G,U,W 순, 황금은 마지막).
- `noble`: 후보 중 ID가 작은 것.
- 끊긴 사람: 10초 유예 후 자동 행동. 3번 연속이면 "자리 비움".
### 8.6 종료 조건과 결과(GameResult)
- `endTriggered`이고 방금 차례를 마친 사람이 `order`의 마지막(선 바로 앞)이면 종료.
```ts
interface SplendorResult {
ranking: { rank: number; player: PlayerId; points: number; cards: number; nobles: number; bonuses: Gems }[];
winners: PlayerId[];
revealedReserved: Record<PlayerId, string[]>;
summary: string; // 예: "현우 승리! 16점 (카드 12장, 귀족 2)"
}
```
## 9. UI/UX
- 모바일 세로: 맨 위 귀족 줄(작은 타일, 요구 보너스 아이콘), 그 아래 3단계 → 1단계 순 카드 3줄(줄마다 더미 + 공개 4장, 가로 폭에 맞춘 카드 최소 72px 너비), 그 아래 공급처 토큰 6개(원형 56px), 맨 아래 내 영역(보너스·토큰·예약 카드·점수). 다른 플레이어는 상단 탭/요약 칩으로, 누르면 펼침.
- 조작: 토큰을 차례로 누르면 선택 바구니에 담기고 "가져오기" 버튼 활성(같은 색 두 번 누르면 2개 가져오기로 자동 전환, 불가하면 흔들림 + 이유 표시). 카드를 누르면 "구매 / 예약" 큰 버튼 두 개가 뜨고, 구매 시 자동 지불 내역 미리보기(황금 사용 수 표시), "지불 방법 바꾸기"로 수동 조정.
- 합법 수 하이라이트: 지금 살 수 있는 카드에 초록 테두리, 황금을 써야 살 수 있으면 금색 테두리. 귀족 타일에 "남은 보너스 2" 표시.
- 애니메이션: 토큰이 공급처에서 내 영역으로 날아감, 카드 구매 시 카드가 내 보너스 줄로, 귀족 방문 시 왕관 효과.
- 초보자 도움말: 첫 판에 "보석 → 카드 → 할인 → 더 비싼 카드 → 귀족" 흐름도를 한 장으로 안내.
- 색약 대비: 보석마다 색과 모양(다이아몬드·물방울·네모·하트·원 등)과 글자(W/U/G/R/K 대신 "흰·파·초·빨·검")를 함께 표시.
## 10. 테스트 체크리스트
- [ ] 셋업: 2/3/4명에서 색별 토큰 4/5/7, 황금 5, 귀족 3/4/5장, 공개 카드 12장, 더미 36/26/16장.
- [ ] 카드 데이터: 90장, 단계별 40/30/20, 색별 8/6/4, 점수 분포가 3.1과 일치(테이블 스냅샷 테스트).
- [ ] `TAKE_SAME`: 공급 4개 → 허용, 3개 → 거부.
- [ ] `TAKE_DIFFERENT`: 같은 색 중복 거부, 황금 포함 거부. 공급에 2색만 남고 `allowFewerTokens=true` → 2개 허용, 3색 남았는데 2개 → 거부.
- [ ] 예약: 3장 보유 시 거부. 황금 0개일 때 예약 성공(황금 없이). 더미 예약 후 다른 사람 View에 카드 내용 없음.
- [ ] 구매: 5.1 예시 두 개 재현. 보너스로 비용 전부 충당 → 토큰 0개 지불. 수동 `payment`로 색 토큰이 있는데 황금을 쓰는 것 허용, 초과 지불 거부.
- [ ] 토큰 한도: 9개 + 3개 → `discard` 단계, 반납 후 정확히 10이 아니면 거부.
- [ ] 귀족: 한 차례에 2명 조건 충족 → `noble` 단계에서 1명만, 다음 차례 끝에 나머지 자동 방문(조건 여전히 충족 시).
- [ ] 종료: 2번째 플레이어가 15점 도달 → 3·4번째까지 진행 후 종료. 선 바로 앞 사람이 도달하면 즉시 종료.
- [ ] 동점: 점수 같으면 카드 수 적은 쪽 승, 그것도 같으면 공동.
- [ ] `PASS`: 할 수 있는 행동이 있으면 거부, 전혀 없을 때만 허용.
- [ ] 더미 소진: 공개 칸이 비어도 진행, 빈 더미 예약 거부.
- [ ] 시간 초과: 살 수 있는 카드가 있으면 자동 구매, 없으면 토큰 3개 가져오기.
- [ ] 연결 끊김 중 `discard`·`noble` 단계도 자동 처리되어 게임이 멈추지 않음.
- [ ] 정보 유출: 관전자·상대 View/이벤트에 블라인드 예약 카드 ID, 더미 내용, 제외 귀족, `rng`가 없음(직렬화 검사).
- [ ] 리플레이 결정성.
## 11. 참고 자료
- 원작 규칙서(영문, Space Cowboys): https://cdn.1j1ju.com/medias/7f/91/ba-splendor-rulebook.pdf
- Wikipedia "Splendor (game)": https://en.wikipedia.org/wiki/Splendor_(game)
- BoardGameGeek "Splendor": https://boardgamegeek.com/boardgame/148228/splendor
- 카드 데이터 대조용 공개 구현체: https://github.com/bouk/splendimax/blob/master/Splendor%20Cards.csv , https://github.com/seal256/splendor/blob/master/assets/cards.csv (귀족 목록: 같은 저장소 pysplendor/splendor.py의 NOBLES)
- 나무위키 "스플렌더": https://namu.wiki/w/스플렌더
## 12. 메모 (상표·법적 주의 등)
- "Splendor"와 "스플렌더"는 Space Cowboys(Asmodee)의 상표다. 화면·URL·광고 문구에 쓰지 말고 표시 이름은 "보석 상인"(후보: "보석 거상", "젬 마켓")으로 한다. 게임 규칙 자체는 저작권 보호 대상이 아니지만, 원작 카드 일러스트·귀족 초상·로고·규칙서 문장은 쓰지 않는다.
- 카드 구성 수치(비용·점수 표)는 게임 규칙의 일부인 사실 데이터로 보는 것이 일반적이나, 원작과 완전히 같은 수치를 쓰는 것이 부담되면 같은 구조(단계별 장 수, 점수 분포, 비용 합 범위)를 유지한 자체 카드 목록으로 바꿀 수 있도록 카드 테이블을 데이터 파일로 분리해 둔다. 이 결정은 프로젝트 오너 확인 필요.
- 돈이나 포인트를 걸지 않는다.