Files
joke-app/docs/06-lobby-rooms.md

7.3 KiB

06. 로비와 방

1. 목표

  • 방 만들기: 첫 화면에서 게임 카드 누르기 → "방 만들기" 두 번이면 대기실, 코드와 링크가 바로 보인다.
  • 방 참여: 링크를 누르면 바로 대기실(처음이면 닉네임 한 칸). 코드를 받았으면 첫 화면 "코드로 참여"에 숫자 6개.
  • 글을 몰라도 쓸 수 있게 큰 아이콘 + 짧은 글자.

2. 화면과 주소

주소 화면
/ 홈: 위쪽에 큰 버튼 두 개 [방 만들기] [코드로 참여], 그 아래 "진행 중인 게임으로 돌아가기"(있을 때만), 그 아래 게임 카드 목록
/g/:gameId 게임 소개: 한 줄 설명, 인원, 시간, 난이도, [방 만들기] [빠른 시작] [규칙 보기], 열린 공개 방 목록
/r/:code 방(대기실 ↔ 게임 화면 ↔ 결과 화면이 같은 주소에서 전환)
/j 코드 입력 전용 화면(숫자 키패드)
/me 계정
/rules/:gameId 규칙 설명(그림 포함)

초대 링크는 https://<도메인>/r/123456. 짧고 읽기 쉬워서 말로 불러 줘도 된다.

3. 방 코드

  • 6자리 숫자(000000 제외, 같은 숫자 6개·연속 숫자 제외). 숫자만 쓰는 이유: 어르신·어린이가 키패드로 입력하기 가장 쉽고, 말로 전달하기 쉽다.
  • 화면에는 123 456처럼 3자리씩 띄어 보여 준다. 입력은 띄어쓰기·하이픈 무시.
  • 사용 중인 코드와 겹치지 않게 무작위 발급. 방이 끝나고 24시간 동안은 같은 코드 재사용 금지(옛 링크로 엉뚱한 방에 들어가는 것 방지).
  • 코드 입력 화면: 6칸 큰 숫자 칸, 휴대폰 숫자 키패드(inputmode="numeric"), 6자리가 채워지면 자동으로 입장 시도. 클립보드에 6자리 숫자나 초대 링크가 있으면 "붙여넣기" 버튼을 크게 표시.

4. 방 만들기

  1. 게임 선택(홈 카드 또는 홈의 [방 만들기] → 게임 고르기 화면).
  2. 바로 대기실 생성. 모든 옵션은 기본값으로 이미 정해져 있다(인원은 게임 기본 인원, 공개 여부는 "친구만(비공개)").
  3. 대기실에서 방장이 바꿀 수 있는 것: 게임 모드/옵션(시간 제한 포함 — 게임마다 다름), 최대 인원(게임의 최소~최대 범위 안), 공개/비공개, 관전 허용, 채팅 허용.
  4. 옵션은 "기본 설정" 몇 개만 보이고 나머지는 "자세한 설정" 접기 안에 둔다.

5. 대기실 화면 (PC는 두 칸 배치 — 07-ui-ux.md 3.4. 아래는 내용 순서이며 좁은 화면에서는 이 순서로 한 칸에 쌓인다)

  1. 초대 카드(가장 눈에 띄게): 큰 글씨 코드 123 456
    • [코드 복사] [링크 복사] [공유하기](모바일: 공유 시트 → 카카오톡 등) [QR 보기](같은 공간의 가족이 폰으로 찍어서 입장)
    • 복사하면 버튼이 1.5초간 "복사했어요 ✓"로 바뀜 + 진동(지원 기기).
    • 공유 문구: "OO님이 오목 방에 초대했어요! 코드 123 456 · https://<도메인>/r/123456"
  2. 자리 목록: 게임 인원만큼 자리 칸. 각 칸: 프로필, 닉네임, 방장 왕관, 준비 체크, 연결 상태 점(초록/노랑/회색). 빈자리는 점선 "+ 초대하기"(누르면 초대 카드로 스크롤).
  3. 게임 설정 요약: "오목 · 렌주룰 · 한 수 30초" + 방장에게만 [설정 바꾸기].
  4. 아래 고정 버튼(엄지 닿는 곳):
    • 방장: [게임 시작] — 최소 인원이 안 되면 비활성 + "1명 더 필요해요" 안내. 모두 준비가 아니어도 시작 가능하되 "준비 안 된 사람이 있어요. 시작할까요?" 확인.
    • 나머지: [준비하기] / [준비 취소].
  5. 채팅(접을 수 있음), 관전자 수.

방장 기능: 내보내기, 방장 넘기기, 자리 순서 섞기(선 정하기), 게임 바꾸기(같은 방 유지 — 친구들끼리 여러 게임 이어서 하기). 방장이 나가면 가장 먼저 들어온 사람이 방장.

6. 링크로 들어오기

  1. /r/123456 접속.
  2. 로그인 안 됨 → 같은 화면 위에 작은 창: 방 정보("오목 · 방장 OO · 1/2명") + 닉네임 칸(추천 이름 채워짐) + [이 이름으로 입장] + 작게 [디스코드로 로그인].
  3. 입장 → 빈자리가 있으면 자동으로 자리에 앉는다. 없으면 관전자로 들어가고 "자리가 나면 앉을 수 있어요".
  4. 방이 없거나 끝났으면: "이 방은 끝났어요" + [같은 게임 새 방 만들기] [홈으로].

7. 빠른 시작

  • 게임 상세의 [빠른 시작]: 같은 게임의 공개 대기실 중 사람이 가장 많은(가득 차지 않은) 방에 들어간다. 없으면 공개 방을 새로 만들고 기다린다.
  • 기다리는 동안 "다른 사람을 기다리는 중… (공개 방)" + [친구 초대하기] 버튼.

8. 게임 중

  • 위쪽 상태 줄: 지금 할 일 문장("내 차례예요" / "OO님 차례예요"), 남은 시간 링.
  • 메뉴(⋯): 규칙 보기, 소리, 글자 크기, 무르기 요청(지원 게임), 무승부 제안(지원 게임), 기권, 나가기.
  • 나가기를 누르면: "게임 중에 나가면 OO 처리돼요. 정말 나갈까요?"(게임별: 2인 대전은 기권패, 카드 게임은 다이 후 자리 비움 등).
  • 관전자는 [자리 나면 참가] 대기 가능(다음 판부터).

9. 게임 끝

  • 결과 화면: 순위, 점수, 핵심 장면 요약(예: "흑 5목 완성"), [한 판 더] [게임 바꾸기] [나가기].
  • [한 판 더]는 투표: 자리에 앉은 사람 모두 누르면 같은 설정으로 즉시 새 게임(선/딜러는 규칙에 따라 교대). 안 누르는 사람이 있으면 그 사람이 [관전하기]로 일어나거나 방장이 [게임 시작]으로 바로 시작할 수 있다.
  • 기록: 끝난 게임은 game_results에 저장, 방 안에서 "이번 방 전적"(예: OO 3승 2패) 표시.

10. 공개 방 목록

  • 게임 상세 화면에서만 보인다(홈을 복잡하게 하지 않음). 정렬: 곧 시작할 방(빈자리 1개) 우선.
  • 각 줄: 방 이름(기본 "OO님의 방"), 인원, 모드, [참여].

11. 서버 방 모델

type RoomStatus = 'lobby' | 'playing' | 'finished' | 'paused' | 'broken';
interface Room {
  code: string;            // 6자리
  id: string;              // 내부 ULID
  hostId: UserId;
  gameId: GameId;
  options: unknown;        // 게임 optionsSchema로 검증된 값
  visibility: 'private' | 'public';
  maxPlayers: number;      // 게임 min~max 범위
  graceSec: number;        // 연결 끊김 유예(기본 60초). 턴 시간은 게임 옵션
  allowSpectators: boolean;
  chat: { enabled: boolean; filter: boolean };
  seats: Array<{ userId: UserId; ready: boolean } | null>;
  spectators: Set<UserId>;
  presence: Map<UserId, { connected: boolean; since: number; autoCount: number }>;
  status: RoomStatus;
  seq: number;
  game?: { state: unknown; rng: RngState; deadline: number | null; startedAt: number; seedHash: string };
  rematchVotes: Set<UserId>;
  sessionStats: Map<UserId, { wins: number; played: number }>;
  createdAt: number; lastActiveAt: number;
}
  • 한 사용자는 동시에 한 방에만 플레이어로 있을 수 있다(다른 방에 들어가면 이전 방 대기실 자리에서 빠짐. 이전 방이 게임 중이면 "진행 중인 게임이 있어요. 그래도 이동할까요?").
  • 제한: 서버 전체 방 2,000개, 사용자당 동시에 만든 방 3개.