- 개요, 조사(BGA·한국 인기 보드게임·UI/UX), 아키텍처, 실시간 프로토콜, 안정성·보안, 계정, 로비·방, UI/UX, 데이터 모델, 게임 엔진, 테스트, 배포·운영, 로드맵(M0~M9), 사용자 결정 항목 - 게임별 규칙·엔진 설계·UI·테스트 체크리스트 26종 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
18 KiB
18 KiB
그림 맞히기 (catchmind)
마일스톤: M8 · 인원: 최소 2 ~ 최대 10명 · 예상 시간: 약 10~20분 · 난이도: 쉬움
1. 개요
- 한 사람(출제자)이 제시어를 그림으로 그리고, 나머지가 채팅으로 정답을 맞히는 실시간 게임. 차례대로 돌아가며 출제자를 맡는다.
- 한국에서는 넷마블 "캐치마인드"(2000년대)와 방송·모바일 앱으로 전 연령에 익숙한 형식이다. 글자를 몰라도 그림으로 참여할 수 있어 어린이·어르신 모두 즐기기 좋다.
- 인원 근거: 출제자 1명 + 맞히는 사람 1명 이상이면 성립하므로 최소 2명. 최대는 캐치마인드 방 정원 관행과 채팅 가독성(모바일 한 화면)을 고려해 10명.
2. 모드와 옵션
| 옵션 키 | 설명 | 선택지 | 기본값 |
|---|---|---|---|
guessMode |
맞히기 방식 | multi(여러 명이 맞힐 때까지) / firstOnly(첫 정답자가 나오면 즉시 종료, 원조 캐치마인드식) |
multi |
rounds |
라운드 수(1라운드 = 모두 1번씩 출제) | 1 / 2 / 3 | 2 |
drawSeconds |
그리기 제한 시간 | 60 / 80 / 100 / 120 | 80 |
wordChoice |
출제자가 3개 중 고르기 | on / off(무작위 1개) | on |
categories |
사용할 단어 분야 | 3.2 분야 다중 선택 | 전체 |
difficulty |
단어 난이도 | easy / normal / mixed |
mixed |
customWords |
방장 추가 단어(줄바꿈 구분, 최대 200개, 필터 통과분만) | 텍스트 | 없음 |
customOnly |
추가 단어만 사용 | on / off | off |
hints |
힌트 단계 | none / basic(글자 수) / full(글자 수 + 초성 + 한 글자) |
full |
closeHint |
"아까워요" 개인 알림 | on / off | on |
letterReport |
맞히는 사람이 "글자를 썼어요" 신고 투표 | on / off | on |
3. 구성물
3.1 그림판
- 논리 좌표: 1000 × 1000 정사각형(정수). 모든 기기에서 같은 비율로 축소/확대.
- 도구: 펜(굵기 4단계: 4, 8, 16, 32), 지우개(굵기 동일 4단계), 색 12개(검정, 흰색, 회색, 빨강, 주황, 노랑, 초록, 하늘, 파랑, 보라, 분홍, 갈색), 되돌리기(마지막 획 삭제), 전체 지우기.
- 채우기(페인트통)는 기기별 래스터 결과 차이 때문에 1차 범위에서 제외.
3.2 단어 목록 (한국어, 어린이 안전)
- 파일:
words.ko.json—{ word, aliases[], category, difficulty }. 목표 600개 이상. - 선정 기준: 2~5음절, 그림으로 그릴 수 있는 구체 명사 위주, 초등 저학년이 아는 말, 실존 인물·상표·캐릭터·정치·종교·폭력·신체 노출·질병 관련 단어 제외.
- 분야와 예시(쉬움 위주):
| 분야 키 | 이름 | 예시 |
|---|---|---|
animal |
동물 | 고양이, 강아지, 기린, 코끼리, 펭귄, 거북이, 토끼, 사자, 문어, 나비 |
food |
음식 | 김밥, 떡볶이, 피자, 라면, 수박, 아이스크림, 바나나, 케이크, 만두, 붕어빵 |
object |
물건 | 우산, 안경, 가위, 시계, 열쇠, 연필, 의자, 냉장고, 칫솔, 선풍기 |
vehicle |
탈것 | 자전거, 비행기, 기차, 배, 버스, 헬리콥터, 로켓, 소방차 |
nature |
자연 | 무지개, 화산, 눈사람, 해바라기, 구름, 번개, 바다, 산 |
place |
장소 | 학교, 병원, 놀이터, 수영장, 도서관, 동물원 |
job |
직업 | 소방관, 요리사, 의사, 경찰관, 우체부, 화가 |
sport |
운동·놀이 | 축구, 줄넘기, 수영, 연날리기, 그네, 미끄럼틀 |
body |
몸 | 눈, 코, 손, 발, 귀, 이빨 (노출 관련 제외) |
season |
계절·명절 | 송편, 떡국, 크리스마스트리, 눈싸움, 부채, 단풍 |
aliases예:휴대폰↔핸드폰,휴대전화,스마트폰/경찰관↔경찰. 별칭은 같은 대상을 가리키는 흔한 말만 넣고 상위·하위 개념(예:아이스크림→아이스크림콘)은 넣지 않는다.customWords는 같은 금칙어 필터를 통과해야 하며, 1~10자 한글/영문/숫자만 허용.
4. 준비(셋업)
- 좌석 순서를 RNG로 섞는다. 이 순서가 출제 순서.
- 옵션에 맞는 단어 풀을 만들고 RNG로 섞는다. 게임 중 같은 단어는 다시 나오지 않는다.
- 점수 0으로 시작. 첫 출제자의
choosing단계로.
5. 진행 규칙
5.1 한 차례(턴)의 흐름
- 고르기(choosing, 10초): 출제자에게 단어 3개 제시(
wordChoiceoff면 1개 자동). 시간 초과 시 첫 단어. - 그리기(drawing,
drawSeconds): 출제자는 그리기만 가능(채팅 금지, 미리 정한 반응 버튼만). 나머지는 채팅으로 추측. - 공개(reveal, 5초): 정답과 이번 턴 점수 표시, 그림 저장(선택).
- 다음 출제자. 모든 라운드가 끝나면 게임 종료.
5.2 그리기 종료 조건
- 시간 만료.
multi: 출제자를 제외한 접속 중인 전원이 맞힘.firstOnly: 첫 정답자가 나오는 즉시.- 출제자 연결 끊김 10초 경과(출제자 0점, 정답 공개).
letterReport신고가 맞히는 사람 과반(아직 못 맞힌 사람 포함 출제자 제외 전원 기준)에 도달 → 즉시 종료, 출제자 이번 턴 0점(맞힌 사람 점수는 유지).
5.3 정답 판정(정규화)
정답 후보 = word + aliases. 입력과 후보 모두 같은 함수로 정규화한 뒤 완전 일치 비교.
normalize(s): // 호출 전: 원문이 호환 자모로만 되어 있으면 정답 판정 생략
1. NFKC 정규화 (전각→반각, NFD로 분리된 한글 자모 → 완성형 결합)
2. 제로폭 문자(U+200B~U+200D, U+FEFF) 제거
3. 공백(모든 유니코드 공백) 제거
4. 문장부호·기호 제거: . , ! ? ~ - _ ' " ( ) [ ] … · 와 이모지
5. 영문 소문자화
6. 끝말 허용: 결과가 정답 + ("요" | "다" | "이다" | "입니다" | "인가" | "인가요") 형태면 끝말 제거 후 비교
- 예:
"아이스 크림!"→아이스크림정답."사과요"→사과정답. macOS 등에서 온 NFD 입력(조합형 자모 U+1100U+11FF로 분리된U+318E)를 조합형으로 바꿔사과)도 NFKC로 완성형사과가 됨. 주의: NFKC는 호환 자모(U+3131ㅅㅏㄱㅘ같은 낱자 입력을사과로 결합해 버린다(실제 확인함). 그래서 정규화 전에 원문 검사를 먼저 한다: 원문(공백 제외)이 호환 자모로만 이루어져 있으면 정답 판정을 하지 않고 아래 초성 규칙으로 처리한다. - 초성만 입력(
ㅅㄱ)은 정답 아님. 호환 자모(U+3131~U+318E)만으로 된 메시지가 정답의 초성과 같으면 다른 사람에게 보이지 않게 막는다(힌트 유출 방지). - 아까워요(
closeHint): 정답이 아니고, 입력과 정답을 자모 단위로 분해(초성·중성·종성, 겹받침·이중모음도 분해)한 뒤 레벤시테인 거리가 1(정답 3음절 이하) 또는 2(4음절 이상) 이하이면 보낸 사람에게만 "아까워요!" 표시. 메시지는 다른 사람에게 그대로 보인다. - 정답 메시지 자체는 다른 사람에게 보내지 않고 "○○님이 맞혔어요!" 시스템 메시지로 대체.
- 정답을 포함하지만 정답이 아닌 메시지(예: "사과나무 아님")도 다른 사람에게 보내지 않고, 보낸 사람에게 "정답이 들어간 말은 보낼 수 없어요" 안내.
- 이미 맞힌 사람의 채팅은 출제자와 이미 맞힌 사람에게만 보인다(스포일러 방지). 시스템이 "맞힌 사람끼리 대화" 라벨 표시.
5.4 힌트 공개 시점(hints=full)
- 시작 즉시: 글자 수(
○○○, 띄어쓰기 위치 포함). - 50% 경과: 모든 글자의 초성(
ㅇㅇㅅㅋㄹ). - 75% 경과: RNG로 고른 한 글자를 완성형으로 공개(
ㅇ이ㅅㅋㄹ). 단, 2글자 이하 단어는 이 단계 생략(정답이 거의 드러남). basic은 글자 수만,none은 아무것도 없음.- 힌트는 이미 맞힌 사람·출제자에게도 같은 화면 요소로 표시(정답은 별도 표시).
5.5 그림 스트리밍(WebSocket)
- 클라이언트 샘플링: Pointer Events(
getCoalescedEvents사용), 논리 좌표로 변환 후 정수 반올림, 직전 점과 거리 2 미만이면 버림. - 배치: 50ms마다 또는 점 32개가 쌓이면 1개 메시지로 전송(최대 초당 20회). 획 시작/끝은 즉시 전송.
- 메시지(JSON, 짧은 키):
{"t":"act","a":{"type":"draw","op":{"k":"b","s":17,"c":3,"w":2,"e":0}}}
{"t":"act","a":{"type":"draw","op":{"k":"p","s":17,"q":5,"d":[412,330,3,-1,4,0,5,2]}}}
{"t":"act","a":{"type":"draw","op":{"k":"e","s":17}}}
k:b(획 시작:s획 번호,c색 인덱스,w굵기 인덱스,e지우개 0/1),p(점 묶음:q배치 순번,d첫 점은 절대 좌표, 이후는 이전 점과의 차이),e(획 끝),u(되돌리기),x(전체 지우기).- 서버 검증: 출제자 본인, drawing 단계, 획 번호는 직전 시작 획 번호 + 1, 배치 순번 연속, 점 개수 1
64, 좌표 01000, 초당 30 메시지 이하, 턴당 누적 점 30,000 이하(초과 시 거부하고 출제자에게 안내). - 서버는 검증 통과 즉시 다른 참가자에게 그대로 중계(브로드캐스트)하고 턴의 그림 기록에 추가한다. 재접속/중간 입장 관전자는 기록 전체를 한 번에 받아 다시 그린다.
- 수신 측 렌더링: 받은 점을 이어 중간점 2차 곡선으로 부드럽게 그림. 패킷 지연으로 늦게 와도 순서는 WebSocket(TCP) 순서 보장에 의존.
- 대역폭 추정: 초당 20 메시지 × 약 120바이트 = 약 2.4KB/s/시청자. 10명 방에서 서버 송신 약 22KB/s.
- 프레임워크 요구사항: 그림 액션은 다른 액션처럼 로그에 남겨 재생 가능해야 하지만, 매 액션마다 전체 view를 다시 보내면 안 된다.
apply가 내보내는canvasOp이벤트만 증분 전송하고, 전체 그림은 (재)접속 시에만 view로 보낸다. 상태의 그림 기록은 추가 전용 배열로 두고 액션마다 깊은 복사하지 않는다.
6. 승패와 점수 계산
multi모드: k번째 정답자(1부터) 점수 =max(10 - 2 × (k - 1), 4)→ 10, 8, 6, 4, 4, ...- 출제자 점수 = 정답자 1명당 2점, 최대 12점. 아무도 못 맞히면 0점.
firstOnly모드: 정답자 10점, 출제자 5점.- 신고 투표로 종료된 턴: 출제자 0점.
- 최종 순위: 총점 내림차순 → 동점이면 정답 횟수 많은 순 → 그래도 같으면 공동 순위.
- 예(5인, multi): 출제자 A, C가 1등·E가 2등으로 맞히고 B·D 실패 → C 10, E 8, A 4.
7. 지역 룰 / 하우스 룰 (옵션으로 켜고 끄기)
| 옵션 | 내용 | 기본값 |
|---|---|---|
firstOnly |
원조 캐치마인드처럼 첫 정답자만 득점, 즉시 다음 문제 | off(multi) |
answererDraws |
정답자가 다음 출제자가 됨(원조 방식, firstOnly에서만) |
off |
noHints |
힌트 없음(고수 방) | off |
speedBonus |
남은 시간 비율 × 5점 추가 | off |
8. 엔진 설계
8.1 상태(State)
type CanvasOp =
| { k: 'b'; s: number; c: number; w: number; e: 0 | 1 }
| { k: 'p'; s: number; q: number; d: number[] }
| { k: 'e'; s: number }
| { k: 'u' }
| { k: 'x' };
interface ChatEntry { id: number; from: PlayerId; text: string; at: number; audience: 'all' | 'solvers' | 'self'; kind: 'chat' | 'correct' | 'close' | 'blocked' }
interface CatchmindState {
options: CatchmindOptions;
rng: RngState;
order: PlayerId[];
scores: Record<PlayerId, number>;
correctCounts: Record<PlayerId, number>;
wordPool: WordEntry[]; // 남은 단어(섞인 순서)
round: number; // 1..rounds
drawerIndex: number;
turn: {
phase: 'choosing' | 'drawing' | 'reveal';
drawer: PlayerId;
choices: WordEntry[]; // choosing 단계 후보
word: WordEntry | null;
startedAt: number; endsAt: number;
hintStage: 0 | 1 | 2; // 0 글자 수, 1 초성, 2 한 글자
revealIndex: number | null; // RNG로 고른 공개 글자 위치
solvers: { id: PlayerId; at: number; points: number }[];
reports: PlayerId[];
canvas: CanvasOp[]; // 추가 전용
totalPoints: number;
lastStroke: number; lastBatch: number;
};
chat: ChatEntry[];
finished: boolean;
}
8.2 액션
| type | payload | 누가 / 언제 | 검증 조건 |
|---|---|---|---|
chooseWord |
{ index: 0 | 1 | 2 } |
출제자, choosing | 후보 범위 |
draw |
{ op: CanvasOp } |
출제자, drawing | 5.5의 서버 검증 |
guess |
{ text } |
출제자 제외, drawing | 1~40자, 초당 3개 이하. 이미 맞힌 사람이면 일반 채팅으로 처리 |
chat |
{ text } |
출제자 제외, choosing/reveal | 1~100자, 레이트 리밋, 정답 포함 여부 검사 |
reaction |
{ code } |
출제자, drawing | 미리 정한 반응 목록("거의 다 왔어요", "아니에요", "비슷해요") |
report |
{} |
출제자 제외, drawing, letterReport on |
1인 1회 |
tick |
{} |
서버 타이머 | 힌트 단계 진행, 시간 만료 처리 |
8.3 공개/비공개 정보 (view)
- 모두: 순서, 점수, 라운드, 출제자, 단계, 남은 시간, 힌트(글자 수/초성/공개 글자), 그림 기록, 정답자 목록(이름과 순서), 공개 채팅.
- 출제자: 위 + 고르기 후보 + 정답 단어.
- 이미 맞힌 사람: 위 + 정답 단어 +
solvers채팅. - 아직 못 맞힌 사람·관전자: 정답 단어 없음,
solvers채팅 없음, 다른 사람의close/blocked메시지 없음. - reveal 단계부터는 모두에게 정답 공개.
- 단어 풀(앞으로 나올 단어)은 누구에게도 보내지 않는다.
8.4 랜덤 요소 (시드 RNG 사용 지점)
좌석(출제) 순서, 단어 풀 셔플, wordChoice off일 때 단어, 75% 힌트 글자 위치.
8.5 타이머·시간 초과·연결 끊김 시 자동 행동
deadline:turn.endsAt과 다음 힌트 시점 중 이른 시각(서버가tick적용).onTimeout: choosing →chooseWord {index: 0}. drawing/reveal은tick으로 진행.- 출제자 연결 끊김: choosing 중이면 즉시 자동 선택 후 10초 대기, drawing 중 10초 이상이면 턴 종료. 재접속하지 않은 플레이어의 다음 출제 차례는 건너뜀(점수 유지).
- 맞히는 사람 연결 끊김:
multi의 "전원 정답" 판정에서 제외.
8.6 종료 조건과 결과(GameResult)
interface CatchmindResult {
ranking: { rank: number; id: PlayerId; score: number; correct: number }[];
turns: { drawer: PlayerId; word: string; solvers: PlayerId[] }[];
summary: string; // 예: "1등 지민님 54점! 가장 많이 맞힌 사람도 지민님"
}
게임 종료: 마지막 라운드의 마지막 출제자 턴의 reveal 종료 시.
9. UI/UX
- 모바일 세로: 상단 정답 힌트 줄(
○○○큰 글씨) + 남은 시간 막대. 그 아래 정사각형 그림판(화면 폭 100%). 출제자는 그림판 아래 도구 막대(색 12개 원형 48px, 굵기 4단계, 지우개, 되돌리기, 전체 지우기). 맞히는 사람은 그림판 아래 채팅 목록 + 입력창(입력창은 키보드 위에 고정). - 점수판: 상단 가로 스크롤 아바타 + 점수, 맞힌 사람은 초록 체크.
- 정답 시: 화면 가운데 "정답!" 애니메이션 + 효과음, 입력창에 정답이 그대로 남지 않게 비움.
- 어린이·어르신 배려: "쉬운 단어만" 기본 프리셋, 단어 고르기 버튼에 그림 아이콘, 큰 글자 모드. 음성 입력(브라우저 받아쓰기)도 같은 정규화로 판정됨.
- 그림 저장: 턴 종료 시 그림을 PNG로 내려받기(클라이언트 렌더).
- PC: 그림판 왼쪽, 채팅 오른쪽.
10. 테스트 체크리스트
- 정규화:
"아이스 크림!","아이스크림요", NFD 입력, 전각 영문(ABC), 제로폭 문자 포함 입력이 모두 정답 처리. - 초성만 입력(
ㅇㅇㅅㅋㄹ)은 정답 아님 + 다른 사람에게 비공개. 호환 자모 낱자 입력ㅅㅏㄱㅘ도 (NFKC로사과가 되지만) 정답 아님. - 끝말 허용이 정답 자체를 깎지 않음: 정답
바다에바입력은 오답,바다요는 정답. - 정답 포함 비정답 메시지("사과나무") 다른 사람에게 비공개, 보낸 사람에게 안내.
- 아까워요:
코끼리정답에코끼라입력 → 본인에게만 close 알림, 다른 사람에게는 일반 채팅. - multi 점수: 4명 순서대로 정답 시 10/8/6/4, 출제자 8점. 출제자 최대 12점 상한.
- firstOnly: 첫 정답 즉시 턴 종료, 정답자 10·출제자 5.
- 힌트: 50%에 초성, 75%에 한 글자. 2글자 단어는 75% 단계 생략.
- 그림 검증: 출제자 아닌 사람의 draw 거부, 좌표 1001 거부, 점 65개 배치 거부, 획 번호 건너뛰기 거부, 초당 31번째 메시지 거부.
- 재접속: 그리기 도중 재접속한 맞히는 사람이 같은 그림 기록 전체를 받음(획 수·점 수 일치).
- 출제자 연결 끊김 10초 → 턴 종료, 정답 공개, 다음 출제자.
- 신고 과반 → 턴 종료, 출제자 0점.
- 비공개 누출 검사: 못 맞힌 사람·관전자 view와 이벤트에 정답 단어, 다른 후보 단어, 단어 풀, solvers 채팅이 없는지 자동 검사(그리기 중 모든 시점).
- 같은 단어가 한 게임에서 두 번 나오지 않음.
- 같은 시드 + 액션 로그 재생 시 점수·그림 기록 동일.
11. 참고 자료
- 나무위키 "캐치마인드" — https://namu.wiki/w/캐치마인드
- Wikipedia "Pictionary" (그림 맞히기 일반 규칙) — https://en.wikipedia.org/wiki/Pictionary
- Unicode Standard Annex #15 정규화 형식(NFKC) — https://unicode.org/reports/tr15/
- 한글 음절 분해(유니코드 한글 음절 공식: (음절 - 0xAC00) = (초성×21 + 중성)×28 + 종성) — https://www.unicode.org/versions/latest/ch03.pdf (3.12절)
- MDN Pointer Events / getCoalescedEvents — https://developer.mozilla.org/docs/Web/API/PointerEvent/getCoalescedEvents
12. 메모 (상표·법적 주의 등)
- "캐치마인드"는 넷마블의 상표이고 "Pictionary"는 Mattel의 상표다. 표시 이름 후보: "그림 맞히기", "그려서 맞혀요", "쓱싹 퀴즈".
- 사용자가 그린 그림과 채팅은 사용자 생성 콘텐츠다. 부적절한 그림 신고 → 방장 강퇴 기능, 서버에는 턴 종료 후 그림 기록을 보관하지 않음(재생용 액션 로그 보관 기간은 짧게).
- 단어 목록은 직접 작성한 것을 사용(타 서비스 단어 목록 복제 금지).
- 도박 요소 없음.