Skip to content

Commit

Permalink
refactor(api): Refactor and test Game.postPlayerCommand
Browse files Browse the repository at this point in the history
  • Loading branch information
jcowman2 committed Aug 29, 2018
1 parent 1fe5b06 commit 08f1f9b
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 23 deletions.
45 changes: 23 additions & 22 deletions src/game-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { GameOptions } from "./game-config";
import { GameOutput } from "./output";
import { HookManager } from "./api-hooks";
import { RegalError } from "./error";
import { resetRegistry } from "./agent";

const validateGameInstance = (instance: GameInstance): void => {
if (
Expand All @@ -17,8 +18,8 @@ const validateGameInstance = (instance: GameInstance): void => {
};

const wrapApiErrorAsRegalError = (err: any): RegalError => {
if (!err || !err.stack || !err.message) {
throw new RegalError("Not a valid error object.");
if (!err || !err.name || !err.stack || !err.message) {
return new RegalError("Invalid error object.");
}

// If err is already a RegalError, return it
Expand All @@ -27,20 +28,13 @@ const wrapApiErrorAsRegalError = (err: any): RegalError => {
}

// Else, create a RegalError
const msg = `An error occurred while executing the request. Details: ${err.message}`;
const msg = `An error occurred while executing the request. Details: <${err.name}: ${err.message}>`;
const newErr = new RegalError(msg);
newErr.stack = err.stack;

return newErr;
}

// const cycleInstance = (former: GameInstance): GameInstance => {
// const current = new GameInstance();
// current.output.lineCount = former.output.lineCount;

// return former;
// };

export class Game {

static getOptionCommand(instance: GameInstance, options: string[]): GameResponse {
Expand All @@ -54,19 +48,21 @@ export class Game {
}

static postPlayerCommand(instance: GameInstance, command: string): GameResponse {
validateGameInstance(instance);

if (command === undefined) {
throw new RegalError("Command must be defined.");
}
if (HookManager.playerCommandHook === undefined) {
throw new RegalError("onPlayerCommand has not been implemented by the game developer.");
}

let newInstance = instance.cycle();
let newInstance: GameInstance;
let err: RegalError;

try {
validateGameInstance(instance);

if (command === undefined) {
throw new RegalError("Command must be defined.");
}
if (HookManager.playerCommandHook === undefined) {
throw new RegalError("onPlayerCommand has not been implemented by the game developer.");
}

newInstance = instance.cycle();

const activatedEvent = HookManager.playerCommandHook(command);
newInstance.events.invoke(activatedEvent);
} catch (error) {
Expand Down Expand Up @@ -113,9 +109,14 @@ export class Game {
// TODO
throw new Error("Method not implemented");
}
};
}

export interface GameResponse {
instance?: GameInstance;
output: GameOutput;
}
}

export const resetGame = () => {
HookManager.resetHooks();
resetRegistry();
};
2 changes: 1 addition & 1 deletion src/game-instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export default class GameInstance {

cycle(): GameInstance {
const newGame = new GameInstance();
newGame.agents = this.agents.cycle(newGame);
newGame.events = this.events.cycle(newGame);
newGame.agents = this.agents.cycle(newGame);
newGame.output = this.output.cycle(newGame);

return newGame;
Expand Down
149 changes: 149 additions & 0 deletions test/game-api.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { expect } from 'chai';
import 'mocha';

import { Game, GameResponse, resetGame } from '../src/game-api';
import { onPlayerCommand, onStartCommand } from '../src/api-hooks';
import { noop } from '../src/event';
import GameInstance from '../src/game-instance';
import { OutputLineType } from '../src/output';
import { log } from '../src/utils';
import { Agent } from '../src/agent';

describe("Game API", function() {

beforeEach(function() {
resetGame();
});

describe("Game.postPlayerCommand", function() {

it("Sending a good request sends the correct output", function() {
onPlayerCommand(command => game => {
game.output.write(`You typed "${command}".`);
return noop;
});

const response = Game.postPlayerCommand(new GameInstance(), "Hello, World!");

expect(response.instance).to.not.be.undefined;
expect(response.output).to.deep.equal({
wasSuccessful: true,
log: [
{
id: 1,
data: "You typed \"Hello, World!\".",
type: OutputLineType.NORMAL
}
]
});
});

it("Requests do not modify previous instances", function() {
onPlayerCommand(command => game => {
if (!game.state.comms) {
game.state.comms = [command];
} else {
game.state.comms = (<string[]>game.state.comms).concat(command);
}
return noop;
});

const r1 = Game.postPlayerCommand(new GameInstance(), "One");
const r2 = Game.postPlayerCommand(r1.instance, "Two");
const r3 = Game.postPlayerCommand(r2.instance, "Three");

expect(r1.instance.state.comms).to.deep.equal(["One"]);
expect(r2.instance.state.comms).to.deep.equal(["One", "Two"]);
expect(r3.instance.state.comms).to.deep.equal(["One", "Two", "Three"]);
});

it("Running the same request multiple times does not have any side effects", function() {
onPlayerCommand(command => game => {
if (!game.state.guy) {
game.state.guy = new Agent();
}
const guy = game.state.guy;
guy[command] = true;

game.output.write(`Set guy[${command}] to true.`);

return noop;
});

const init = Game.postPlayerCommand(new GameInstance(), "init");

let foo: GameResponse;
for (let i = 0; i < 5; i++) {
foo = Game.postPlayerCommand(init.instance, `foo${i}`);
}

expect(foo.output).to.deep.equal({
wasSuccessful: true,
log: [
{
id: 2,
data: "Set guy[foo4] to true.",
type: OutputLineType.NORMAL
}
]
});
expect(foo.instance.state.guy.init).to.be.true;
expect(foo.instance.state.guy.foo3).to.be.undefined;
expect(foo.instance.state.guy.foo4).to.be.true;
});

it("Invalid GameInstance isn't allowed", function() {
onPlayerCommand(() => noop);

const response = Game.postPlayerCommand(<GameInstance><any>"bork", "bork");

expect(response.output.wasSuccessful).to.be.false;
expect(response.output.error.message).to.equal("RegalError: Invalid GameInstance.");
expect(response.instance).to.be.undefined;
});

it("Undefined command isn't allowed", function() {
onPlayerCommand(() => noop);

const response = Game.postPlayerCommand(new GameInstance(), undefined);

expect(response.output.wasSuccessful).to.be.false;
expect(response.output.error.message).to.equal("RegalError: Command must be defined.");
expect(response.instance).to.be.undefined;
});

it("An error is thrown if the onPlayerCommand hook isn't set", function() {
const response = Game.postPlayerCommand(new GameInstance(), "foo");

expect(response.output.wasSuccessful).to.be.false;
expect(response.output.error.message).to.equal("RegalError: onPlayerCommand has not been implemented by the game developer.");
expect(response.instance).to.be.undefined;
});

it("An error is thrown if the developer tries to throw an invalid error object", function() {
onPlayerCommand(() => () => {
throw 5;
});

const response = Game.postPlayerCommand(new GameInstance(), "foo");

expect(response.output.wasSuccessful).to.be.false;
expect(response.output.error.message).to.equal("RegalError: Invalid error object.");
expect(response.instance).to.be.undefined;
});

it("A new RegalError is made if an error occurred during the game's runtime", function() {
onPlayerCommand(() => () => {
(<string[]><any>5).push("blarp"); // yum
return noop;
});

const response = Game.postPlayerCommand(new GameInstance(), "foo");

expect(response.output.wasSuccessful).to.be.false;
expect(response.output.error.message).to.equal("RegalError: An error occurred while executing the request. Details: <TypeError: 5.push is not a function>");
expect(response.instance).to.be.undefined;
});

});
});

0 comments on commit 08f1f9b

Please sign in to comment.