Compare commits
2 Commits
1e7dbea129
...
a8774f34bc
| Author | SHA1 | Date |
|---|---|---|
|
|
a8774f34bc | |
|
|
49109963bc |
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue