Compare commits

...

2 Commits

Author SHA1 Message Date
hypercross a8774f34bc refactor: make stuff peer dependencies 2026-04-06 12:38:24 +08:00
hypercross 49109963bc refactor: hide api 2026-04-06 12:05:11 +08:00
4 changed files with 36 additions and 25 deletions

14
package-lock.json generated
View File

@ -8,19 +8,23 @@
"name": "boardgame-core", "name": "boardgame-core",
"version": "1.0.0", "version": "1.0.0",
"license": "MIT", "license": "MIT",
"dependencies": { "devDependencies": {
"@preact/signals-core": "^1.5.1", "@preact/signals-core": "^1.5.1",
"inline-schema": "file:../inline-schema", "inline-schema": "file:../inline-schema",
"mutative": "^1.3.0" "mutative": "^1.3.0",
},
"devDependencies": {
"tsup": "^8.0.2", "tsup": "^8.0.2",
"typescript": "^5.3.3", "typescript": "^5.3.3",
"vitest": "^1.3.1" "vitest": "^1.3.1"
},
"peerDependencies": {
"@preact/signals-core": "^1.5.1",
"inline-schema": "file:../inline-schema",
"mutative": "^1.3.0"
} }
}, },
"../inline-schema": { "../inline-schema": {
"version": "1.0.0", "version": "1.0.0",
"dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"csv-parse": "^5.5.6" "csv-parse": "^5.5.6"
@ -534,6 +538,7 @@
"version": "1.14.0", "version": "1.14.0",
"resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.14.0.tgz", "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.14.0.tgz",
"integrity": "sha512-AowtCcCU/33lFlh1zRFf/u+12rfrhtNakj7UpaGEsmMwUKpKWMVvcktOGcwBBNiB4lWrZWc01LhiyyzVklJyaQ==", "integrity": "sha512-AowtCcCU/33lFlh1zRFf/u+12rfrhtNakj7UpaGEsmMwUKpKWMVvcktOGcwBBNiB4lWrZWc01LhiyyzVklJyaQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
@ -1511,6 +1516,7 @@
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/mutative/-/mutative-1.3.0.tgz", "resolved": "https://registry.npmjs.org/mutative/-/mutative-1.3.0.tgz",
"integrity": "sha512-8MJj6URmOZAV70dpFe1YnSppRTKC4DsMkXQiBDFayLcDI4ljGokHxmpqaBQuDWa4iAxWaJJ1PS8vAmbntjjKmQ==", "integrity": "sha512-8MJj6URmOZAV70dpFe1YnSppRTKC4DsMkXQiBDFayLcDI4ljGokHxmpqaBQuDWa4iAxWaJJ1PS8vAmbntjjKmQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=14.0" "node": ">=14.0"

View File

@ -18,12 +18,15 @@
"test:run": "vitest run", "test:run": "vitest run",
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "peerDependencies": {
"@preact/signals-core": "^1.5.1", "@preact/signals-core": "^1.5.1",
"inline-schema": "file:../inline-schema", "inline-schema": "file:../inline-schema",
"mutative": "^1.3.0" "mutative": "^1.3.0"
}, },
"devDependencies": { "devDependencies": {
"@preact/signals-core": "^1.5.1",
"inline-schema": "file:../inline-schema",
"mutative": "^1.3.0",
"tsup": "^8.0.2", "tsup": "^8.0.2",
"typescript": "^5.3.3", "typescript": "^5.3.3",
"vitest": "^1.3.1" "vitest": "^1.3.1"

View File

@ -1,23 +1,20 @@
import { ReadonlySignal, Signal } from '@preact/signals-core'; import { ReadonlySignal, Signal } from '@preact/signals-core';
import type { import {
CommandSchema, CommandSchema,
CommandRegistry, CommandRegistry,
PromptEvent, PromptEvent,
CommandRunnerContextExport,
} from '@/utils/command'; } from '@/utils/command';
import type { MutableSignal } from '@/utils/mutable-signal';
import {createGameCommandRegistry, createGameContext, IGameContext} from './game'; import {createGameCommandRegistry, createGameContext, IGameContext} from './game';
export type GameHostStatus = 'created' | 'running' | 'disposed'; export type GameHostStatus = 'created' | 'running' | 'disposed';
export class GameHost<TState extends Record<string, unknown>, TResult=unknown> { export class GameHost<TState extends Record<string, unknown>, TResult=unknown> {
readonly context: IGameContext<TState>; readonly state: ReadonlySignal<TState>;
readonly status: ReadonlySignal<GameHostStatus>; readonly status: ReadonlySignal<GameHostStatus>;
readonly activePromptSchema: ReadonlySignal<CommandSchema | null>; readonly activePromptSchema: ReadonlySignal<CommandSchema | null>;
readonly activePromptPlayer: ReadonlySignal<string | null>; readonly activePromptPlayer: ReadonlySignal<string | null>;
private _state: MutableSignal<TState>; private _context: IGameContext<TState>;
private _commands: CommandRunnerContextExport<IGameContext<TState>>;
private _start: (ctx: IGameContext<TState>) => Promise<TResult>; private _start: (ctx: IGameContext<TState>) => Promise<TResult>;
private _status: Signal<GameHostStatus>; private _status: Signal<GameHostStatus>;
private _activePromptSchema: Signal<CommandSchema | null>; private _activePromptSchema: Signal<CommandSchema | null>;
@ -35,8 +32,9 @@ export class GameHost<TState extends Record<string, unknown>, TResult=unknown> {
this._eventListeners = new Map(); this._eventListeners = new Map();
const initialState = createInitialState(); const initialState = createInitialState();
this.context = createGameContext(registry, initialState); this._context = createGameContext(registry, initialState);
this._start = start; this._start = start;
this.state = this._context._state;
this._status = new Signal<GameHostStatus>('created'); this._status = new Signal<GameHostStatus>('created');
this.status = this._status; this.status = this._status;
@ -47,22 +45,19 @@ export class GameHost<TState extends Record<string, unknown>, TResult=unknown> {
this._activePromptPlayer = new Signal<string | null>(null); this._activePromptPlayer = new Signal<string | null>(null);
this.activePromptPlayer = this._activePromptPlayer; this.activePromptPlayer = this._activePromptPlayer;
this._state = this.context._state;
this._commands = this.context._commands;
this._setupPromptTracking(); this._setupPromptTracking();
} }
private _setupPromptTracking() { private _setupPromptTracking() {
let currentPromptEvent: PromptEvent | null = null; let currentPromptEvent: PromptEvent | null = null;
this._commands.on('prompt', (e) => { this._context._commands.on('prompt', (e) => {
currentPromptEvent = e as PromptEvent; currentPromptEvent = e as PromptEvent;
this._activePromptSchema.value = currentPromptEvent.schema; this._activePromptSchema.value = currentPromptEvent.schema;
this._activePromptPlayer.value = currentPromptEvent.currentPlayer; this._activePromptPlayer.value = currentPromptEvent.currentPlayer;
}); });
this._commands.on('promptEnd', () => { this._context._commands.on('promptEnd', () => {
currentPromptEvent = null; currentPromptEvent = null;
this._activePromptSchema.value = null; this._activePromptSchema.value = null;
this._activePromptPlayer.value = null; this._activePromptPlayer.value = null;
@ -77,7 +72,7 @@ export class GameHost<TState extends Record<string, unknown>, TResult=unknown> {
if (this._isDisposed) { if (this._isDisposed) {
return 'GameHost is disposed'; return 'GameHost is disposed';
} }
return this._commands._tryCommit(input); return this._context._commands._tryCommit(input);
} }
/** /**
@ -85,7 +80,7 @@ export class GameHost<TState extends Record<string, unknown>, TResult=unknown> {
* @see MutableSignal.addInterruption * @see MutableSignal.addInterruption
*/ */
addInterruption(promise: Promise<void>): void { addInterruption(promise: Promise<void>): void {
this._state.addInterruption(promise); this._context._state.addInterruption(promise);
} }
/** /**
@ -93,7 +88,7 @@ export class GameHost<TState extends Record<string, unknown>, TResult=unknown> {
* @see MutableSignal.clearInterruptions * @see MutableSignal.clearInterruptions
*/ */
clearInterruptions(): void { clearInterruptions(): void {
this._state.clearInterruptions(); this._context._state.clearInterruptions();
} }
start(): Promise<TResult> { start(): Promise<TResult> {
@ -101,12 +96,12 @@ export class GameHost<TState extends Record<string, unknown>, TResult=unknown> {
throw new Error('GameHost is disposed'); throw new Error('GameHost is disposed');
} }
this._commands._cancel(); this._context._commands._cancel();
const initialState = this._createInitialState(); const initialState = this._createInitialState();
this._state.value = initialState as any; this._context._state.value = initialState as any;
const promise = this._start(this.context); const promise = this._start(this._context);
this._status.value = 'running'; this._status.value = 'running';
this._emitEvent('start'); this._emitEvent('start');
@ -120,7 +115,7 @@ export class GameHost<TState extends Record<string, unknown>, TResult=unknown> {
} }
this._isDisposed = true; this._isDisposed = true;
this._commands._cancel(); this._context._commands._cancel();
this._status.value = 'disposed'; this._status.value = 'disposed';
// Emit dispose event BEFORE clearing listeners // Emit dispose event BEFORE clearing listeners

View File

@ -10,11 +10,18 @@ import {
import { createGameHost, GameHost } from '@/core/game-host'; import { createGameHost, GameHost } from '@/core/game-host';
import type { PromptEvent } from '@/utils/command'; import type { PromptEvent } from '@/utils/command';
import { MutableSignal } from '@/utils/mutable-signal'; import { MutableSignal } from '@/utils/mutable-signal';
import {IGameContext} from "../../src";
type TestGameHost = GameHost<TicTacToeState> & {
_context: IGameContext<TicTacToeState>;
context: IGameContext<TicTacToeState>;
}
function createTestHost() { function createTestHost() {
const host = createGameHost( const host: TestGameHost = createGameHost(
{ registry, createInitialState, start } { registry, createInitialState, start }
); );
host.context = host._context;
return { host }; return { host };
} }