# 리버시 (오셀로) (`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개(엔진에서는 개수 제한 없음 — 칸 수가 상한). - 좌표: 열 a~h(왼→오), 행 1~8(위→아래, 오셀로 표준 표기). 내부 `(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) ```ts type Color = 'black' | 'white'; type Cell = 0 | 1 | 2; interface OthelloState { options: OthelloOptions; board: Cell[]; // 64, index = y*8 + x players: Record; 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; turnStartedAt: number; autoMoveStreak: Record }; undo: { usedBy: Record; pending: { by: Color; plies: number } | null }; draw: { pendingBy: Color | null; lastOfferPly: Record }; 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) ```ts 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. 참고 자료 - World Othello Federation 공식 규칙: https://www.worldothello.org/about/about-othello/othello-rules/official-rules/english (초기 배치, 흑 선, 합법 수, 패스, 종료, 다수 승리) - Wikipedia "Reversi": https://en.wikipedia.org/wiki/Reversi (오셀로/리버시 역사, 대회의 빈칸 승자 귀속 관행) - 위키백과 「오셀로 (보드 게임)」: https://ko.wikipedia.org/wiki/%EC%98%A4%EC%85%80%EB%A1%9C_(%EB%B3%B4%EB%93%9C_%EA%B2%8C%EC%9E%84) ## 12. 메모 (상표·법적 주의 등) - "Othello"/"오셀로"는 상표(현재 일본 MegaHouse 및 Othello Co. 계열이 보유)이므로 서비스 표시 이름은 일반 명칭 "리버시"를 권장한다. 규칙 설명에서 "오셀로와 같은 규칙" 정도의 비교 서술은 지명적 사용으로 보통 허용되나, 로고·트레이드 드레스(초록 판 + 특정 디자인) 모방은 피한다. 최종 표시 이름은 사용자 결정 사항. - 게임 id `othello`는 내부 식별자일 뿐 UI에 노출하지 않는다(필요하면 `reversi`로 변경 가능). - 금전·베팅 요소 없음.