Skip to content

Commit

Permalink
feat(state): provide a way for scripts to end game (#83)
Browse files Browse the repository at this point in the history
  • Loading branch information
ssube committed May 29, 2021
1 parent 6148dea commit 66b348a
Show file tree
Hide file tree
Showing 14 changed files with 118 additions and 119 deletions.
22 changes: 19 additions & 3 deletions src/main.ts
Expand Up @@ -3,19 +3,28 @@ import { BaseOptions, Container, Module } from 'noicejs';

import { BunyanLogger } from './logger/BunyanLogger';
import { ActorType } from './model/entity/Actor';
import { INJECT_ACTOR, INJECT_CONFIG, INJECT_EVENT, INJECT_LOADER, INJECT_LOCALE, INJECT_PARSER, INJECT_RENDER, INJECT_STATE } from './module';
import {
INJECT_ACTOR,
INJECT_CONFIG,
INJECT_EVENT,
INJECT_LOADER,
INJECT_LOCALE,
INJECT_RENDER,
INJECT_STATE,
} from './module';
import { ActorLocator, ActorModule } from './module/ActorModule';
import { BrowserModule } from './module/BrowserModule';
import { LocalModule } from './module/LocalModule';
import { NodeModule } from './module/NodeModule';
import { EventBus } from './service/event';
import { LoaderService } from './service/loader';
import { LocaleService } from './service/locale';
import { Parser } from './service/parser';
import { RenderService } from './service/render';
import { StateService } from './service/state';
import { parseArgs } from './util/args';
import { asyncDebug, asyncTrack } from './util/async';
import { loadConfig } from './util/config/file';
import { onceWithRemove } from './util/event';

const DI_MODULES = new Map<string, new () => Module>([
['browser', BrowserModule],
Expand All @@ -25,6 +34,9 @@ const DI_MODULES = new Map<string, new () => Module>([
]);

export async function main(args: Array<string>): Promise<number> {
const { asyncHook, asyncOps } = asyncTrack();
asyncHook.enable();

// parse args
const arg = parseArgs(args);

Expand Down Expand Up @@ -100,12 +112,16 @@ export async function main(args: Array<string>): Promise<number> {
],
});

// await step
// await output before next command
const { pending } = onceWithRemove(events, 'actor-output');
await pending;
}

await state.stop();
await render.stop();
await loader.stop();

// asyncDebug(asyncOps);

return 0;
}
2 changes: 1 addition & 1 deletion src/module/BrowserModule.ts
Expand Up @@ -5,7 +5,7 @@ import { INJECT_LOADER, INJECT_RENDER } from '.';
import { BrowserFetchLoader } from '../service/loader/browser/FetchLoader';
import { BrowserPageLoader } from '../service/loader/browser/PageLoader';
import { RenderService } from '../service/render';
import { ReactDomRender } from '../service/render/ReactDomRender';
import { ReactDomRender } from '../service/render/react/DomRender';
import { Singleton } from '../util/container';

export class BrowserModule extends Module {
Expand Down
2 changes: 1 addition & 1 deletion src/module/NodeModule.ts
Expand Up @@ -5,7 +5,7 @@ import { INJECT_LOADER, INJECT_RENDER } from '.';
import { NodeFetchLoader } from '../service/loader/node/FetchLoader';
import { NodeFileLoader } from '../service/loader/node/FileLoader';
import { RenderService } from '../service/render';
import { InkRender } from '../service/render/InkRender';
import { InkRender } from '../service/render/react/InkRender';
import { LineRender } from '../service/render/LineRender';
import { Singleton } from '../util/container';

Expand Down
1 change: 1 addition & 0 deletions src/script/common/ActorStep.ts
Expand Up @@ -35,6 +35,7 @@ export async function ActorStep(this: ScriptTarget, context: ScriptContext, verb
if (health <= 0) {
if (this.actorType === ActorType.PLAYER) {
await context.focus.show('actor.step.command.dead', { actor: this });
await context.focus.quit();
}
return;
}
Expand Down
7 changes: 7 additions & 0 deletions src/service/event/index.ts
Expand Up @@ -22,10 +22,17 @@ export interface RoomEvent {
}

export interface OutputEvent {
/**
* The lines of output.
*/
lines: Array<{
context?: LocaleContext;
key: string;
}>;

/**
* The state step from which this output was emitted.
*/
step: StepResult;
}

Expand Down
2 changes: 1 addition & 1 deletion src/service/render/LineRender.ts
Expand Up @@ -10,7 +10,7 @@ import { onceWithRemove } from '../../util/event';
import { EventBus, LineEvent, RoomEvent } from '../event';
import { LocaleService } from '../locale';
import { StepResult } from '../state';
import { BaseRenderOptions } from './BaseReactRender';
import { BaseRenderOptions } from './react/BaseRender';

@Inject(/* all from base */)
export class LineRender implements RenderService {
Expand Down
@@ -1,13 +1,13 @@
import { constructorName, InvalidArgumentError, mustExist } from '@apextoaster/js-utils';
import { BaseOptions, Inject, Logger } from 'noicejs';

import { RenderService } from '.';
import { INJECT_EVENT, INJECT_LOCALE, INJECT_LOGGER } from '../../module';
import { onceWithRemove } from '../../util/event';
import { debounce } from '../../util/event/Debounce';
import { EventBus, LineEvent, OutputEvent, RoomEvent } from '../event';
import { LocaleService } from '../locale';
import { StepResult } from '../state';
import { RenderService } from '..';
import { INJECT_EVENT, INJECT_LOCALE, INJECT_LOGGER } from '../../../module';
import { onceWithRemove } from '../../../util/event';
import { debounce } from '../../../util/event/Debounce';
import { EventBus, LineEvent, OutputEvent, RoomEvent } from '../../event';
import { LocaleService } from '../../locale';
import { StepResult } from '../../state';

export interface BaseRenderOptions extends BaseOptions {
[INJECT_EVENT]?: EventBus;
Expand Down Expand Up @@ -48,7 +48,16 @@ export abstract class BaseReactRender implements RenderService {
};
}

public abstract start(): Promise<void>;
public async start(): Promise<void> {
this.renderRoot();
this.prompt(`turn ${this.step.turn}`);

this.event.on('actor-output', (output) => this.onOutput(output));
this.event.on('state-room', (room) => this.onRoom(room));
this.event.on('state-step', (step) => this.onStep(step));
this.event.on('quit', () => this.onQuit());
}

public abstract stop(): Promise<void>;
protected abstract renderRoot(): void;

Expand Down Expand Up @@ -87,6 +96,7 @@ export abstract class BaseReactRender implements RenderService {
public onQuit(): void {
this.logger.debug('handling quit event from state');
this.output.push('game over');
this.renderRoot();
}

/**
Expand Down
Expand Up @@ -3,9 +3,9 @@ import { Inject } from 'noicejs';
import * as React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';

import { RenderService } from '.';
import { Frame } from '../../component/react/Frame';
import { BaseReactRender } from './BaseReactRender';
import { RenderService } from '..';
import { Frame } from '../../../component/react/Frame';
import { BaseReactRender } from './BaseRender';

/**
* Interface with React tree using an event emitter.
Expand All @@ -15,13 +15,7 @@ export class ReactDomRender extends BaseReactRender implements RenderService {
public async start(): Promise<void> {
this.logger.debug('starting React render');

this.renderRoot();
this.prompt(`turn ${this.step.turn}`);

this.event.on('actor-output', (output) => this.onOutput(output));
this.event.on('state-room', (room) => this.onRoom(room));
this.event.on('state-step', (step) => this.onStep(step));
this.event.on('quit', () => this.onQuit());
await super.start();
}

public async stop(): Promise<void> {
Expand Down
Expand Up @@ -3,9 +3,9 @@ import { Instance as InkInstance, render } from 'ink';
import { Inject } from 'noicejs';
import * as React from 'react';

import { RenderService } from '.';
import { Frame } from '../../component/ink/Frame';
import { BaseReactRender } from './BaseReactRender';
import { RenderService } from '..';
import { Frame } from '../../../component/ink/Frame';
import { BaseReactRender } from './BaseRender';

/**
* Interface with Ink's React tree using an event emitter.
Expand All @@ -17,13 +17,7 @@ export class InkRender extends BaseReactRender implements RenderService {
public async start(): Promise<void> {
this.logger.debug('starting Ink render');

this.renderRoot();
this.prompt(`turn ${this.step.turn}`);

this.event.on('actor-output', (output) => this.onOutput(output));
this.event.on('state-room', (room) => this.onRoom(room));
this.event.on('state-step', (step) => this.onStep(step));
this.event.on('quit', () => this.onQuit());
await super.start();
}

public async stop(): Promise<void> {
Expand Down
15 changes: 10 additions & 5 deletions src/service/state/TurnState.ts
Expand Up @@ -261,12 +261,12 @@ export class LocalStateService implements StateService {
case META_LOAD:
await this.doLoad(cmd.target, cmd.index);
break;
case META_QUIT:
await this.doQuit();
break;
case META_SAVE:
await this.doSave(cmd.target);
break;
case META_QUIT:
this.event.emit('quit');
break;
default: {
// step world
const result = await this.step();
Expand Down Expand Up @@ -342,6 +342,10 @@ export class LocalStateService implements StateService {
});
}

public async doQuit(): Promise<void> {
this.event.emit('quit');
}

public async doSave(path: string): Promise<void> {
const state = mustExist(this.state);
const world = mustFind(this.worlds, (it) => it.meta.id === state.meta.id);
Expand Down Expand Up @@ -460,8 +464,9 @@ export class LocalStateService implements StateService {
this.focus = await this.container.create(StateFocusResolver, {
events: {
onActor: () => Promise.resolve(),
onRoom: async (room) => this.onRoom(room),
onShow: async (line, context) => this.onOutput(line, context),
onQuit: () => this.doQuit(),
onRoom: (room) => this.onRoom(room),
onShow: (line, context) => this.onOutput(line, context),
},
});

Expand Down
10 changes: 9 additions & 1 deletion src/util/state/FocusResolver.ts
Expand Up @@ -10,10 +10,12 @@ import { LocaleContext } from '../../service/locale';

export type FocusChangeRoom = (room: Room) => Promise<void>;
export type FocusChangeActor = (actor: Actor) => Promise<void>;
export type FocusQuit = () => Promise<void>;
export type FocusShow = (line: string, context?: LocaleContext) => Promise<void>;

interface FocusEvents {
export interface FocusEvents {
onActor: FocusChangeActor;
onQuit: FocusQuit;
onRoom: FocusChangeRoom;
onShow: FocusShow;
}
Expand All @@ -39,6 +41,7 @@ interface StateFocusResolverOptions extends BaseOptions {
*/
export class StateFocusResolver {
protected onActor: FocusChangeActor;
protected onQuit: FocusQuit;
protected onRoom: FocusChangeRoom;
protected onShow: FocusShow;

Expand All @@ -47,6 +50,7 @@ export class StateFocusResolver {
constructor(options: StateFocusResolverOptions) {
const events = mustExist(options.events);
this.onActor = events.onActor;
this.onQuit = events.onQuit;
this.onRoom = events.onRoom;
this.onShow = events.onShow;
}
Expand Down Expand Up @@ -97,6 +101,10 @@ export class StateFocusResolver {
}
}

public async quit(): Promise<void> {
await this.onQuit();
}

/**
* Display a message from an entity.
*/
Expand Down
11 changes: 11 additions & 0 deletions test/helper.ts
@@ -1,7 +1,9 @@
import { Container, LogLevel, Module, NullLogger } from 'noicejs';
import { stub } from 'sinon';

import { ConfigFile } from '../src/model/file/Config';
import { INJECT_CONFIG, INJECT_LOGGER } from '../src/module';
import { FocusEvents } from '../src/util/state/FocusResolver';

export function getTestLogger() {
// TODO: return console/bunyan logger if TEST_LOGGER=that
Expand Down Expand Up @@ -36,3 +38,12 @@ export async function getTestContainer(...modules: Array<Module>): Promise<Conta

return container;
}

export function getStubEvents(): FocusEvents {
return {
onActor: stub(),
onQuit: stub(),
onRoom: stub(),
onShow: stub(),
};
}
19 changes: 4 additions & 15 deletions test/service/script/TestLocalScript.ts
Expand Up @@ -9,6 +9,7 @@ import { MathRandomGenerator } from '../../../src/service/random/MathRandom';
import { LocalScriptService } from '../../../src/service/script/LocalScript';
import { StateEntityTransfer } from '../../../src/util/state/EntityTransfer';
import { StateFocusResolver } from '../../../src/util/state/FocusResolver';
import { getStubEvents } from '../../helper';

const TEST_STATE: State = {
focus: {
Expand Down Expand Up @@ -61,11 +62,7 @@ describe('local script service', () => {
await script.invoke(target, 'foo', {
data: new Map(),
focus: await container.create(StateFocusResolver, {
events: {
onActor: async () => { },
onRoom: async () => { },
onShow: async () => { },
},
events: getStubEvents(),
state: TEST_STATE,
}),
random: await container.create(MathRandomGenerator),
Expand Down Expand Up @@ -99,11 +96,7 @@ describe('local script service', () => {
await script.invoke(target, 'foo', {
data: new Map(),
focus: await container.create(StateFocusResolver, {
events: {
onActor: async () => { },
onRoom: async () => { },
onShow: async () => { },
},
events: getStubEvents(),
state: TEST_STATE,
}),
random: await container.create(MathRandomGenerator),
Expand Down Expand Up @@ -140,11 +133,7 @@ describe('local script service', () => {
await script.invoke(target, 'foo', {
data: new Map(),
focus: await container.create(StateFocusResolver, {
events: {
onActor: async () => { },
onRoom: async () => { },
onShow: async () => { },
},
events: getStubEvents(),
state: TEST_STATE,
}),
random: await container.create(MathRandomGenerator),
Expand Down

0 comments on commit 66b348a

Please sign in to comment.