Files
tetrig/README.md
tkrmagid cbd0f62319 Kotlin Multiplatform 네이티브 앱으로 전환 (Android + iOS 공용 코드)
TWA 래퍼를 걷어내고 Compose Multiplatform 으로 다시 만들었다. 엔진·UI·모드
전부 commonMain 한 벌이라 Android 와 iOS 가 같은 코드를 쓴다.

- engine/: 웹 JS 엔진을 Kotlin 으로 이식 (SRS+180 킥, 7-bag, T-spin, B2B,
  콤보, All Clear, 점수·레벨). commonTest 28개 통과
- game/: 모드 규칙 + 중력/락딜레이/DAS/ARR 타이밍
- ui/: 네오브루탈 컴포넌트, 메인/솔로시트/인게임/결과/설정
- ui/Responsive.kt: 고정 해상도를 버리고 가용 크기에서 셀 크기·배율 계산.
  폰 세로, 플립 초세로, 플립 커버(소형), 폴드 펼침, 태블릿/아이패드 가로 대응
- androidMain: resizeableActivity + configChanges 로 접기/펴기·회전 시 재시작 없음
- iosMain + iosApp/: MainViewController + SwiftUI 껍데기 + XcodeGen 설정
  (실제 iOS 빌드는 macOS + Xcode 필요)
- scripts/release.sh: 버전 상승 → 테스트 → 서명 빌드 → 태그 → Gitea 릴리스 자동화
2026-09-23 10:37:17 +09:00

190 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TETRIG (테트리그)
> 쌓아라, 부숴라, 올라가라.
TETR.IO 스타일의 **모바일 실시간 대전 블록 스태커** 웹 게임. 솔로 3모드 + 인원 무제한 커스텀 룸(FFA) + Glicko-2 랭크 리그전을 하나의 단일 HTML 클라이언트와 Cloudflare Workers 서버로 구현한다.
이 문서는 앱 제작을 위한 **최종 빌드 진입점(entry point)** 이다. 모든 설계 결정·수치·에셋의 **정본(source of truth)은 [`TETRIG-최종명세서.md`](./TETRIG-최종명세서.md)** 이며, 구현 시 반드시 명세서와 UX 목업을 함께 열어 작업한다.
> **비용 0 · 아이폰/안드로이드 모두 지원 (2026-08-22 확정)**: 명세서대로 **웹 게임 + PWA(홈화면 설치)** 로 간다. Cloudflare Pages(무료)에 배포하면 iPhone Safari·Android Chrome에서 그대로 돌아가고, "홈 화면에 추가"로 전체화면 앱처럼 실행된다(`manifest.webmanifest` + `sw.js` 오프라인 캐시 + 앱 아이콘). **맥·Xcode·앱스토어/구글플레이 등록비·Apple Developer 멤버십이 전혀 필요 없다.** (네이티브 iOS 앱만 유료·맥 필수라 웹/PWA 경로가 무료로 크로스플랫폼을 달성하는 유일한 방법이다.) 클라이언트 진입점은 [`index.html`](./index.html)(빌드 산출물), 소스는 [`src/`](./src), 서버 계획(Cloudflare Workers + D1 + WebSocket)은 명세서 그대로.
---
## 무엇을 만드는가
산출물은 딱 2개다.
1. **클라이언트** — 단일 HTML 파일 (HTML + CSS + JS, Canvas 2D). **외부 에셋 0개**, 사운드는 100% Web Audio 신스 생성.
2. **서버** — Cloudflare Workers 프로젝트 (wrangler 표준 구조: Durable Objects 2종 + D1 + Cron).
게임 구성:
- **솔로 3모드**: SPRINT(40줄 최속) · TIME ATTACK(2분 점수) · ENDLESS(무한)
- **커스텀 룸**: 6자리 초대 코드, 인원 무제한 FFA, 호스트 규칙 설정
- **리그전**: Glicko-2 레이팅 + 18단계 랭크 + 배치 10경기 + 시즌제
## 아키텍처
```
[모바일 브라우저] [Cloudflare]
클라이언트 (단일 HTML, Canvas) ⇄ WSS ⇄ Workers
├ 게임 엔진 (SRS+, 판정, 렌더) ├ MatchmakerDO (리그 매칭 큐, 싱글턴)
├ 솔로 모드 3종 ├ GameRoomDO (경기방 — 리그 & 커스텀 공용)
└ 멀티/커스텀/프로필/리더보드 UI ├ D1 (계정·레이팅·전적·리더보드)
└ Cron (RD 감쇠, 시즌 처리)
```
- 클라이언트: Cloudflare Pages(또는 Workers 정적 자산)로 호스팅, 시작 주소는 `tetrig.pages.dev`.
- 서버: 무료 티어로 운영 가능 (DO SQLite 백엔드 + WebSocket Hibernation).
- 로컬 개발: `wrangler dev` + 브라우저 탭 여러 개로 다인전 테스트.
## 저장소 파일
| 파일 | 용도 |
|---|---|
| `TETRIG-최종명세서.md` | **정본 명세서** — 모든 규칙·수치·좌표·스키마·프로토콜 |
| `tetrig-ux-mockup.png` | 9화면 UX 목업 (1390×2944) — **UI 구현의 시각 기준** |
| `tetrig-logo.svg` / `tetrig-logo.png` | 가로형 워드마크 로고 (실사용 에셋 / 렌더 미리보기) |
| `tetrig-icon.svg` / `tetrig-icon.png` | 앱 아이콘 512×512 (실사용 에셋 / 렌더 미리보기) |
명세서의 화면 번호 ①~⑨는 목업 시트의 화면과 1:1 대응한다. **시각 판단은 목업 이미지를 따르고**, 명세서 §11~§13의 좌표·팔레트 수치로 정확도를 보정한다.
## 핵심 규칙 요약 (상세는 명세서 §4~§8)
- **보드**: 10열 × 40행(내부), 하단 20행 표시. 랜덤 7-bag, 넥스트 5, 홀드 1회/피스, 고스트 피스.
- **회전**: SRS + 180° 킥테이블 (명세서 §4.1). 락 딜레이 500ms / 최대 15회 리셋.
- **판정**: T-spin / T-spin mini, B2B, 콤보, All Clear. 4줄 클리어는 **"QUAD"** (Tetris/테트리스 단어 금지).
- **대전**: 가비지 전송 테이블 + 상쇄(counter) + 8줄/회 상한 + 장기전 방지 배율.
- **리그**: Glicko-2 (R=1500, RD=350, σ=0.06, τ=0.5) → 표시용 TR(0~25,000) → 18단계 랭크. 배치 10경기, RD<100일 때만 랭크 표시.
- **매치메이킹**: MatchmakerDO 싱글턴 큐, R ± 2×RD 구간이 겹치면 성사. 형식은 높은 랭크 기준 A+ 이하 FT3 / S- 이상 FT5.
## 디자인 시스템 (NEOBRUTAL — 상세는 명세서 §13)
- **테마 이원화**: 메뉴/로비/결과는 BONE(#EDE7DA) 라이트, 인게임은 INK(#17161B) 다크.
- **컴포넌트**: 2px 잉크 보더 + 블러 0 하드 오프셋 섀도 + 우하단 챔퍼컷 + 고대비 플랫 컬러.
- **시그니처**: 45° 해저드 스트라이프(가비지·공격·광고·난투에만 절제 사용).
- **금지**: 다크 네이비 배경 · 네온 글로우 · 그라디언트 · 블러 섀도 · 과도한 라운딩.
- **피스 7색(v2 확정)**: I `#3EC1B6` / O `#F5C531` / T `#A76BF2` / S `#7FCC4C` / Z `#F25C4C` / J `#4C7DF2` / L `#F2913D` / 가비지 `#46464F`.
## 개발 로드맵 (이 순서로 진행)
각 Phase 완료 시 실행 가능한 파일 + 코어 로직 단위 테스트 통과를 완료 기준으로 삼고, 사용자 확인 후 다음으로 넘어간다.
| 단계 | 내용 | 완료 기준 |
|---|---|---|
| Phase 1 | 코어 엔진: 보드·피스·SRS+180 킥·7-bag·홀드·고스트·락딜레이·클리어 | 키보드 플레이 + 킥/판정 단위 테스트 통과 |
| Phase 2 | 판정·점수 + 솔로 3모드 + 솔로 결과 화면 | 3모드 정상 플레이·기록 저장 |
| Phase 3 | 가상 버튼 + 스와이프 + 핸들링 설정 + 메뉴 화면들 | 모바일 브라우저 실기 조작 확인 |
| Phase 4 | 서버 뼈대: 게스트/계정·기록 이전·GameRoomDO·WS·가비지 원장 + 커스텀 1:1 | 탭 2개로 코드 방 1:1 대전 성사 |
| Phase 5 | 커스텀 FFA: 다인 타겟팅·미니뷰 그리드·KO 관전·도중 입장·리매치 투표 | 탭 4개+ FFA 라운드 정상 진행 |
| Phase 6 | MatchmakerDO + Glicko-2 + TR/랭크 + 리더보드·프로필·전적 + 리그 결과 | 매칭→경기→TR 반영 전체 사이클 |
| Phase 7 | 연출·사운드·시즌·광고 슬롯 연결·재접속/부하 점검·폴리시 | v1 배포 (wrangler deploy + Pages) |
## 개발 환경
```bash
# 서버 (Cloudflare Workers)
npm create cloudflare@latest # wrangler 프로젝트 초기화
wrangler dev # 로컬 개발 서버 + WS
wrangler deploy # 배포
# 클라이언트
# 단일 HTML 파일 — 브라우저로 직접 열거나 Pages/Workers 정적 자산으로 서빙
```
준비물(필요 시점에 사용자에게 확인 — 명세서 §17):
- Google OAuth 클라이언트 ID (Phase 4)
- 애드센스 계정 ID (Phase 7)
## 배포
### 네이티브 앱 (Kotlin Multiplatform + Compose Multiplatform)
`app/` 이 실제 출시 대상이다. 엔진과 UI 전부 `commonMain` 한 벌이고
Android / iOS 가 그걸 공유한다.
```
app/composeApp/src/
commonMain/ 엔진(engine/) · 모드·타이밍(game/) · UI(ui/) · 저장소(data/)
commonTest/ 엔진 테스트 (Android/iOS 양쪽에서 동일하게 돈다)
androidMain/ MainActivity, 매니페스트, 아이콘, SharedPreferences 구현
iosMain/ MainViewController, NSUserDefaults 구현
app/iosApp/ SwiftUI 껍데기 + XcodeGen 설정
```
안드로이드 빌드 (이 저장소에서 바로 가능):
```bash
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export ANDROID_HOME=$HOME/android-sdk
cd app
cp keystore.properties.sample keystore.properties # 서명 키 경로/비번 입력
./gradlew :composeApp:testDebugUnitTest :composeApp:assembleRelease :composeApp:bundleRelease
# → composeApp/build/outputs/apk/release/composeApp-release.apk
# → composeApp/build/outputs/bundle/release/composeApp-release.aab
```
iOS 빌드 (**macOS + Xcode 필요**. 리눅스에서는 코드만 컴파일 검증되고 실제
프레임워크 빌드는 되지 않는다):
```bash
brew install xcodegen
cd app/iosApp && xcodegen generate && open iosApp.xcodeproj
# Xcode 에서 실행하면 prebuild 스크립트가
# ./gradlew :composeApp:embedAndSignAppleFrameworkForXcode 를 돌려
# ComposeApp.framework 를 만들어 링크한다.
```
App Store 출시에는 Apple Developer Program 등록(연 $99)과 맥이 필요하다.
### 기기 호환
고정 해상도를 전제하지 않고 `BoxWithConstraints` 로 실제 가용 크기에서
셀 크기·UI 배율을 계산한다 (`ui/Responsive.kt`).
| 폼팩터 | 동작 |
| --- | --- |
| 일반 폰 세로 | 보드 위 + 조작 아래 |
| 갤럭시 Z 플립 본화면(초세로) | 보드를 세로로 더 키움 |
| 갤럭시 Z 플립 커버(소형) | 축소 배율 + 넥스트 3개로 감축 |
| 갤럭시 Z 폴드 펼침(정사각형에 가까움) | 넓은 폭 레이아웃, 카드 가로 배치 |
| 태블릿 / 아이패드 가로 | 보드 가운데, 조작을 좌우 엄지 영역으로 분리 |
| 아이폰 | 동일 코드. 세이프에어리어 처리 |
접었다 펴기·회전은 액티비티 재시작 없이 처리한다
(`configChanges` + `resizeableActivity=true`).
### 버전 관리
- `app/gradle.properties` 의 `appVersionName`(semver) / `appVersionCode`(단조 증가)가 원본
- 빌드 시 `-PappVersionName` `-PappVersionCode` 로 덮어쓸 수 있다
- 릴리스는 스크립트 한 방:
```bash
scripts/release.sh patch # 또는 minor / major
```
버전 상승 → 테스트 → 서명 빌드 → 커밋 → `vX.Y.Z` 태그 → Gitea push →
릴리스 생성 + APK/AAB 업로드까지 자동으로 한다.
### 웹 (Cloudflare Pages)
`index.html` 기반 PWA 는 그대로 유지한다 (아이폰 미설치 사용자용 / 데모).
```bash
node build.js
wrangler pages deploy dist --project-name tetrig --branch main
```
## 구현 가드레일 (반드시 지킬 것)
- **IP 금지**: 사용자 노출 텍스트에 "Tetris/테트리스" 금지, 테트리스 음악(Korobeiniki 등) 금지, 표준 가이드라인 색상 1:1 복제 금지, TETR.IO 에셋·명칭 복제 금지.
- **BRAND 상수화**: 앱 이름·로고 문구는 코드 1곳(`const BRAND = "TETRIG"` / `BRAND_KO = "테트리그"`)에서 관리해 1분 내 리브랜딩 가능하게.
- **튜닝 수치 상수화**: 중력·TR 커브·콤보 테이블 등 조정 대상은 코드 상단 상수로 분리.
- **언어**: 사용자 노출 텍스트는 한국어 (게임 용어 SPRINT/HOLD 등 영문 허용).
- **테스트 필수**: 킥테이블·T-spin 판정은 반드시 jsdom/node 단위 테스트로 검증.
- **명세 준수**: 명세서에 값이 명시된 것은 그대로 구현하고, 없는 것만 질문한다.
## 라이선스 / 상표
앱 이름 "테트리그(TETRIG)"는 상표 유사성 리스크를 인지한 뒤 **스토어 출시·수익화(광고 게시)까지 최종 확정**한 이름이다(2026-08-21). 이름 재검토 트리거는 없다. `BRAND` 상수화는 리브랜딩 대비가 아니라 표기 일관성·유지보수를 위한 단일 소스로서 유지한다 (명세서 §2).