게임 4개 원래 이름으로, .5 + NPMplus 배포 구성

- 보석 상인 → 스플렌더, 벽 쌓기 길찾기 → 쿼리도, 숫자 타일 → 루미큐브, 주사위 땅따먹기 → 부루마불
- deploy/docker-compose.proxy.yml: Caddy 없이 앱만(.5:2970), HTTPS·헤더는 NPMplus
- deploy/npmplus-advanced.conf: Caddyfile과 같은 보안 헤더
- 문서: 2026-10-06 결정, 배포 7절 (e2e 구십구 0 카드 방향 버튼 반영 포함)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
EJClaw
2026-10-06 22:16:32 +09:00
parent 7da06d1b27
commit 6390e3e64c
45 changed files with 143 additions and 78 deletions

View File

@@ -1,5 +1,5 @@
/**
* 주사위 땅따먹기 판 데이터 (docs/games/marble.md §3). Pure data + price helpers, no zod,
* 부루마불 판 데이터 (docs/games/marble.md §3). Pure data + price helpers, no zod,
* so it stays cheap to import. Every number here is copied from the doc tables (tests compare
* them with the §3.2 formulas).
*/

View File

@@ -1,5 +1,5 @@
/**
* 주사위 땅따먹기 computer player: buys and builds while it keeps a cash reserve against the tolls ahead, sells the
* 부루마불 computer player: buys and builds while it keeps a cash reserve against the tolls ahead, sells the
* cheapest thing that covers a debt, escapes the island early in the game, flies to the best square on 우주여행,
* never proposes trades and only accepts clearly good ones.
*/

View File

@@ -1,4 +1,4 @@
/** 주사위 땅따먹기 (docs/games/marble.md). Hidden info: the 행운카드 deck order (only its size is public). */
/** 부루마불 (docs/games/marble.md). Hidden info: the 행운카드 deck order (only its size is public). */
import type { ZodType } from 'zod';
import { fail, ok, type GameDefinition, type GameEvent, type GameResult, type PlayerId, type Validation } from '@bg/engine';
import {
@@ -246,7 +246,7 @@ function spacePreview(s: MarbleState, id: PlayerId): MarbleSpacePreview[] {
export const marble: GameDefinition<MarbleState, MarbleAction, MarbleView, MarbleOptions> = {
id: 'marble',
nameKo: '주사위 땅따먹기',
nameKo: '부루마불',
minPlayers: 2,
maxPlayers: 4,
stateVersion: 1,

View File

@@ -1,5 +1,5 @@
/**
* 주사위 땅따먹기 상태 기계 (docs/games/marble.md §5, §8). Functions here mutate a state that
* 부루마불 상태 기계 (docs/games/marble.md §5, §8). Functions here mutate a state that
* `apply` has already cloned. The RNG is only used for dice (ROLL) — the deck is shuffled once in
* setup and never again (cards go back to the bottom).
*/

View File

@@ -1,5 +1,5 @@
/**
* 벽 쌓기 길찾기 판 계산 (docs/games/quoridor.md §3, §5): 좌표, 벽 충돌, 말 이동, BFS 경로.
* 쿼리도 판 계산 (docs/games/quoridor.md §3, §5): 좌표, 벽 충돌, 말 이동, BFS 경로.
* 칸 (c, r) 0~8, r=0이 1행(아래). 벽 앵커 (c, r) 0~7은 칸 (c,r)(c+1,r)(c,r+1)(c+1,r+1)의 가운데 교차점.
*/
export type Seat = 'S' | 'W' | 'N' | 'E';

View File

@@ -1,4 +1,4 @@
/** 벽 쌓기 길찾기 computer player: shortest path, winning step, walls a rival who is about to win. */
/** 쿼리도 computer player: shortest path, winning step, walls a rival who is about to win. */
import { describe, expect, test } from 'bun:test';
import { SeededRng } from '@bg/engine';
import { decideBotAction } from '../bots/index';

View File

@@ -1,5 +1,5 @@
/**
* 벽 쌓기 길찾기 computer player: step onto the goal when possible, otherwise walk the shortest path; when the
* 쿼리도 computer player: step onto the goal when possible, otherwise walk the shortest path; when the
* leading rival would get home first, put up the wall that delays that rival most compared to itself.
*/
import type { BotStrategy } from '../bots/types';

View File

@@ -1,4 +1,4 @@
/** 벽 쌓기 길찾기 (docs/games/quoridor.md). 내부 id 'quoridor'는 화면에 보이지 않는다(§12). */
/** 쿼리도 (docs/games/quoridor.md). 내부 id 'quoridor'는 화면에 보이지 않는다(§12). */
import { z } from 'zod';
import { fail, nextSeat, ok, type GameDefinition, type GameEvent, type GameResult, type PlayerId, type Rng } from '@bg/engine';
import {
@@ -504,7 +504,7 @@ function chargeClock(s: QuoridorState, i: number, now: number): boolean {
export const quoridor: GameDefinition<QuoridorState, QuoridorAction, QuoridorView, QuoridorOptions> = {
id: 'quoridor',
nameKo: '벽 쌓기 길찾기',
nameKo: '쿼리도',
minPlayers: 2,
maxPlayers: 4,
stateVersion: 1,

View File

@@ -1,5 +1,5 @@
/**
* 숫자 타일 computer player. A turn is two decisions: first one `setDraft` with the whole plan (rack-only sets, then
* 루미큐브 computer player. A turn is two decisions: first one `setDraft` with the whole plan (rack-only sets, then
* single tiles laid off on table / new sets), then `commit` once the view says the draft is valid. If the draft is
* not valid (should not happen) it draws, which also throws the draft away — it never leaves a half-built draft.
* Table sets are never rearranged beyond adding tiles to their ends.

View File

@@ -1,4 +1,4 @@
/** 숫자 타일 (루미큐브 방식, docs/games/rummikub.md). Scores are room-only result points (§6, §12). */
/** 루미큐브 (docs/games/rummikub.md). Scores are room-only result points (§6, §12). */
import { z } from 'zod';
import { fail, ok, type GameDefinition, type GameEvent, type GameResult, type PlayerId, type Rng, type Validation } from '@bg/engine';
import {
@@ -525,7 +525,7 @@ function describeSet(d: DraftSet, n: NormalSet | null): DraftSetView {
export const rummikub: GameDefinition<RummikubState, RummikubAction, RummikubView, RummikubOptions> = {
id: 'rummikub',
nameKo: '숫자 타일',
nameKo: '루미큐브',
minPlayers: 2,
maxPlayers: 4,
stateVersion: 1,

View File

@@ -1,5 +1,5 @@
/**
* 숫자 타일 (docs/games/rummikub.md §3, §5.1, §5.5): tile ids, set validation / joker interpretation,
* 루미큐브 (docs/games/rummikub.md §3, §5.1, §5.5): tile ids, set validation / joker interpretation,
* scoring helpers and simple "what can I play from my rack" search (hint button + random playouts).
* Pure functions without zod so the screen can import them cheaply.
*/

View File

@@ -1,5 +1,5 @@
/**
* 보석 상인 computer player: buys the best affordable card (points first, then bonuses that nobles and
* 스플렌더 computer player: buys the best affordable card (points first, then bonuses that nobles and
* wanted cards need); otherwise picks a target card (value per missing gem) and takes gems toward it,
* reserving it now and then. Uses only its own view (board, nobles, its tokens / bonuses / reservations).
*/

View File

@@ -1,5 +1,5 @@
/**
* 보석 상인 component data (docs/games/splendor.md §3.1, §3.2). Kept in a separate data file so the card
* 스플렌더 component data (docs/games/splendor.md §3.1, §3.2). Kept in a separate data file so the card
* list can be swapped for an own list with the same structure (§12). Encoding: `<id>:<points>:<W U G R K cost digits>`.
*/
export const COLORS = ['W', 'U', 'G', 'R', 'K'] as const;

View File

@@ -1,5 +1,5 @@
/**
* 보석 상인 (스플렌더 방식, docs/games/splendor.md). Pure rules module: all randomness is in setup
* 스플렌더 (docs/games/splendor.md). Pure rules module: all randomness is in setup
* (turn order, nobles, decks), state is plain JSON, the view hides deck contents and blind reservations.
*/
import { z } from 'zod';
@@ -656,7 +656,7 @@ function meView(s: SplendorState, me: PlayerId): SplendorMeView {
export const splendor: GameDefinition<SplendorState, SplendorAction, SplendorView, SplendorOptions> = {
id: 'splendor',
nameKo: '보석 상인',
nameKo: '스플렌더',
minPlayers: 2,
maxPlayers: 4,
stateVersion: 1,

View File

@@ -1,4 +1,4 @@
/** Random playouts for 보석 상인 (docs/09 §7): 1,000 seeds plus a few full engine randomPlayout runs. */
/** Random playouts for 스플렌더 (docs/09 §7): 1,000 seeds plus a few full engine randomPlayout runs. */
import { describe, expect, test } from 'bun:test';
import { SeededRng, randomPlayout, seedN, type PlayerId } from '@bg/engine';
import { COLORS, NOBLES, TIERS, componentCounts, splendor, type SplendorAction, type SplendorOptions, type SplendorState, type Tokens } from './index';

View File

@@ -21,16 +21,16 @@ export const CATALOG: CatalogEntry[] = [
{ id: 'yacht', nameKo: '요트 다이스', minPlayers: 1, maxPlayers: 8, minutes: '10~30', level: '쉬움', blurb: '주사위 다섯 개로 족보 만들기 · 혼자서도 OK', available: true },
{ id: 'yut', nameKo: '윷놀이', minPlayers: 2, maxPlayers: 8, minutes: '15~25', level: '쉬움', blurb: '도·개·걸·윷·모! 개인전 또는 팀전', available: true },
{ id: 'one-card', nameKo: '원카드', minPlayers: 2, maxPlayers: 10, minutes: '10~15', level: '쉬움', blurb: '같은 무늬·숫자를 내고 공격 카드로 먹이기! · 색깔 카드 모드', available: true },
{ id: 'rummikub', nameKo: '숫자 타일', minPlayers: 2, maxPlayers: 4, minutes: '30~60', level: '보통', blurb: '같은 숫자, 이어지는 숫자로 묶어 먼저 다 내려놓기!', available: true },
{ id: 'rummikub', nameKo: '루미큐브', minPlayers: 2, maxPlayers: 4, minutes: '30~60', level: '보통', blurb: '같은 숫자, 이어지는 숫자로 묶어 먼저 다 내려놓기!', available: true },
{ id: 'gostop', nameKo: '고스톱', minPlayers: 2, maxPlayers: 5, minutes: '20~40', level: '보통', blurb: '같은 월을 맞춰 먹고 고·스톱! 2인 맞고·3인·광팔기 · 놀이용 칩', available: true },
{ id: 'bang', nameKo: '뱅!', minPlayers: 4, maxPlayers: 7, minutes: '20~40', level: '어려움', blurb: '보안관과 무법자, 숨은 정체를 추리하며 쏘고 피하기!', available: true },
{ id: 'davinci-code', nameKo: '다빈치 코드', minPlayers: 2, maxPlayers: 4, minutes: '10~15', level: '쉬움', blurb: '검은·흰 숫자 타일의 숫자를 추리해 맞히기', available: true },
{ id: 'mahjong', nameKo: '마작', minPlayers: 3, maxPlayers: 4, minutes: '25~50', level: '어려움', blurb: '리치 마작 4인 · 3인 산마 · 초보 모드로 대기패 힌트', available: true },
{ id: 'halli-galli', nameKo: '할리갈리', minPlayers: 2, maxPlayers: 6, minutes: '5~15', level: '쉬움', blurb: '한 과일이 딱 5개가 되면 누구보다 먼저 종을 땡!', available: true },
{ id: 'marble', nameKo: '주사위 땅따먹기', minPlayers: 2, maxPlayers: 4, minutes: '30~50', level: '쉬움', blurb: '주사위로 세계 도시를 돌며 땅 사고 건물 짓기 · 놀이용 포인트', available: true },
{ id: 'marble', nameKo: '부루마불', minPlayers: 2, maxPlayers: 4, minutes: '30~50', level: '쉬움', blurb: '주사위로 세계 도시를 돌며 땅 사고 건물 짓기 · 놀이용 포인트', available: true },
{ id: 'dalmuti', nameKo: '달무티', minPlayers: 4, maxPlayers: 8, minutes: '20~40', level: '쉬움', blurb: '낮은 숫자일수록 강해요! 먼저 다 털고 신분을 올리기', available: true },
{ id: 'splendor', nameKo: '보석 상인', minPlayers: 2, maxPlayers: 4, minutes: '20~40', level: '보통', blurb: '보석을 모아 카드를 사고, 할인으로 더 비싼 카드까지! 귀족 방문은 덤', available: true },
{ id: 'quoridor', nameKo: '벽 쌓기 길찾기', minPlayers: 2, maxPlayers: 4, minutes: '10~20', level: '쉬움', blurb: '말을 움직이거나 벽을 세워 먼저 반대편 끝줄로! · 2명 또는 4명', available: true },
{ id: 'splendor', nameKo: '스플렌더', minPlayers: 2, maxPlayers: 4, minutes: '20~40', level: '보통', blurb: '보석을 모아 카드를 사고, 할인으로 더 비싼 카드까지! 귀족 방문은 덤', available: true },
{ id: 'quoridor', nameKo: '쿼리도', minPlayers: 2, maxPlayers: 4, minutes: '10~20', level: '쉬움', blurb: '말을 움직이거나 벽을 세워 먼저 반대편 끝줄로! · 2명 또는 4명', available: true },
{ id: 'las-vegas', nameKo: '라스베가스', minPlayers: 2, maxPlayers: 5, minutes: '20~30', level: '쉬움', blurb: '주사위를 같은 숫자 카지노에 몽땅! 개수가 같으면 둘 다 꽝 · 놀이용 지폐', available: true },
{ id: 'cockroach-poker', nameKo: '바퀴벌레 포커', minPlayers: 2, maxPlayers: 6, minutes: '15~20', level: '쉬움', blurb: '벌레 카드를 건네며 "이건 쥐야!" 진짜일까 거짓말일까?', available: true },
{ id: 'ninety-nine', nameKo: '구십구', minPlayers: 2, maxPlayers: 10, minutes: '5~15', level: '쉬움', blurb: '카드를 내며 숫자를 더해요. 99를 넘기면 탈락!', available: true },