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

11 KiB
Raw Blame History

리버시 (오셀로) (othello)

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

1. 개요

  • 8×8 판에서 상대 돌을 내 돌 사이에 끼우면 뒤집어 내 색으로 만드는 2인 게임. 끝났을 때 자기 색 돌이 더 많으면 승리. 한국에서는 "오셀로"라는 이름으로 잘 알려져 있고 규칙 설명이 1분이면 끝나 어린이·어르신 모두에게 적합하다.
  • 인원 근거: 원작이 흑·백 2인 게임. 최소 2, 최대 2.
  • 규칙 기준: 세계오셀로연맹(WOF) 공식 규칙.
  • 완전 정보 게임. view에서 제외할 것은 RNG 상태뿐.
  • 표시 이름은 상표 문제로 "리버시"를 권장(12장).

2. 모드와 옵션

옵션 키 설명 선택지 기본값
colorAssignment 흑백 결정 random / hostBlack / hostWhite / alternate random
emptyToWinner 종료 시 빈칸을 승자 점수에 더함(WOF 대회 방식, 승패에는 영향 없음 — 점수 표시만 바뀜) true / false false
timeControl.kind 시간 방식 none / perMove / fischer perMove
timeControl.perMoveSec 한 수당 제한 10 / 20 / 30 / 60 30
timeControl.baseSec / incSec 전체 시간 / 수당 추가 3+2, 5+3, 10+5 5+3
timeoutPolicy perMove 시간 초과 처리 autoMove(무작위 합법 수) / lose autoMove
idleLimitSec none 무응답 한도 120 / 300 / 600 300
undo 무르기 허용 횟수(상대 동의) off / 1 / 3 / unlimited 3
drawOffer 무승부 제안 true / false true
showLegalMoves 둘 수 있는 칸 표시 true / false true
showFlipCount 둘 수 있는 칸에 뒤집히는 개수 숫자 표시(초보 도움말) true / false false
disconnectGraceSec 연결 끊김 유예 30 / 60 / 120 60

3. 구성물

  • 8×8 판(64칸). 돌은 칸 안에 놓는다.
  • 한 면이 흑, 다른 면이 백인 원판 돌 64개(엔진에서는 개수 제한 없음 — 칸 수가 상한).
  • 좌표: 열 ah(왼→오), 행 18(위→아래, 오셀로 표준 표기). 내부 (x, y) 0~7, 표시 'abcdefgh'[x] + (y+1).

4. 준비(셋업)

  1. colorAssignment로 흑백 결정.
  2. 가운데 4칸에 배치: d4 백, e4 흑, d5 흑, e5 백.
  3. 흑이 먼저 둔다. 시작 시 흑의 합법 수는 d3, c4, f5, e6 네 곳.

5. 진행 규칙

  • 합법 수: 빈칸 p에 내 돌을 놓았을 때, 8방향(가로·세로·대각) 중 적어도 한 방향에서 p 바로 옆부터 상대 돌이 1개 이상 연속되고 그 끝에 내 돌이 있는 경우.
  • 착수 처리: 위 조건을 만족하는 모든 방향에 대해, 사이에 낀 상대 돌을 전부 내 색으로 뒤집는다. 뒤집힌 돌로 인해 새로 끼워지는 돌은 연쇄로 뒤집지 않는다(한 수에서 놓은 돌 기준의 직선만).
  • 뒤집을 수 있는 칸이 있으면 반드시 그중 한 곳에 둬야 한다(자발적 패스 없음).
  • 패스: 차례인 플레이어에게 합법 수가 하나도 없으면 자동으로 차례가 상대에게 넘어간다(서버가 처리, 플레이어 행동 불필요). 이벤트 passed를 남긴다.
  • 종료: 양쪽 모두 합법 수가 없으면(판이 가득 찬 경우 포함) 즉시 종료.
  • 예시: 시작 직후 흑이 d3에 두면 d4 백이 흑으로 뒤집힌다(d3–d4–d5 세로). 흑 4, 백 1.

6. 승패와 점수 계산

  • 점수 = 종료 시 판 위 자기 색 돌 수.
  • 많은 쪽 승리, 같으면 무승부.
  • emptyToWinner=true(WOF 대회 방식): 빈칸이 남은 채 끝났으면 빈칸 수를 승자 점수에 더한다. 무승부면 빈칸을 반씩 나눈다(빈칸 수는 무승부 시 항상 짝수). 예: 흑 40, 백 20, 빈칸 4 → 표시 점수 44 : 20. 승패는 동일.
  • 그 외 종료: 기권, 시간패, 무승부 합의.
  • 기권·시간패로 끝나도 결과에 당시 돌 수를 함께 기록.

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

  • 가운데 4칸 초기 배치를 직접 놓는 옛 방식(초기 4수를 번갈아 가운데 4칸에 두는 리버시 원형 규칙)은 지원하지 않는다. 표준 대각 배치 고정.
  • 6×6, 10×10 판 변형은 v1 미지원(추후 boardSize 옵션 후보).
  • 빈칸을 승자에게 주는 대회 점수 방식은 옵션.

8. 엔진 설계

8.1 상태(State)

type Color = 'black' | 'white';
type Cell = 0 | 1 | 2;

interface OthelloState {
  options: OthelloOptions;
  board: Cell[];                               // 64, index = y*8 + x
  players: Record<Color, PlayerId>;
  turn: Color;
  phase: 'playing' | 'finished';
  moves: ({ kind: 'place'; color: Color; x: number; y: number; flipped: number[]; auto: boolean }
        | { kind: 'pass'; color: Color })[];   // 자동 패스도 기록(무르기·리플레이용)
  clock: { remainingMs: Record<Color, number>; turnStartedAt: number; autoMoveStreak: Record<Color, number> };
  undo: { usedBy: Record<Color, number>; pending: { by: Color; plies: number } | null };
  draw: { pendingBy: Color | null; lastOfferPly: Record<Color, number> };
  result: OthelloResult | null;
  rng: RngState;
}

8.2 액션

type payload 누가 / 언제 검증 조건
place { x, y } 차례인 플레이어, playing 판 안, 빈칸, 뒤집히는 돌이 1개 이상("여기에 두면 뒤집을 돌이 없어요")
requestUndo {} 플레이어 횟수 남음, 대기 없음, 본인 착수 존재. 무를 범위 = 기록의 끝에서부터 본인의 마지막 place까지(그 사이 자동 패스 포함)
respondUndo { accept } 상대 수락 시 moves를 처음부터 재생해 판 복원, 차례 = 요청자. 시계 유지
offerDraw / respondDraw / cancelOffer 플레이어 거절 후 본인 3수 이후 재제안
resign {} 플레이어 언제든
timeout {} 시스템 8.5
  • place 처리 후: 상대에게 합법 수가 있으면 차례 넘김. 없고 나에게 있으면 상대 자동 패스 기록 후 다시 내 차례. 둘 다 없으면 종료.
  • 시계: fischer는 착수 시 차감 후 증가분 가산. 자동 패스는 시간이 들지 않으며 다시 차례가 온 플레이어의 turnStartedAt = now.
  • 이벤트: placed(뒤집힌 칸 목록 — 애니메이션용), passed, gameEnded.

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

  • 공통 공개: 판, 차례, 돌 수, 기보, 마지막 수, 시계, deadline, 대기 요청, 결과.
  • 차례인 플레이어에게만: legalMoves: { x, y, flips: number }[] (showLegalMoves/showFlipCount 옵션에 따라 클라이언트가 표시).
  • 관전자: legalMoves 없음(해설 모드 필요 시 추후).
  • 제외: rng.

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

  1. colorAssignment=random.
  2. timeoutPolicy=autoMove의 자동 착수: 모서리(a1, h1, a8, h8)가 합법이면 그중 무작위, 아니면 전체 합법 수 중 균등 무작위. 모서리 우선은 시간 초과 당한 쪽이 지나치게 불리해지지 않게 하는 최소한의 배려.

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

  • deadline(state): perMove → turnStartedAt + perMoveSec*1000, fischer → turnStartedAt + remainingMs[turn], none → turnStartedAt + idleLimitSec*1000, 종료 → null.
  • onTimeout(state, player) → { type: 'timeout' }. apply:
    • perMove + autoMove: 자동 착수(auto: true), streak +1, 3연속이면 시간패.
    • 그 외: 시간패.
  • 연결 끊김: 끊긴 플레이어 차례에 min(deadline, 끊김 + disconnectGraceSec)에 onTimeout.

8.6 종료 조건과 결과(GameResult)

interface OthelloResult {
  ranking: { playerId: PlayerId; rank: 1 | 2 }[];
  winner: Color | null;
  reason: 'noMoves' | 'resign' | 'timeLoss' | 'drawAgreed';
  discs: { black: number; white: number; empty: number };
  displayScore: { black: number; white: number };   // emptyToWinner 반영
  summaryKo: string;                                // "흑 승 40 : 24"
}

9. UI/UX

  • 모바일 세로: 상단 상대(돌 수, 시계), 가운데 8×8 판(폰 360px에서 칸 약 44px — 칸 전체가 터치 영역, 48px에 근접하므로 한 번 탭 착수 허용), 하단 내 정보와 버튼.
  • 둘 수 있는 칸은 작은 반투명 점. showFlipCount면 숫자.
  • 착수 시 뒤집히는 돌을 놓은 돌에서 가까운 순서로 순차 회전 애니메이션(전체 400ms 이내).
  • 자동 패스 시 "둘 곳이 없어서 차례가 넘어갔어요" 토스트(상대 화면에도).
  • 돌 수 막대(흑/백 비율)를 상단에 상시 표시.
  • 규칙 보기: 끼워서 뒤집기 3단계 그림, 패스 설명, 모서리 팁(선택).
  • PC: 판 좌측, 기보·채팅 우측.

10. 테스트 체크리스트

  • 초기 배치(d4 백, e4 흑, d5 흑, e5 백)와 흑의 첫 합법 수 4개(d3, c4, f5, e6).
  • 8방향 각각 뒤집기, 한 수에 여러 방향 동시 뒤집기.
  • 연쇄 뒤집기 없음(뒤집힌 돌이 만든 새 끼움은 뒤집지 않음).
  • 뒤집을 돌이 없는 빈칸 착수 거부, 판 밖·이미 찬 칸 거부.
  • 자동 패스: 상대 합법 수 없음 → 패스 기록 후 내 차례 유지.
  • 양쪽 합법 수 없음 → 판이 다 차지 않아도 종료(예: 한 색이 전멸).
  • 동점 무승부, emptyToWinner 점수 표시(승리·무승부 각각).
  • 무르기: 자동 패스가 끼어 있는 경우에도 정확히 본인 마지막 착수 전으로 복원.
  • autoMove: 모서리 우선, 같은 시드에서 같은 선택, 3연속 시간패.
  • fischer 시간패, 증가분 가산.
  • 연결 끊김 유예 후 처리, 재접속 시 해제.
  • 정보 누출: 관전자·비차례 플레이어 view에 legalMoves 없음, 모든 view에 rng 없음.
  • 리플레이 결정성.

11. 참고 자료

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

  • "Othello"/"오셀로"는 상표(현재 일본 MegaHouse 및 Othello Co. 계열이 보유)이므로 서비스 표시 이름은 일반 명칭 "리버시"를 권장한다. 규칙 설명에서 "오셀로와 같은 규칙" 정도의 비교 서술은 지명적 사용으로 보통 허용되나, 로고·트레이드 드레스(초록 판 + 특정 디자인) 모방은 피한다. 최종 표시 이름은 사용자 결정 사항.
  • 게임 id othello는 내부 식별자일 뿐 UI에 노출하지 않는다(필요하면 reversi로 변경 가능).
  • 금전·베팅 요소 없음.