feat(bgm): add surface mounting and setup surfaces
This commit is contained in:
@@ -46,6 +46,13 @@ type: board
|
||||
id: harbor
|
||||
role: surface
|
||||
size: [300, 200]
|
||||
mount:
|
||||
kind: table
|
||||
x: 0
|
||||
y: 0
|
||||
rotation: 0
|
||||
children:
|
||||
- board#player
|
||||
layout:
|
||||
- route: /dock/:seat
|
||||
candidates:
|
||||
@@ -56,6 +63,29 @@ layout:
|
||||
rotation: 0
|
||||
```
|
||||
|
||||
```yaml file=parts/player.yaml
|
||||
type: board
|
||||
id: player
|
||||
role: surface
|
||||
size: [200, 200]
|
||||
mount:
|
||||
kind: child
|
||||
x: 100
|
||||
y: 50
|
||||
rotation: 0
|
||||
layout:
|
||||
- route: /hand/:slot
|
||||
candidates:
|
||||
$variants: ./hand.csv
|
||||
```
|
||||
|
||||
```csv file=parts/hand.csv
|
||||
slot,x,y,rotation
|
||||
string,number,number,number
|
||||
0,0,0,0
|
||||
1,0,20,0
|
||||
```
|
||||
|
||||
```csv file=parts/seats.csv
|
||||
seat,x,y,rotation
|
||||
string,number,number,number
|
||||
@@ -69,6 +99,9 @@ string,number,number,number
|
||||
role: setup
|
||||
type: game
|
||||
id: main
|
||||
surfaces:
|
||||
- board#harbor
|
||||
- board#player
|
||||
setup:
|
||||
/dock/0: harbor:token#wood
|
||||
/deck: harbor:token#grain
|
||||
|
||||
@@ -26,10 +26,12 @@ describe('collectPackages', () => {
|
||||
expect(wood.face).toBe('./assets/tokens.png');
|
||||
expect(wood.faceCrop).toEqual([1, 0, 5, 2]);
|
||||
|
||||
// One surface with a $variants-expanded candidates list.
|
||||
expect([...harbor.surfaces.keys()]).toEqual(['board#harbor']);
|
||||
// Two surfaces: the table board and its child player board.
|
||||
expect([...harbor.surfaces.keys()].sort()).toEqual(['board#harbor', 'board#player']);
|
||||
const board = harbor.surfaces.get('board#harbor')!;
|
||||
expect(board.size).toEqual([300, 200]);
|
||||
expect(board.mount).toEqual({ kind: 'table', x: 0, y: 0, rotation: 0 });
|
||||
expect(board.children).toEqual(['board#player']);
|
||||
expect(board.layout).toHaveLength(2);
|
||||
const dock = board.layout[0]!;
|
||||
expect(dock.route).toBe('/dock/:seat');
|
||||
@@ -41,9 +43,14 @@ describe('collectPackages', () => {
|
||||
expect(deck.route).toBe('/deck');
|
||||
expect(deck).toMatchObject({ x: -100, y: 0, rotation: 0 });
|
||||
|
||||
// One setup.
|
||||
const player = harbor.surfaces.get('board#player')!;
|
||||
expect(player.mount).toEqual({ kind: 'child', x: 100, y: 50, rotation: 0 });
|
||||
expect(player.layout).toHaveLength(1);
|
||||
|
||||
// One setup, declaring the enabled surfaces.
|
||||
expect([...harbor.setups.keys()]).toEqual(['game#main']);
|
||||
const setup = harbor.setups.get('game#main')!;
|
||||
expect(setup.surfaces).toEqual(['board#harbor', 'board#player']);
|
||||
expect(setup.setup).toEqual({
|
||||
'/dock/0': 'harbor:token#wood',
|
||||
'/deck': 'harbor:token#grain',
|
||||
|
||||
@@ -40,16 +40,27 @@ const partSchema = z.object({
|
||||
fillet: z.number().optional(),
|
||||
});
|
||||
|
||||
const surfaceMount = z.object({
|
||||
kind: z.enum(['table', 'hud', 'child']),
|
||||
x: z.number().optional(),
|
||||
y: z.number().optional(),
|
||||
rotation: z.number().optional(),
|
||||
area: z.string().optional(),
|
||||
});
|
||||
|
||||
const surfaceSchema = z.object({
|
||||
type: z.string().min(1),
|
||||
id: z.string().min(1),
|
||||
size: surfaceSize.optional(),
|
||||
mount: surfaceMount.optional(),
|
||||
children: z.array(z.string()).optional(),
|
||||
layout: z.array(route),
|
||||
});
|
||||
|
||||
const setupSchema = z.object({
|
||||
type: z.string().min(1),
|
||||
id: z.string().min(1),
|
||||
surfaces: z.array(z.string()).optional(),
|
||||
setup: z.record(z.string(), z.union([z.string(), z.array(z.string())])),
|
||||
});
|
||||
|
||||
|
||||
@@ -104,21 +104,44 @@ export interface Stacking {
|
||||
steps?: number;
|
||||
}
|
||||
|
||||
/** How a surface is mounted. `kind` selects the mount type. */
|
||||
export type SurfaceMountKind = 'table' | 'hud' | 'child';
|
||||
|
||||
/**
|
||||
* How a surface is mounted. Always an object, anchored by `x`/`y`/`rotation`
|
||||
* like a route. `table` is the root table surface; `hud` mounts to a HUD
|
||||
* area; `child` mounts relative to a parent surface (see `children`).
|
||||
*/
|
||||
export interface SurfaceMount {
|
||||
kind: SurfaceMountKind;
|
||||
x?: number;
|
||||
y?: number;
|
||||
rotation?: number;
|
||||
/** HUD area, for `kind: hud`. */
|
||||
area?: string;
|
||||
}
|
||||
|
||||
/** A view over the state store, purely for visual rendering. */
|
||||
export interface Surface {
|
||||
type: string;
|
||||
id: string;
|
||||
/** Reference `[width, height]` in mm; may be scaled to fit the table. */
|
||||
size?: SurfaceSize;
|
||||
/** How this surface is mounted; defaults to `{ kind: 'table' }`. */
|
||||
mount?: SurfaceMount;
|
||||
/** Child surfaces (`type#id` refs), mounted relative to this surface. */
|
||||
children?: string[];
|
||||
layout: Route[];
|
||||
}
|
||||
|
||||
export type SetupValue = string | string[];
|
||||
|
||||
/** Seeds the state store: a map from path to a stack of parts. */
|
||||
/** Seeds the state store: the enabled surfaces and a map from path to parts. */
|
||||
export interface Setup {
|
||||
type: string;
|
||||
id: string;
|
||||
/** Surfaces (`type#id` refs) enabled at the start; omitted = all enabled. */
|
||||
surfaces?: string[];
|
||||
setup: Record<string, SetupValue>;
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ describe('bgm vite plugin', () => {
|
||||
|
||||
// Consumers read the collections with Object.values / Object.keys.
|
||||
expect(Object.values(pkg.parts).map((p: any) => p.id).sort()).toEqual(['grain', 'wood']);
|
||||
expect(Object.keys(pkg.surfaces)).toEqual(['board#harbor']);
|
||||
expect(Object.keys(pkg.surfaces)).toEqual(['board#harbor', 'board#player']);
|
||||
expect(Object.keys(pkg.setups)).toEqual(['game#main']);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user