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

15 KiB
Raw Permalink Blame History

할리갈리 (halli-galli)

마일스톤: M6 · 인원: 최소 2 ~ 최대 6명 · 예상 시간: 약 5~15분 · 난이도: 쉬움

1. 개요

  • 차례대로 과일 카드를 한 장씩 뒤집다가, 펼쳐진 카드에서 한 종류 과일이 정확히 5개가 되면 가운데 종을 가장 먼저 친 사람이 펼쳐진 카드를 모두 가져가는 순발력 게임.
  • 한국에서 가장 유명한 보드게임 중 하나로(코리아보드게임즈 유통), 어린이부터 어르신까지 규칙을 1분이면 이해한다.
  • 인원 근거: 원작(Amigo, 1990, 하임 샤피르) 박스 표기 2~6인.

2. 모드와 옵션

옵션 키 설명 선택지 기본값
endMode 종료 방식 lastStanding(한 명이 모든 카드를 가질 때까지, 국내 통상 규칙) / official(2명 남으면 다음 종 판정으로 종료, 원작 규칙) lastStanding
timeLimitMin 전체 제한 시간(분). 만료 시 카드 많은 사람 승 5 / 10 / 15 / 없음 10
flipSeconds 자기 차례에 뒤집기 제한 시간(초과 시 자동 뒤집기) 2 / 3 / 5 / 8 3
autoFlip 모든 뒤집기를 서버가 일정 간격으로 자동 진행 on / off off
flipGuardMs 뒤집기 직후 다음 뒤집기를 받지 않는 시간(지연 공정성) 400 / 600 / 1000 600
wrongRingLockoutMs 틀린 종 후 그 사람의 종 잠금 시간 0 / 1000 / 2000 1000
leftoverCards 나누고 남은 카드 처리 pot(종 밑, 다음 정답자가 가져감) / remove(게임에서 제외) pot
latencyComp 지연 보정 off / light off
showRtt 각자 핑 표시 on / off on
kidMode 어린이 모드: flipSeconds 8, 과일 개수 합계 보조 표시 on / off off

3. 구성물

  • 카드 56장: 과일 4종(딸기, 바나나, 라임, 자두) × 14장.
  • 과일 1종당 구성:
그려진 과일 수 장수
1개 5장
2개 3장
3개 3장
4개 2장
5개 1장

과일 1종 합계 14장, 과일 개수 총합 5×1 + 3×2 + 3×3 + 2×4 + 1×5 = 33개.

  • 종 1개(가상, 화면 가운데).

4. 준비(셋업)

  1. 56장을 RNG로 섞는다. 좌석과 첫 플레이어도 RNG.
  2. 같은 장수로 나눈다: floor(56 / N)장씩. 남은 카드는 leftoverCards에 따라 종 밑(공개되지 않은 더미, 장수만 공개) 또는 제외.
인원 1인당 남는 카드
2 28 0
3 18 2
4 14 0
5 11 1
6 9 2
  1. 각자 받은 카드는 뒷면 더미(덱). 그 앞에 공개 더미(펼친 카드) 자리가 빈 채로 시작.
  2. 3초 카운트다운 후 첫 플레이어 차례.

5. 진행 규칙

5.1 뒤집기

  • 차례인 플레이어는 자기 덱 맨 위 카드를 뒤집어 자기 공개 더미 위에 놓는다. 그 다음 시계 방향으로 다음 "참가 중" 플레이어 차례.
  • 각 플레이어의 공개 더미 맨 위 카드만 셈에 들어간다.
  • flipSeconds 안에 뒤집지 않으면 서버가 자동으로 뒤집는다.
  • 뒤집은 직후 flipGuardMs 동안은 다음 사람의 뒤집기를 받지 않는다(예약해 두었다가 시간이 지나면 처리). 지연이 큰 사람도 같은 화면을 보고 종을 칠 시간을 보장하기 위함.

5.2 종 치기

  • 누구든(차례와 무관하게, 참가 중이면) 언제든 종을 칠 수 있다.
  • 판정 기준: 종을 친 순간의 테이블 상태. 테이블의 모든 공개 더미 맨 위 카드(탈락자의 공개 더미 포함)에서 과일별 개수를 합산해 어떤 과일이든 정확히 5면 정답. 6 이상, 4 이하는 정답이 아니다.
    • 예: 딸기 2 + 딸기 3 + 바나나 4 → 딸기 5 → 정답. 딸기 2 + 딸기 3 + 딸기 1 → 딸기 6 → 오답.
  • 정답: 가장 먼저 도착한 사람이 모든 공개 더미(+ 종 밑 카드)를 가져와 자기 덱 맨 아래에 넣는다(순서: 좌석 순으로 각 공개 더미를 아래부터, 마지막에 종 밑 카드). 그 사람이 다음 뒤집기 차례.
  • 오답: 종을 친 사람은 자기 덱에서 다른 참가 중인 모든 플레이어에게 1장씩 준다(받는 사람 덱 맨 아래, 지급 순서는 시계 방향). 덱이 모자라 다 주지 못하면 시계 방향 순으로 줄 수 있는 만큼만 주고 즉시 탈락. 정확히 다 줘서 0장이 된 경우는 5.3과 같이 자기 차례가 올 때까지 참가 상태. 진행 차례는 바뀌지 않는다.
  • 늦은 종: 종에는 클라이언트가 보고 있던 테이블 버전(tableVersion, 뒤집기·수거마다 +1)이 담긴다. 서버 버전과 다르면 "늦었어요"로 무시(벌칙 없음). 같은 버전에서 이미 누가 정답을 가져갔다면 버전이 바뀌었으므로 자연히 무시된다.
  • 2명이 거의 동시에 친 경우: 서버 도착 순서가 우선(5.4).

5.3 탈락

  • 자기 차례가 왔는데 덱이 0장이면 탈락(공개 더미는 테이블에 남아 계속 셈에 들어가며, 다음 정답자가 가져간다).
  • 덱이 0장이어도 자기 차례가 오기 전에는 종을 칠 수 있다(정답이면 카드를 얻어 복귀). 단 오답이면 줄 카드가 없으므로 즉시 탈락.
  • 탈락자는 관전 화면으로 전환.

5.4 실시간 판정과 공정성

  • 방 단위 단일 큐에서 서버 수신 순서대로 serverTs를 붙여 처리한다. 같은 ms라도 큐 순서가 판정 순서이고 로그로 재생 가능.
  • 클라이언트 시각은 신뢰하지 않는다.
  • RTT: 2초마다 ping, 지수이동평균(α=0.2). showRtt면 이름 옆에 표시(초록 < 80ms, 노랑 < 200ms, 빨강 ≥ 200ms). 200ms 이상인 사람에게 "연결이 느려요" 안내.
  • flipGuardMs: 테이블이 바뀐 뒤 최소 이 시간 동안은 다시 바뀌지 않으므로, 지연 RTT/2가 이 값보다 작은 사람은 정답 상태를 놓치지 않는다.
  • latencyComp=light(선택): 첫 정답 종이 도착하면 50ms 창을 열고, 창 안의 같은 버전 정답 종들을 serverTs - min(RTT/2, 80) 기준으로 비교해 가장 이른 사람 승리. 창 마감 시 서버 내부 resolve 액션으로 확정(재생 가능). 오답 종은 창과 무관하게 즉시 벌칙.
  • wrongRingLockoutMs: 오답 후 잠금(연타 방지). 잠금 중 종은 거부(벌칙 없음).

5.5 예시(3인 A·B·C)

  1. A 뒤집기: 바나나 3. B 뒤집기: 딸기 2. C 뒤집기: 바나나 2 → 바나나 5.
  2. B가 먼저 종(버전 3) → 정답. B가 세 공개 더미 + 종 밑 2장을 덱 아래로. B 차례로 다음 뒤집기.
  3. 0.1초 뒤 A의 종(버전 3) 도착 → 버전이 4로 바뀌었으므로 "늦었어요", 벌칙 없음.
  4. 이후 A가 바나나 4 상태에서 착각해 종 → 오답 → A가 B와 C에게 1장씩.

6. 승패와 점수 계산

  • lastStanding: 마지막까지 남은 1명(모든 카드 보유) 승리. 순위는 탈락 역순.
  • official: 참가자가 2명 남으면, 그다음 정답 종이 나올 때까지 진행하고 종료. 그 2명 상태에서 오답 종이 나오면 상대가 펼쳐진 카드 전부를 가져가고 즉시 종료. 종료 시 보유 카드가 많은 순. 처음부터 2인 게임이면 official도 lastStanding으로 진행한다(원작 문구대로면 첫 종에서 끝나 버리므로).
  • timeLimitMin 만료: 즉시 종료, 보유 카드 수 많은 순.
  • 보유 카드 수 = 덱 장수 + 자기 공개 더미 장수(아직 아무도 가져가지 않은 자기 카드). 종 밑 카드는 누구 것도 아님.
  • 동점: 정답 종 횟수 많은 순 → 그래도 같으면 공동 순위.
  • 방 기록용 점수: 1등 3점, 2등 2점, 3등 1점.

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

옵션 내용 기본값
duelWrongLoses 1대1 상황에서 오답 종을 친 사람이 즉시 패배(국내 하우스 룰) off
strikeOut 오답 3회 누적 시 몰수패 off
penaltyEscalation 오답마다 줄 장수가 1, 2, 3장...으로 증가 off
wrongToPot 오답 벌칙 카드를 다른 사람에게 주지 않고 종 밑으로 off

8. 엔진 설계

8.1 상태(State)

type Fruit = 'strawberry' | 'banana' | 'lime' | 'plum';
interface Card { id: number; fruit: Fruit; count: 1 | 2 | 3 | 4 | 5 }   // id 0..55

interface HalliGalliState {
  options: HalliGalliOptions;
  rng: RngState;
  order: PlayerId[];
  decks: Record<PlayerId, number[]>;     // 뒷면 덱, 맨 위 = 끝
  faceUp: Record<PlayerId, number[]>;    // 공개 더미, 맨 위 = 끝
  pot: number[];                          // 종 밑
  active: PlayerId[];                     // 참가 중(탈락 안 함)
  eliminated: PlayerId[];                 // 탈락 순서
  turn: PlayerId;
  turnDeadline: number;
  flipAllowedAt: number;                  // flipGuard
  pendingFlip: PlayerId | null;           // 가드 중 들어온 뒤집기 예약
  tableVersion: number;
  lockedUntil: Record<PlayerId, number>;
  wrongCount: Record<PlayerId, number>;
  correctCount: Record<PlayerId, number>;
  ringWindow: null | { version: number; openedAt: number; rings: { player: PlayerId; adjTs: number }[] };
  phase: 'countdown' | 'playing' | 'ended';
  startedAt: number;
  endsAt: number | null;                  // timeLimit
  lastBell?: { player: PlayerId; correct: boolean; fruit?: Fruit; at: number };
}

8.2 액션

액션에는 서버가 serverTs를 붙인다.

type payload 누가 / 언제 검증 조건
flip {} 차례인 플레이어, playing turn == actor, 덱 1장 이상. serverTs < flipAllowedAt이면 거부하지 않고 pendingFlip에 예약
ring { version: number } 참가 중 플레이어(덱 0장 포함), playing serverTs >= lockedUntil[actor], version == tableVersion(다르면 "늦었어요"). 정답/오답 판정은 apply에서
tick {} 서버 타이머 자동 뒤집기, 예약 뒤집기 실행, 시간 제한, 카운트다운
resolve {} 서버 내부, latencyComp=light 창 열림

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

  • 모두(관전자 포함): 각 공개 더미 맨 위 카드(과일·개수), 공개 더미 장수, 각자 덱 장수, 종 밑 장수, 차례, 테이블 버전, 잠금 상태, RTT, 남은 시간, 탈락자.
  • 비공개: 모든 덱의 카드 내용과 순서(본인 덱 포함 — 실제 게임에서도 자기 덱을 보지 않음), 종 밑 카드 내용, 공개 더미의 맨 위 아래 카드(가려져 있으므로 보낼 필요 없음).
  • 자기 덱 내용을 본인에게도 보내지 않는 이유: 다음 카드를 미리 알면 뒤집기 타이밍을 조작할 수 있다.
  • "지금 5개다"라는 판정 결과는 view에 넣지 않는다(kidMode의 과일 합계 보조 표시는 클라이언트가 공개 정보로 계산).

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

덱 셔플, 좌석, 첫 플레이어. 이후 카드 이동은 모두 결정적(수거 순서 고정).

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

  • activePlayers: 참가 중 전원(종은 누구나), 뒤집기는 turn.
  • deadline: min(turnDeadline, flipAllowedAt(예약 있을 때), ringWindow 마감, endsAt).
  • onTimeout(player): 차례 플레이어의 flip. 종은 자동으로 치지 않는다.
  • 연결 끊김: 그 사람 차례는 flipSeconds 후 자동 뒤집기로 계속 진행(카드를 모두 잃을 때까지 남아 있음). 60초 이상 끊기면 flipSeconds를 1초로 줄여 진행 속도 유지.
  • autoFlip on이면 모든 뒤집기를 서버가 flipSeconds 간격으로 실행(flip 액션 거부).

8.6 종료 조건과 결과(GameResult)

interface HalliGalliResult {
  endReason: 'lastStanding' | 'official' | 'timeLimit';
  ranking: { rank: number; id: PlayerId; cards: number; correct: number; wrong: number }[];
  summary: string;   // 예: "도윤님이 56장을 모두 모아 우승!"
}

9. UI/UX

  • 모바일 세로: 화면 가운데 큰 종 버튼(지름 화면 폭 40% 이상, 최소 120px) — 화면 어디를 쳐도 되게 하지 않고 종만 반응(오조작 방지). 종 주위에 플레이어별 공개 카드(최대 6장)를 원형 배치. 하단 내 덱(장수 표시)과 "뒤집기" 버튼(내 차례에만 활성, 큰 버튼). 상단 남은 시간, 순위.
  • 차례 표시: 차례인 사람 카드 자리 테두리 깜빡임 + 남은 뒤집기 시간 원형 게이지.
  • 종 피드백: 정답 시 "땡!" 효과음, 카드가 정답자에게 날아가는 애니메이션. 오답 시 "삐!" + 벌칙 카드 이동 애니메이션 + 잠금 원형 타이머. 늦음 시 작은 "늦었어요".
  • 키보드(PC): 스페이스 = 종, 엔터 = 뒤집기.
  • 어린이 모드: 느린 뒤집기, 과일별 합계 막대(공개 정보로 계산), "5개가 되면 종을 쳐요" 상시 안내.
  • 효과음은 끄기 가능, 진동(지원 기기) 옵션.

10. 테스트 체크리스트

  • 덱: 56장, 과일별 14장, 개수별 5/3/3/2/1장.
  • 분배: 인원별 장수와 남는 카드가 4장 표와 일치, pot/remove 처리.
  • 판정: 딸기 2+3 → 정답, 딸기 2+3+1 → 오답, 다른 과일 섞여도 한 과일이 정확히 5면 정답, 탈락자 공개 더미 맨 위도 합산.
  • 정답 수거 순서 결정적(좌석 순, 종 밑 마지막), 정답자가 다음 차례.
  • 동시 종: 같은 버전 정답 종 2개 → 먼저 도착한 사람만 수거, 두 번째는 버전 불일치로 "늦었어요" 무벌칙.
  • 오답 벌칙: 4인에서 덱 2장인 사람 오답 → 시계 방향 2명에게만 1장씩 주고 0장 → 즉시 탈락.
  • 덱 0장인 사람이 차례 전에 정답 종 → 카드 획득 후 계속 참가. 차례가 왔을 때 0장 → 탈락.
  • flipGuardMs: 가드 중 들어온 flip은 예약되었다가 가드 종료 시 실행, 테이블 버전 1회만 증가.
  • 뒤집기 시간 초과 → 자동 뒤집기. 연결 끊긴 사람도 계속 자동 진행.
  • official 종료: 3인 → 2인 남은 뒤 첫 정답 종에서 종료, 2인 상태 오답 → 상대 수거 후 종료. 처음부터 2인이면 lastStanding처럼 진행.
  • 시간 제한 만료: 보유 카드(덱 + 자기 공개 더미) 기준 순위, 동점은 정답 횟수.
  • 비공개 누출 검사: 모든 view/이벤트에 덱 카드 내용(본인 포함), 종 밑 카드 내용, 다음 카드가 없는지 자동 검사.
  • 같은 시드 + 액션 로그(serverTs 포함) 재생 시 동일 결과.

11. 참고 자료

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

  • "Halli Galli / 할리갈리"는 Amigo Spiele의 상표이며 국내 유통은 코리아보드게임즈. 카드 그림도 저작물이므로 자체 제작. 표시 이름 후보: "과일 종치기", "다섯 개 땡!", "후르츠 벨".
  • 도박 요소 없음.
  • 종 버튼 연타는 터치스크린 기기 성능 차이(입력 지연)가 있으므로 경쟁 기록(랭킹)을 만들 경우 공정성 한계를 안내한다.