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:
EJClaw
2026-10-04 04:18:29 +09:00
parent b4a3c51201
commit f4d63e1544
43 changed files with 8948 additions and 0 deletions

303
docs/games/splendor.md Normal file
View File

@@ -0,0 +1,303 @@
# 보석 상인 (`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·광고 문구에 쓰지 말고 표시 이름은 "보석 상인"(후보: "보석 거상", "젬 마켓")으로 한다. 게임 규칙 자체는 저작권 보호 대상이 아니지만, 원작 카드 일러스트·귀족 초상·로고·규칙서 문장은 쓰지 않는다.
- 카드 구성 수치(비용·점수 표)는 게임 규칙의 일부인 사실 데이터로 보는 것이 일반적이나, 원작과 완전히 같은 수치를 쓰는 것이 부담되면 같은 구조(단계별 장 수, 점수 분포, 비용 합 범위)를 유지한 자체 카드 목록으로 바꿀 수 있도록 카드 테이블을 데이터 파일로 분리해 둔다. 이 결정은 프로젝트 오너 확인 필요.
- 돈이나 포인트를 걸지 않는다.