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

191 lines
12 KiB
Markdown
Raw Permalink 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.
# 바퀴벌레 포커 (`cockroach-poker`)
> 마일스톤: M7 · 인원: 최소 2 ~ 최대 6명 · 예상 시간: 약 15~20분 · 난이도: 쉬움
## 1. 개요
- 해충 카드를 뒤집어 건네며 "이건 쥐야"라고 말하면, 받은 사람이 진실/거짓을 맞히거나 몰래 보고 다른 사람에게 떠넘기는 블러핑 게임. 같은 해충 4장을 앞에 모으는 사람이 진다(패배자 1명을 정하는 게임).
- 한국에서는 보드게임 카페 입문용 파티 게임으로 인기가 높고, 규칙이 단순해 어린이·어른이 함께 하기 좋다.
- 인원 근거: 원작(Drei Magier Spiele, 2004, 자크 젤리거) 박스 표기 2~6인. 2인은 원작의 별도 2인 규칙을 따른다.
## 2. 모드와 옵션
| 옵션 키 | 설명 | 선택지 | 기본값 |
|---|---|---|---|
| `loseCount` | 같은 종류가 몇 장 쌓이면 패배 | 3 / 4 / 5 | 3~6인 4, 2인 5(원작) |
| `turnSeconds` | 카드 건네기/판단 제한 시간 | 15 / 30 / 60 | 30 |
| `showSeenBy` | 현재 카드를 이미 본 사람 표시 | on 고정 | on |
| `claimHistory` | 이번 라운드의 주장 이력(누가 무엇이라 했는지) 표시 | on / off | on |
| `quickChat` | 정해진 문구 버튼("정말이야!", "의심스러운데?") | on / off | on |
## 3. 구성물
- 카드 64장: 해충 8종 × 8장.
| 종류 키 | 표시 이름 |
|---|---|
| `cockroach` | 바퀴벌레 |
| `bat` | 박쥐 |
| `fly` | 파리 |
| `toad` | 두꺼비 |
| `rat` | 쥐 |
| `scorpion` | 전갈 |
| `spider` | 거미 |
| `stinkbug` | 노린재 |
어린이 대상 서비스이므로 그림은 귀여운 캐릭터풍 자체 제작 아트를 쓴다(원작 아트 사용 금지).
## 4. 준비(셋업)
1. 64장을 RNG로 섞는다.
2. 2인: 섞은 뒤 10장을 보지 않고 제거(공개하지 않음). 남은 54장을 27장씩.
3. 3~6인: 전부 나눈다. 좌석 순(첫 플레이어부터)으로 1장씩 돌려 나누므로 앞 좌석이 1장 더 받을 수 있다.
| 인원 | 손패 장수 |
|---|---|
| 2 | 27, 27 (10장 제거) |
| 3 | 22, 21, 21 |
| 4 | 16 × 4 |
| 5 | 13, 13, 13, 13, 12 |
| 6 | 11, 11, 11, 11, 10, 10 |
4. 첫 플레이어는 RNG로 정하고, 좌석은 그 사람부터 시작하게 배치한다(첫 플레이어가 가장 많이 받음).
5. 각자 앞의 "앞마당"(공개 카드 영역)은 비어 있다.
## 5. 진행 규칙
### 5.1 라운드 시작
- 라운드 시작 플레이어(시작자)가 손패 1장을 골라 뒷면으로 다른 플레이어 1명에게 건네며 해충 종류 1개를 주장한다. 주장은 진실이어도 거짓이어도 된다.
- 시작자의 손패가 0장이면 그 즉시 시작자가 진다(6장).
### 5.2 카드를 받은 사람의 선택
받은 사람(수신자)은 둘 중 하나를 고른다.
1. **판정**: "진실이다" 또는 "거짓이다"라고 말하고 카드를 공개한다.
- 맞히면: 카드를 건넨 사람(직전 전달자)이 그 카드를 자기 앞마당에 공개해 놓는다.
- 틀리면: 수신자가 자기 앞마당에 놓는다.
2. **떠넘기기**: 카드를 몰래 확인한 뒤, 이 카드를 아직 보지 않은 다른 플레이어에게 건네며 다시 주장한다(같은 주장이든 다른 주장이든 자유).
- "본 사람" = 원래 건넨 시작자 + 이 카드를 본 모든 수신자. 이 사람들에게는 건넬 수 없다.
- 건넬 수 있는 사람이 없으면(나머지 전원이 이미 봄) 떠넘기기 불가, 반드시 판정.
- 손패가 0장인 사람에게도 건넬 수 있다(손패와 무관).
- 2인 규칙: 떠넘기기 없음. 항상 판정.
### 5.3 라운드 종료와 다음 시작자
- 앞마당에 카드를 받은 사람이 다음 라운드 시작자.
- 판정 대상은 언제나 **가장 마지막 주장**이다.
### 5.4 예시(4인 A·B·C·D)
1. A가 실제 "거미"를 B에게 건네며 "파리"라고 주장.
2. B가 몰래 보고(거미) C에게 "거미"라고 주장(진실). 본 사람 = {A, B}.
3. C가 "거짓이다" 판정 → 공개하니 거미 → 주장은 진실이었으므로 C가 틀림 → C의 앞마당에 거미. C가 다음 시작자.
4. 만약 C가 떠넘기기를 골랐다면 보고 나서 D에게만 건넬 수 있다. D는 떠넘길 사람이 없으므로 반드시 판정.
## 6. 승패와 점수 계산
- 패배 조건(먼저 성립하는 즉시 종료):
1. 어떤 플레이어의 앞마당에 같은 종류가 `loseCount`장(기본 4, 2인 5) 모였다.
2. 라운드 시작자가 되어야 하는데 손패가 0장이다.
- 패배자 1명, 나머지 전원 공동 승리.
- 순위(방 기록용 보조 표시): 패배자 꼴찌. 나머지는 "앞마당 최다 동일 종류 장수"가 적은 순 → 앞마당 총 장수가 적은 순 → 그래도 같으면 공동 순위.
- 점수: 승리자 각 1점, 패배자 0점.
## 7. 지역 룰 / 하우스 룰 (옵션으로 켜고 끄기)
| 옵션 | 내용 | 기본값 |
|---|---|---|
| `loseCount=3` | 짧은 게임(어린이 모드 추천) | off |
| `eightKindsRule` | 앞마당에 8종을 모두 1장 이상 모아도 패배(일부 하우스 룰) | off |
| `twoPlayerPass` | 2인에서도 수신자가 보고 되돌려 줄 수 있음(원작에 없음) | off |
## 8. 엔진 설계
### 8.1 상태(State)
```ts
type Kind = 'cockroach' | 'bat' | 'fly' | 'toad' | 'rat' | 'scorpion' | 'spider' | 'stinkbug';
interface Card { id: number; kind: Kind } // id 0..63
interface CockroachState {
options: CockroachOptions;
rng: RngState;
order: PlayerId[]; // 좌석
hands: Record<PlayerId, number[]>;
fronts: Record<PlayerId, number[]>; // 공개 앞마당
removed: number[]; // 2인 제거 10장(비공개)
round: {
starter: PlayerId;
card: number | null; // 건네는 중인 카드
holder: PlayerId | null; // 현재 수신자(판단할 사람). null이면 시작자가 고르는 중
lastPasser: PlayerId | null;
claim: Kind | null; // 마지막 주장
seen: PlayerId[]; // 이 카드를 본 사람
claims: { from: PlayerId; to: PlayerId; claim: Kind }[];
deadline: number;
};
loser: PlayerId | null;
lossReason: 'four' | 'emptyHand' | null;
lastReveal?: { card: number; claim: Kind; call: 'true' | 'false'; caller: PlayerId; correct: boolean; takenBy: PlayerId };
}
```
### 8.2 액션
| type | payload | 누가 / 언제 | 검증 조건 |
|---|---|---|---|
| `offer` | `{ cardId; to; claim }` | 시작자, `card == null` | 손패에 있음, `to` != 본인, `claim`이 8종 중 하나 |
| `call` | `{ call: 'true' \| 'false' }` | 현재 holder, 아직 peek 안 함 | — |
| `peek` | `{}` | 현재 holder, 아직 안 봄 | 2인이 아님(`twoPlayerPass` 제외), 떠넘길 대상(`seen`에 없고 본인 아닌 사람)이 1명 이상. 이후 holder는 `seen`에 추가되고 view로 카드 내용을 받으며 `call` 불가 |
| `pass` | `{ to; claim }` | peek 완료한 holder | `to`가 `seen`에 없고 본인 아님, `claim`이 8종 중 하나 |
클라이언트는 서버가 카드 내용을 보내기 전에는 카드를 알 수 없으므로 "보기"와 "넘기기"를 두 액션으로 나눈다. 원작에서 "보고 나면 반드시 넘겨야 한다"는 규칙을 그대로 강제한다(peek 후 call 거부, 사유 "카드를 이미 봤으니 다른 사람에게 넘겨야 해요").
### 8.3 공개/비공개 정보 (view)
- 모두: 좌석, 각자 손패 장수, 모든 앞마당, 시작자, 현재 holder, 현재 주장, 주장 이력, `seen` 목록, 공개된 판정 결과.
- 본인: 자기 손패 내용. 건네는 중인 카드 내용은 `seen`에 있는 사람(시작자와 peek한 사람)에게만.
- 비공개: 다른 사람 손패, 건네는 중인 카드(미확인자·관전자), 2인 제거 카드(진행 중 비공개, 게임 종료 후 전체 손패와 함께 공개).
- 관전자: 공개 정보만.
### 8.4 랜덤 요소 (시드 RNG 사용 지점)
덱 셔플, 2인 제거 카드(셔플 결과의 앞 10장), 첫 시작자, 시간 초과 시 무작위 선택.
### 8.5 타이머·시간 초과·연결 끊김 시 자동 행동
- `activePlayers`: card 없으면 시작자, 있으면 holder.
- `onTimeout`:
- 시작자 → RNG로 손패 1장, 다음 좌석 플레이어에게, 주장은 RNG 종류.
- holder(미확인) → `call` 중 RNG(true/false).
- holder(peek 완료) → `seen`에 없는 첫 좌석 플레이어에게 "실제 종류"로 `pass`.
- 연결이 90초 이상 끊긴 플레이어는 차례가 오면 즉시 onTimeout 처리. 수신 대상으로는 계속 지정 가능(지정 시 바로 자동 판정).
### 8.6 종료 조건과 결과(GameResult)
```ts
interface CockroachResult {
loser: PlayerId;
reason: 'four' | 'emptyHand';
losingKind?: Kind;
ranking: PlayerId[][]; // 공동 순위 그룹, 마지막 그룹 = [loser]
fronts: Record<PlayerId, Record<Kind, number>>;
summary: string; // 예: "민수님 앞에 쥐 4장! 민수님 패배, 나머지 모두 승리"
}
```
## 9. UI/UX
- 모바일 세로: 상단 상대 플레이어 카드(아바타 + 손패 장수 + 앞마당 종류별 개수 배지, 3장째부터 노란색 경고, 위험 종류는 빨간색). 가운데 "건네지는 카드" 애니메이션과 말풍선 주장("이건 쥐야!"). 하단 내 손패(종류별 묶음 + 개수 배지, 탭으로 선택).
- 건네기 흐름: 카드 선택 → 받을 사람 탭(건넬 수 없는 사람은 회색) → 8종 그림 버튼 중 주장 선택(그림 + 글자, 각 64px 이상) → 확인.
- 판정 화면: 큰 버튼 두 개 "진짜!"(초록), "거짓말!"(빨강) + "몰래 보고 넘기기"(파랑, 넘길 사람이 없으면 비활성 + 이유).
- 공개 애니메이션: 카드 뒤집기 후 "맞혔어요!/틀렸어요!"와 앞마당으로 날아가는 연출.
- 초보자 도움말: "같은 벌레 4장 모이면 져요", 위험 경고 툴팁.
## 10. 테스트 체크리스트
- [ ] 인원별 손패 장수가 4장 표와 일치, 2인은 10장 제거 후 27장씩, 카드 총합 보존.
- [ ] 판정 정답(진실 주장에 "진실") → 전달자 앞마당, 오답 → 수신자 앞마당, 받은 사람이 다음 시작자.
- [ ] 떠넘기기 시 `seen`에 있는 사람(시작자 포함)에게 pass 거부.
- [ ] 나머지 전원이 본 상태의 holder는 peek 거부, call만 가능.
- [ ] peek 후 call 거부.
- [ ] 2인: peek 거부, 같은 종류 5장에서 패배(4장에서는 계속).
- [ ] 같은 종류 4장 도달 즉시 종료(다른 종류 4장은 무관).
- [ ] 다음 시작자의 손패가 0장 → 즉시 패배(reason emptyHand).
- [ ] 손패 0장인 사람에게 카드 건네기 허용, 그가 떠넘기기/판정 정상 동작.
- [ ] 시간 초과: 시작자 무응답 → 자동 offer, holder 무응답 → 자동 call, peek 후 무응답 → 자동 pass.
- [ ] 비공개 누출 검사: 미확인 holder·관전자·다른 플레이어 view에 건네는 카드의 kind가 없는지, 다른 사람 손패 내용이 없는지, 2인 제거 카드가 진행 중에 없는지.
- [ ] 같은 시드 + 액션 로그 재생 시 동일 결과.
## 11. 참고 자료
- 원작 규칙(영문) 요약, UltraBoardGames "How to play Cockroach Poker" — https://www.ultraboardgames.com/cockroach-poker/game-rules.php
- Wikipedia "Cockroach Poker" — https://en.wikipedia.org/wiki/Cockroach_Poker
- BoardGameGeek "Cockroach Poker" — https://boardgamegeek.com/boardgame/11971/cockroach-poker
- University of Waterloo 빠른 규칙 요약(PDF) — https://uwaterloo.ca/childrens-communication-lab/sites/default/files/uploads/documents/cockroach-poker.pdf
## 12. 메모 (상표·법적 주의 등)
- "Cockroach Poker / Kakerlakenpoker / 바퀴벌레 포커"는 Drei Magier Spiele(현 Schmidt Spiele)의 제품명이다. 규칙은 보호 대상이 아니지만 이름과 아트는 사용하지 않는다. 표시 이름 후보: "벌레 떠넘기기", "거짓말 벌레", "벌레 블러핑".
- 이름에 "포커"가 들어가지만 칩·베팅이 전혀 없는 게임이므로 도박 관련 이슈 없음. 다만 표시 이름에서 "포커"를 빼는 편이 어린이 서비스 인상에 좋다.
- 벌레 그림을 싫어하는 이용자를 위해 "동물 테마" 스킨(종류 키는 동일, 그림만 교체) 검토.