게임 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';