HOLDEN = Texas Hold'em 에서 따온 이름. 포커 프리플랍 의사결정을 연습하고, 내 누수(leak)를 데이터로 분석해 어디가 약한지 보여주는 풀스택 학습 도구.
상용 트레이너(GTO Wizard 등)는 비싸고, 무료 도구는 대부분 "정답 채점"에서 끝난다. 한두 판은 재밌지만 계속 돌아올 이유가 없다. 사람을 붙잡는 건 UI가 아니라 성장 체감이라고 생각해서, 이 프로젝트는 단순 채점기가 아니라 누수 분석을 코어로 잡았다.
- 한 판은 "맞췄나?" — 즉시 채점 + "왜 이 핸드는 폴드/레이즈인가" 원리 설명
- 여러 판은 "나는 어떤 플레이어인가?" — "너는 과하게 루즈해. 폴드해야 할 18번 중 9번을 레이즈했어"
- 약점 포지션, 틀린 핸드 다시 풀기, XP/레벨로 성장이 보이게
즉 내 의사결정 패턴을 데이터로 모델링하는 게 목표고, 포커는 정답이 존재하는 닫힌 도메인이라 그 모델링을 연습하기 가장 깨끗한 영역이라 골랐다.
| 🎯 트레이너 | 6-max / 9-max, 스택 깊이(100/50/30BB)별 프리플랍 RFI 채점 |
| 📊 누수 리포트 | 성향(루즈/타이트), 실수 TOP 3, 포지션 마스터리, XP/레벨 — 서버에서 계산 |
| 🔁 복습 모드 | 틀린 핸드만 컨텍스트째 다시 출제 |
| 💡 "왜?" 설명 | 포지션·핸드 카테고리·스택 깊이 기반 정직한 원리 (지어낸 솔버 EV 아님) |
| 🃏 레인지 표 | 13×13 핸드 차트에 내 핸드 하이라이트 |
| 🔌 graceful degradation | 백엔드가 꺼져도 트레이너 자체는 동작 |
┌──────────────────────────┐ REST / JSON ┌───────────────────────────┐
│ Next.js 16 · TypeScript │ ──────────────────────▶ │ Spring Boot 4 · Java 24 │
│ · 트레이너 루프 │ POST /api/hands │ HandController │
│ · 레인지 / 설명 로직 │ GET /api/hands/report │ HandLogService │
│ · 누수 대시보드 UI │ GET /api/hands/mistakes│ StatsService (누수 분석) │
│ │ ◀────────────────────── │ JPA · HandLog 엔티티 │
└──────────────────────────┘ └─────────────┬─────────────┘
│
┌─────▼─────┐
│ H2 (파일) │
└───────────┘
설계 포인트: 누수 분석(성향·실수 TOP·마스터리·XP) 계산을 프론트가 아니라 StatsService(서버) 에서 한다.
단순 CRUD 가 아니라 도메인 로직이 백엔드에 있는 구조. 매 핸드는 DB 행 하나로 append-only 로 쌓이고,
리포트는 그 원본 행들을 매번 집계해서 만든다(집계값을 따로 저장하지 않아 항상 최신).
- 프론트: Next.js 16 (App Router), TypeScript, CSS (글래스모피즘 다크 UI)
- 백엔드: Spring Boot 4, Java 24, Spring Data JPA, Bean Validation
- DB: H2 (파일 모드 — 외부 설치 불필요)
- 테스트: Vitest (도메인 규칙 단위 테스트 13개)
- 빌드: Gradle 9 (wrapper), npm
백엔드 (포트 8080)
cd backend
./gradlew bootRun프론트엔드 (포트 3000)
cd frontend
npm install
npm run dev테스트
cd frontend && npm test # Vitest — 핸드 카테고리 판정 규칙 검증
cd backend && ./gradlew test # StatsService 누수 분석 로직 검증- 레인지 데이터는 표준 차트 근사치다. 솔버에서 받아온 정밀값이 아니다. 그래서 핸드별 "솔버 EV" 같은 가짜 수치를 지어내지 않고, 실제로 도출 가능한 원리만 설명에 넣었다. → 추후 오픈소스 솔버 export 로 교체.
- localStorage → DB. 처음엔 단일 HTML + localStorage 로 아이디어/UX 를 빠르게 검증한 뒤, 검증된 스펙을 풀스택으로 재구성했다.
- H2 파일 DB 로 외부 설치 없이 바로 실행. 운영 시 datasource URL 만 바꿔 Postgres 로 교체 가능.
- 인증 없음 (단일 사용자). 세션 식별자로 그룹핑만. 계정/동기화는 다음 단계.
- 도메인 규칙은 명시적으로. 핸드 카테고리(수티드/브로드웨이/커넥터 등) 판정을
lib/poker-rules.ts의 이름 붙은 술어로 분리하고 단위 테스트로 고정했다.
- vs-오픈 디펜스 모드 — 앞에서 레이즈가 나온 상황(폴드/콜/3벳) + Pot Odds 계산
- 깊이별·믹스 빈도를 실제 솔버 데이터로 교체
- 인증 + Postgres + 기기 간 동기화
- 배포 (Vercel + Railway)