Skip to content

Commit

Permalink
Merge pull request #22 from regal/doc/16-api
Browse files Browse the repository at this point in the history
Doc/16 api
  • Loading branch information
jcowman2 committed Aug 23, 2018
2 parents c4a090e + e9274d6 commit 9b8b9c4
Show file tree
Hide file tree
Showing 2 changed files with 338 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Component | Description | Designed | Implemented | Tested
--- | --- | --- | --- | ---
**API** | Public API for consumers of the library | 🔵 | 🔵 | ⚪
**Agent** | Model for immutable game objects | ✔️ | 🔵 | 🔵
**Event** | Model for game events | 🔵 | 🔵 |
**Event** | Model for game events | ✔️ | ✔️ | ✔️

✔️ - Complete
🔵 - In-Progress
Expand Down
337 changes: 337 additions & 0 deletions docs/overview/game.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,337 @@
# Game

The Regal `Game` object is the API through which games are played.

In a sense, any operation on a Regal game can be thought of as a pure function. A request (usually containing the game's current state) is sent to the Game API, and a response (consisting of the game's output and the updated game state) is returned. The game's source and the original game state are never modified.

Regal was structured like this so that a client's only responsibilties are:

* Accepting user input
* Reporting game output
* Storing the game state (although awareness of the data's structure isn't needed)
* Calling the Regal API

The idea is that a Regal game is deterministic; i.e. it will always return the same output when given the same input. This makes debugging easier and means that no user-specific game data ever needs to be stored on the game servers—in fact, a Regal game can be serverless! All one needs is a client that can store data and call the Regal API. Multiple clients playing multiple games can call the same API, and they won't interfere with each other.

## `GameResponse`

A `GameResponse` contains the output of a request's effect on the game and the new instance state of the game at that moment. A `GameResponse` is returned by the `Game` API whenever one of its methods is called.

The `GameResponse` schema is as follows:

```ts
interface GameResponse {
instance?: GameInstance;
output: GameOutput;
}
```

### `GameOutput`

`GameOutput` is an interface that represents the output generated by a request. Its properties are as follows:

Property | Type | Required | Description
--- | --- | --- | ---
wasSuccessful | boolean | Yes | Will be false if an error occurred during the game's execution of the request.
error | RegalError | No | If `wasSuccessful` is false, this will contain the error that was thrown.
log | OutputLine[] | No | Contains any lines of text emitted by the game.
options | GameOptions | No | Contains any game options requested by `getOptionCommand` or updated by `postOptionCommand`.
metadata | GameMetadata | No | Contains the game's metadata if `getMetadataCommand` was called.

An `OutputLine` usually contains a line of text meant to notify the player of something that happened in the game. It has a `data` property of type `string`, and a `type` property that is of type `OutputLineType`.

The enum `OutputLineType` is used to convey semantic meaning of the `OutputLine` to the client. The types are:

OutputLineType | Description | Usage
--- | --- | ---
NORMAL | Standard output line; presented to the player normally. (Default) | Use for most game content.
MAJOR | Important line; emphasized to the player. | Use when something important happens, like a character dying, an achievement, win/loss of the game, etc.
MINOR | Non-important line; emphasized less than `NORMAL` lines, and won't always be shown to the player. | Use for repetitive/flavor text that might add a bit to the game experience but won't be missed if it isn't seen.
DEBUG | Meant for debugging purposes; only visible when the `DEBUG` option is enabled. | Debugging
SECTION_TITLE | Signifies the start of a new section or scene in the game. | In games that have scenes, rooms, or other disconnected sections. (i.e. "**West of House**")

## Calling the `Game` API

`Game` is a global object that contains the public API for interacting with a Regal game.

It has six methods for commanding the game:

* `getOptionCommand`
* `getMetadataCommand`
* `postPlayerCommand`
* `postStartCommand`
* `postUndoCommand`
* `postOptionCommand`

Each of these methods returns a `GameResponse` object.

### `Game.getOptionCommand(instance: GameInstance, options: string[])`

Outputs the values of the named game options.

#### Example

```ts
const response = Game.getOptionCommand(myGame, [ "debug", "showMinor" ]);

response.output === {
wasSuccessful: true,
options: {
debug: false,
showMinor: true
}
};
```

### `Game.getMetadataCommand()`

Gets the game's metadata. Note that this is not specific to any game instance.

```ts
const response = Game.getMetadataCommand();

response.output === {
wasSuccessful: true,
metadata: {
title: "My Awesome Game",
author: "Joe Cowman",
version: "1.0.0",
headline: "This is the best game ever.",
description: "Let me tell you why this is the best game ever..."
}
}
```

### `Game.postPlayerCommand(instance: GameInstance, command: string)`

Posts a command that was spoken or typed by a player. Usually used to do something in the game.

```ts
const response = Game.postPlayerCommand(myGame, "enter door");

response.output === {
wasSuccessful: true,
log: [
{
type: OutputLineType.NORMAL,
data: "The door swings open with a creak."
},
{
type: OutputLineType.SECTION_TITLE,
data: "The Attic"
}
]
};
```

### `Game.postStartCommand(options: GameOption)`

Starts a new game with the option to override one or more `GameOption`s.

```ts
const response1 = Game.postStartCommand();

reponse1.output === {
wasSuccessful: true,
log: [
{
type: OutputLineType.MAJOR,
data: "WELCOME TO MY AWESOME GAME!"
}
]
};

const response2 = Game.postStartCommand({ debug: true });

response2.output === {
wasSuccessful: true,
log: [
{
type: OutputLineType.DEBUG,
data: "Debugging enabled."
},
{
type: OutputLineType.MAJOR,
data: "WELCOME TO MY AWESOME GAME!"
}
]
};
```

If options are omitted, their default values will be used.

### `Game.postUndoCommand(instance: GameInstance)`

Undoes the effects of the previous command.

```ts
const response1 = Game.PostUndoCommand(myGame);

response1.output === {
wasSuccessful: true
};
```

If the previous command was not a `PlayerCommand`, an error will be thrown.

```ts
const response2 = Game.PostUndoCommand(response1.instance);

response2.output === {
wasSuccessful: false,
error: {
message: "Can not undo an undo command; only a player command can be undone."
}
};
```

### `Game.postOptionCommand(instance: GameInstance, options: GameOption)`

Updates the values of the named game options.

```ts
const response = Game.PostOptionCommand(myGame, { debug: false });

response.output === {
wasSuccessful: true
}

Game.GetOptionCommand(response.instance, [ "debug" ]).output.options.debug === false;
```

## Hooking into the Game API

So far, everything explained by this guide is on the "client-end" of a Regal game. It is unlikely that a game developer will call any of the `Game` static methods, as they are meant to be called by a client playing the game. However, it's important to know what they are so that their behavior can be controlled.

On the "game-end," which refers to the source of a Regal game, there are several of *hooks* that a game developer should call to set up their game.

A *hook* is a method that is called internally by the Game API and can be implemented by the game developer. This allows the developer to specify functions that should be executed at certain times.

There are three hooks for the Game Command API:

* `onPlayerCommand`
* `onStartCommand`
* `onBeforeUndoCommand`

Each of these methods return `void`, and will error if called more than once.

### `onPlayerCommand(handler: (command: string) => EventFunction)`

Executes whenever `postPlayerCommand` is called. The `handler` function in this case should use the player's `command`, which is a string, to generate a `EventFunction`.

Note that if `handler` is not a `TrackedEvent`, it will be wrapped in a new `TrackedEvent` called `INPUT`.

#### Example

```ts
const handleInput = (command: string) =>
(game: GameInstance) => {
game.output.write(`You entered ${command}!`);
return noop;
});

onPlayerCommand(handleInput);
```

### `onStartCommand(handler: EventFunction)`

Executes whenever `postStartCommand` is called.

Note that if `handler` is not a `TrackedEvent`, it will be wrapped in a new `TrackedEvent` called `START`.

#### Example

```ts
onStartCommand(game => {
game.output.write("Welcome to my awesome game!");
return noop;
});
```

### `onBeforeUndoCommand(handler: (game: GameInstance) => boolean)`

Executes whenever `postUndoCommand` is called. The `handler` function, which takes place before the game's undo operation command happens, should return a boolean specifying whether or not the undo is allowed to take place.

If `handler` returns true, the previous command will be undone. If it returns false, a `RegalError` will be thrown.

Note that `onBeforeUndoCommand` will only be triggered if the previous command was a `PlayerCommand`.

#### Example

```ts
// Initialize game.foo = 0
onStartCommand(game => {
game.foo = 0;
return noop;
});

// Set game.foo to the given number
onPlayerCommand(command => game => {
game.foo = command;
});

// Only allow undo if game.foo > 1
onBeforeUndoCommand(game => {
return game.foo > 1;
});

let myGame = Game.postStartCommand();
myGame = Game.postPlayerCommand(myGame, -1); // Set myGame.foo = -1

myGame = Game.postUndoCommand(myGame); // throws RegalError "You can't undo that operation."

myGame = Game.postPlayerCommand(myGame, 10); // Set myGame.foo = 10;

myGame = Game.postUndoCommand(myGmae); // Successful! Sets myGame.foo back to 0.
```

## Game Configuration

Configuration for a Regal game is kept in the project's root directory, in a file called `regal.json`. This file contains metadata about the game (such as its title and author), as well as default values for its game options.

A game's `regal.json` file might look something like this:

```
{
name: "My Awesome Game",
author: "Joe Cowman",
version: "1.0.0",
headline: "This is the best game ever.",
description: "Let me tell you why this is the best game ever...",
options: {
debug: false,
showMinor: true
}
}
```

The complete schema of `regal.json` is as follows:

**Property** | Type | Description
--- | --- | ---
**name** | string | The game's title.
**author** | string | The game's author.
**headline** | string | A brief description of the game to be displayed with links to the game.
**description** | string | The full description of the game.
**homepage** | string | The URL of the project's homepage.
**repository** | string | The URL of the project's repository.
**options** | object | Default values for the game's options. (See below.)

None of the above properties are required, nor is a `regal.json` file even required. For every property except `headline`, if that property is omitted, its value will be retrieved from `package.json`. If a property exists in both `regal.json` and `package.json`, the value from `regal.json` will be used.

#### `GameMetadata` Interface

The `GameMetadata` interface exists to contain metadata from `regal.json` in the game's source. It is the same structure as the json file.

### Game Options

The `options` object contains default values for the game's options. It's not required to provide defaults for all options, or even an `options` object at all. The options and their defaults are as follows:

**Property** | Type | Default Value | Description
**debug** | boolean | `false` | Whether debug output should be returned to the client.
**showMinor** | boolean | `true` | Whether minor output should be returned to the client.

#### `GameOption` Interface

The `GameOption` interface exists to contain metadata from `regal.json`'s `options` object in the game's source. It is the same structure as the `options` value in the `regal.json` file.

0 comments on commit 9b8b9c4

Please sign in to comment.