Regal
Introduction
The Regal Game Library is the official TypeScript library for the Regal Framework, a project designed to help developers bring text-driven games and story experiences to players in exciting new ways.
What is the Regal Framework?
The Regal Framework is a set of tools for developers to create text-driven games, sometimes called Interactive Fiction (IF), as pure functions.
For more information, check out the project's about page.
How is the Regal Game Library Used?
The Regal Game Library, often referred to as the Game Library or its package name regal
, is the JavaScript library that game developers use to create games for the Regal Framework. It was designed with first-class support for TypeScript, but doesn't require it.
When a game is created using the Regal Game Library, it can be played by any Regal client automatically.
What's the point?
Similar to Java's "write once, run anywhere" mantra, the goal of the Regal Framework is to produce games that can be played on all kinds of platforms without needing to rewrite any code.
The name Regal is an acronym for Reinventing Gameplay through Audio and Language. The project was inspired by the idea of playing adventure games on smart voice assistants, but it doesn't have to stop there. Chatrooms, consoles, smart fridges...they're all within reach!
Table of Contents
- Introduction
- Installation
- Project Roadmap
- Contributing
- Guide: Creating Your First Regal Game
- Documentation
- API Reference
Installation
regal
is available on npm and can be installed with the following command:
npm install regal
If you're using TypeScript (highly recommended), import it into your files like so:
;
Otherwise, using Node's require
works as well:
const regal = ;
Project Roadmap
The Regal Game Library has been in development since June 2018. The first stable version, alias Beacon, was released on February 1st, 2019.
Regal 2.0.0
, alias Dakota, was released on November 11th, 2019. This version included some sweeping refactors that fixed bugs in the initial release, namely adding the Agent Prototype Registry to support class methods for Agents.
Moving forward, the most pressing features that should be added to the Game Library are the player command and plugin interfaces.
Outside of the library, other priorities include:
- Improving the development tooling surrounding the framework, such as expanding regal-bundler and creating a CLI.
- Building clients to play Regal games on various platforms.
- Creating fun Regal games.
Contributing
Currently, the Regal Framework is developed solely by Joe Cowman (jcowman2), but pull requests, bug reports, suggestions, and questions are all more than welcome!
If you would like to get involved, please see the contributing page or the project's about page.
Guide: Creating Your First Regal Game
The following is a step-by-step guide for creating a basic game of Rock, Paper, Scissors with Regal and TypeScript.
For more detailed information on any topic, see the API Reference below. Everything in this guide is available in the Regal demos repository as well.
Step 1. Set up project
Start with an empty folder. Create a package.json
file in your project's root directory with at least the following properties:
Then, install the regal
dependency.
npm install regal
Since your game will be written in TypeScript (as is recommended for all Regal games), you'll need to install typescript
as well:
npm install --save-dev typescript
Create a src
directory and a new file called index.ts
inside it. This is where you'll write your game logic.
At this point, your project should have the following structure:
.
├── node_modules
├── package.json
├── package-lock.json
└── src
└── index.ts
Step 2. Write game logic
In index.ts
, place the following import statement on the top line:
;
The Regal Game Library has way more tools to help you make games, but these imports are all you need for a game this basic.
Beneath the import line, paste the following constants. You'll use these when writing the game's logic. WIN_TABLE
is a lookup table to see if one move beats another. For example, WIN_TABLE.paper.scissors
is false
, since paper loses to scissors.
;
Next, you'll set the game's start behavior with onStartCommand
. When a player starts a new game, both the player's and the opponent's scores will be initialized to zero, and a prompt will be displayed. Paste the following block of code beneath your constants:
onStartCommand;
Finally, you need the actual gameplay. The following block should be pasted at the end of your file. It contains the behavior that runs every time the player enters a command.
onPlayerCommand;
One last thing: the line if (POSSIBLE_MOVES.includes(playerMove)) {
uses Array.prototype.includes
, which is new in ECMAScript 2016. To make the TypeScript compiler compatible with this, add a tsconfig.json
file to your project's root directory with the following contents:
Step 3. Bundle game
Before your game can be played, it must be bundled. Bundling is the process of converting a Regal game's development source (i.e. the TypeScript or JavaScript source files that the game developer writes) into a game bundle, which is a self-contained file that contains all the code necessary to play the game via a single API.
You can use the Regal CLI to create Regal game bundles from the command line. Install it like so:
npm install -g regal-cli regal
To bundle your game, execute this command in your project's root directory:
regal bundle
This should generate a new file in your project's directory, called my-first-game.regal.js
. Your first game is bundled and ready to be played!
For a list of configuration options you can use, consult the CLI's documentation.
Step 4. Play game
To load a Regal game bundle for playing, use the Regal CLI play
command.
regal play my-first-game.regal.js
The game should open in your terminal. Enter rock
, paper
, or scissors
to play, or :quit
to exit the game. The sequence should look something like this:
Now Playing: my-first-game by Your Name Here
Type :quit to exit the game.
Play rock, paper, or scissors:
paper
The opponent plays rock.
Your paper beats the opponent's rock!
Your wins: 1. The opponent's wins: 0
Play rock, paper, or scissors:
Congratulations, you've created your first game with the Regal Framework! 🎉
Documentation
The following sections provide a guide to each aspect of the Regal Game Library. For detailed information on a specific item, consult the API Reference.
Core Concepts
The Regal Game Library is a JavaScript package that is required by games to be used within the Regal Framework. A game that is built using the Game Library is called a Regal game.
Regal games have the following qualities:
- They are text-based. Simply put, gameplay consists of the player putting text in and the game sending text back in response.
- They are deterministic. When a Regal game is given some input, it should return the same output every time. (To see how this applies to random values, read here.)
These two qualities allow Regal games to be thought of as pure functions. A pure function is a function that is deterministic and has no side-effects. In other words, a Regal game is totally self-contained and predictable.
Think of playing a Regal game like the following equation:
g1(x) = g2
where x is the player's command
g1 is the Regal game before the command
g2 is the Regal game after the command
Entering the player's command into the first game instance creates another game instance with the effects of the player's command applied. For example, if g1
contains a scene where a player is fighting an orc, and x
is "stab orc"
, g2
might show the player killing that orc. Note that g1
is unmodified by the player's command.
The process of one game instance interpreting a command and outputting another game instance is called a game cycle.
Game Data
All data in a Regal game is in one of two forms: static or instance-specific.
Static data is defined in the game's source code, and is the same for every instance of the game. Game events, for example, are considered static because they are defined the same way for everyone playing the game (even though they may have different effects). Metadata values for the game, such as its title and author, are also static.
Instance-specific data, more frequently called game state, is unique to a single instance of the game. A common example of game state is a player's stats, such as health or experience. Because this data is unique to one player of the game and is not shared by all players, it's considered instance-specific.
Understanding the difference between static data and game state is important. Everything that's declared in a Regal game will be in one of these two contexts.
GameInstance
The cornerstone of the Regal Game Library is the GameInstance
.
A GameInstance
represents a unique instance of a Regal game. It contains (1) the game's current state and (2) all the interfaces used to interact with the game during a game cycle.
GameInstance vs Game
To understand how a game instance differs from the game itself, it can be helpful to think of a Regal game like a class. The game's static context is like a class definition, which contains all the immutable events and constants that are the same for every player.
When a player starts a new Regal game, they receive an object of that class. This game instance is a snapshot of the Regal game that is unique to that player. It contains the effects of every command made by the player, and has no bearing on any other players' game instances.
Two people playing different games of Solitare are playing the same game, but different game instances.
Instance State
All game state is stored in a GameInstance
object. Some of this state is hidden from view, but custom properties can be set directly in GameInstance.state
.
// Assumes there's a GameInstance called myGamemyGame.state.foo = "bar";myGame.state.arr = ;
Properties set within the state
object are maintained between game cycles, so it can be used to store game data long-term.
GameInstance.state
is of of type any
, meaning that its properties are totally customizable. Optionally, the state type may be set using a type parameter (see Using StateType for more information).
InstanceX Interfaces
In addition to storing game state, GameInstance
contains several interfaces for controlling the game instance's behavior.
Property | Type | Controls |
---|---|---|
events |
InstanceEvents |
Events |
output |
InstanceOutput |
Output |
options |
InstanceOptions |
Options |
random |
InstanceRandom |
Randomness |
state |
any or StateType |
Miscellaneous state |
Each of these interfaces is described in more detail below.
StateType
Using GameInstance.state
is of of type any
, meaning that its properties are totally customizable. Optionally, the state type may be set using a type parameter called StateType
.
The StateType
parameter allows you to type-check the structure of GameInstance.state
against a custom interface anywhere GameInstance
is used.
;
Keep in mind that StateType
is strictly a compile-time check, and no steps are taken to ensure that the state
object actually matches the structure of StateType
at runtime.
myGame.state.substring2; // Compiles fine, but will throw an error at runtime because state is an object.
StateType
is especially useful for parameterizing events.
Events
A game where everything stays the same isn't much of a game. Therefore, Regal games are powered by events.
An event can be thought of as anything that happens when someone plays a Regal game. Any time the game's state changes, it happens inside of an event.
Event Functions
Events in the Regal Game Library share a common type: EventFunction
.
An EventFunction
takes a GameInstance
as its only argument, modifies it, and may return the next EventFunction
to be executed, if one exists.
Here is a simplified declaration of the EventFunction
type:
;
An EventFunction
can be invoked by passing it a GameInstance
:
// Assumes there's a GameInstance called myGame // and an EventFunction called event1, which returns void. event1myGame; // Invoke the event. // Now, myGame contains the changes made by event1.
EventFunction
has two subtypes: TrackedEvent
and EventQueue
. Both are described below.
Declaring Events
The most common type of event used in Regal games is the TrackedEvent
. A TrackedEvent
is simply an EventFunction
that is tracked by the GameInstance
.
In order for Regal to work properly, all modifications to game state should take place inside tracked events.
To declare a TrackedEvent
, use the on
function:
; ;
The on
function takes an event name and an event function to construct a TrackedEvent
. The event declared above could be invoked like this:
// Assumes there's a GameInstance called myGame. greetmyGame; // Invoke the greet event.
This would cause myGame
to have the following output:
GREET: Hello, world!
Causing Additional Events
As stated earlier, an EventFunction
may return another EventFunction
. This tells the event executor that another event should be executed on the game instance.
Here's an example:
; ; ;
When the outside
event is executed, it checks the value of game.state.isDay
and returns the appropriate event to be executed next.
// Assume that myGame.state.isDay is false. outsidemyGame;
myGame
's output would look like this:
OUTSIDE: You go outside.
NIGHT: The moon glows softly overhead.
Causing Multiple Events
It's possible to have one EventFunction
cause multiple events with the use of an EventQueue
.
An EventQueue
is a special type of TrackedEvent
that contains a collection of events. These events are executed sequentially when the EventQueue
is invoked.
Queued events may be immediate or delayed, depending on when you want them to be executed.
Immediate Execution
To have one event be executed immediately after another, use the TrackedEvent.then()
method. This is useful in situations where multiple events should be executed in direct sequence.
To demonstrate, here's an example of a player crafting a sword. When the makeSword
event is executed, the sword is immediately added to the player's inventory (addItemToInventory
) and the player learns the blacksmithing skill (learnSkill
).
; ; ;
Note: This example is available here.
Execute the makeSword
event on a GameInstance
called myGame
like so:
makeSword"King Arthur"myGame;
This would produce the following output for myGame
:
MAKE SWORD: King Arthur made a sword!
ADD ITEM <Sword>: Added Sword to King Arthur's inventory.
LEARN SKILL <Blacksmithing>: King Arthur learned Blacksmithing!
Delayed Execution
Alternatively, an event may be scheduled to execute only after all of the immediate events are finished by using enqueue()
. This is useful in situations where you have multiple series of events, and you want each series to execute their events in the same "round."
This is best illustrated with an example. Here's a situation where a player executes a command that drops a list of items from their inventory.
; ; ; ;
Note: This example is available here.
We'll walk through each line, starting from the drop
function.
The enqueue()
function takes zero or more TrackedEvent
s as arguments, which it uses to build an EventQueue
. Creating an empty queue has no effect; it simply provides us a reference to which we can add additional events.
;
In addition to being a standalone function, enqueue()
is also a method of EventQueue
. Calling EventQueue.enqueue()
creates a new queue with all previous events plus the new event(s).
for of game.state.items
The previous two code blocks could be simplified by using JavaScript's map
like so:
;
Finally, we return the event queue.
return q; });
The fall
event is simpler. It outputs a message and adds a hitGround
event to the end of the queue for a single item.
;
The hitGround
event outputs a final message.
;
Deciding that game.state.items
contains ["Hat", "Duck", "Spoon"]
, executing the drop
event would produce an output like this:
FALL <Hat>: Hat falls.
FALL <Duck>: Duck falls.
FALL <Spoon>: Spoon falls.
HIT GROUND <Hat>: Hat hits the ground. Thud!
HIT GROUND <Duck>: Duck hits the ground. Thud!
HIT GROUND <Spoon>: Spoon hits the ground. Thud!
If you're still confused about the difference between TrackedEvent.then()
and EventQueue.enqueue()
, here's what the output would have been if TrackedEvent.then()
was used instead:
FALL <Hat>: Hat falls.
HIT GROUND <Hat>: Hat hits the ground. Thud!
FALL <Duck>: Duck falls.
HIT GROUND <Duck>: Duck hits the ground. Thud!
FALL <Spoon>: Spoon falls.
HIT GROUND <Spoon>: Spoon hits the ground. Thud!
Remember, enqueue()
is useful for situations where you have multiple series of events, like our [fall
-> hitGround
] series, and you want each series to execute their alike events in the same "round." We didn't want hat
to finish hitting the ground before duck
fell, we wanted all of the items to fall together and hit the ground together.
TrackedEvent.then()
is for immediate exeuction andenqueue()
is for delayed exeuction.
Event Chains
The event API is chainable, meaning that the queueing methods can be called multiple times to create more complex event chains.
// Immediately executes events 1-4event1.thenevent2, event3, event4;event1.thenevent2.thenevent3.thenevent4;event1.thenevent2.thenevent3, event4;event1.thenevent2, event3.thenevent4; // Immediately executes event1, delays 2-4.event1.thenenqueueevent2, event3, event4;event1.thenqevent2, event3, event4; // TrackedEvent.thenq is shorthand for TrackedEvent.then(enqueue(...)) // Immediately executes events 1-2, delays 3-4.event1.thenevent2.enqueueevent3, event4;event1.thenevent2, enqueueevent3, event4;event1.thenevent2.enqueueevent3.enqueueevent4; // Delays events 1-4.enqueueevent1, event2, event3, event4;enqueueevent1.thenevent2, event3, event4;
If you prefer, you can use the shorthand nq
instead of writing enqueue()
. We're all about efficiency. 👍
; ;q = q.nqevent3;
When creating event chains, keep in mind that all immediate events must be added to the queue before delayed events.
event1.thennqevent2, event3, event4; // ERRORevent1.thenevent4, nqevent2, event3; // Okay
For more information, consult the API Reference.
noop
When to Use noop
is a special TrackedEvent
that stands for no operation. When the event executor runs into noop
, it ignores it.
; ;
EventFunction
doesn't have a required return, so most of the time you can just return nothing instead of returning noop
.
;
However, noop
might be necessary in cases where you want to use a ternary to make things simpler:
on"EVENT", game.state.someCondition ? otherEvent | noop; );
If you have a TypeScript project with noImplicitReturns
enabled, noop
is useful for making sure all code paths return a value.
// Error: [ts] Not all code paths return a value.on"EVENT", ; // Will work as intendedon"EVENT", ;
Parameterizing Events
The StateType
type parameter of GameInstance
can be used to declare the type of GameInstance.state
inside the scope of individual events.
EventFunction
, TrackedEvent
, and EventQueue
all allow an optional type parameter that, if used, will type-check GameInstance.state
inside the body of the event.
; ; ;
Including the type parameter for every event gets unwieldy for games with many events, so the type GameEventBuilder
can be used to alias a customized on
.
; ; // Declare `on` as our parameterized version ; ;
Note: This example is available here.
Using the redefined on
from this example, the following event would not compile:
on"BAD", ;
Agents
Where events describe any change that occurs within a Regal game, agents are the objects on which these changes take place.
Every object that contains game state (like players, items, and score) is considered an agent.
Defining Agents
The Regal Game Library offers the Agent
class, which you can extend to create custom agents for your game.
Here is an example:
;
Now, a Bucket
can be instantiated and used just like any other class.
;bucket.size === 5; // True
Furthermore, you can use them in events.
; ;
Executing init.then(pour, pour)
on a game instance would give the following output:
POUR: You pour out the famous chili.
POUR: The bucket is already empty!
Note: this example is available here.
Active and Inactive Agents
Agents have a single caveat that may seem strange at first, but is important to understand:
Before an agent's properties can be accessed in a game cycle, the agent must first be activated.
To activate an agent simply means to register it with the GameInstance
. Much like how all changes to game state need to take place inside tracked events, the agents that contain this state need to be tracked as well.
Activating an agent allows it to be tracked by the GameInstance
, and can happen either implicitly or explicitly.
If you try to modify an inactive agent within a game cycle, a RegalError
will be thrown. For example:
;
Executing illegalEvent
will throw the following error:
RegalError: The properties of an inactive agent cannot be set within a game cycle.
Note: this example is available here.
Activating Agents Implicitly
Most of the time, agents will activate themselves without you having to do any extra work. In fact, you've already seen an example of this with the Bucket
agent above.
One of the ways that agents may be activated implicitly is by setting them as a property of an already-active agent.
;
When our new Bucket
agent was assigned to the game instance's state.bucket
property, it was activated implicitly. This is because GameInstance.state
is actually an active agent.
Whenever agents are set as properties of
GameInstance.state
, they are activated implicitly.
There are five ways to activate agents implicitly. The first way, which was demonstrated above, is to set the agent as a property of an active agent.
game.state.myAgent = new Parent1; // #1 is activated by GameInstance.stategame.state.myAgent.child = new Parent2; // #2 is activated by #1
Second, all agents that are properties of an inactive agent are activated when the parent agent is activated.
; // #1 and #2 are both inactivegame.state.myAgent = p; // #1 and #2 are activated by GameInstance.state
Third, all agents in arrays that are properties of an inactive agent are activated when the parent agent is activated.
; // #1, #2, and #3 are inactivegame.state.myAgent = mp; // #1, #2, and #3 are activated by GameInstance.state
Fourth, all agents in an array are activated when it is set as the property of an already-active agent.
game.state.myAgent = new MultiParent1; // #1 is activated by GameInstance.stategame.state.myAgent.children = ; // #2 and #3 are activated by #1
Finally, an agent is activated when it is added to an array that's a property of an already-active agent.
game.state.myAgent = new MultiParent1, ; // #1 and #2 are activated by GameInstance.stategame.state.myAgent.children.pushnew Parent3; // #3 is activated by #1
Note: this example is available here.
Activating Agents Explicitly
Agents can be activated explicitly with GameInstance.using()
.
GameInstance.using()
activates one or more agents and returns references to them. The method takes a single argument, which can be one of a few types.
First, a single agent may be activated.
; // #1 is activated
Second, an array of agents may be activated:
; // #1 and #2 are activated
Finally, the argument can be an object where every property is an agent to be activated:
; // #1 and #2 are activated
Note: this example is available here.
Explicit activation is useful for situations where agents aren't being activated implicitly. If you can't figure out whether an agent is getting activated implicitly or not, you may want to explicitly use GameInstance.using()
just to be safe. Activating an agent multiple times has no effect.
Modifying Inactive Agents
Inactive agents aren't truly immutable. In fact, any property of an inactive agent may be set once before the agent is activated. Otherwise, setting properties within constructors wouldn't be possible.
Technically, this means doing something like this is valid (although not recommended):
;a as any.someOtherProperty = "foo";game.state.myAgent = a;
It's important to note that the properties of inactive agents are only inaccessible within the context of a game cycle (i.e. inside a TrackedEvent
). When agents are declared outside of events, they are called static agents and have special characteristics. These are explained in the next section.
Static Agents
All Regal game data is considered either static or instance-specific. The agents we've created up to this point have all been instance-specific, meaning that they are defined inside events and their data is stored in the instance state.
Although it's perfectly valid to store every agent in the instance state, this usually isn't necessary. Most games have a lot of data that is rarely, or never, modified. Rather than storing this data in every GameInstance
, it's more efficient to store these agents in the game's static context. Remember, static data is defined outside the game cycle and is separate from the instance state.
Static agents are agents defined outside of the game cycle.
Once activated, static agents can be used inside events just like non-static agents.
// Declare a new agent class called Book // Declare a static Book agent; ;
Note: This example is available here.
Executing read
on a GameInstance
would produce the following output:
READ: You open Frankenstein, by Mary Shelley.
READ: It begins, "To Mrs. Saville, England..."
Unlike non-static agents, static agents may have their properties read or modified, but only outside of the game cycle. For example, this would be okay:
; NOVEL.title += ", or The Modern Prometheus"; // No error!
In order to use a static agent's properties within a game cycle, however, it must be activated.
This event modifies several properties of the NOVEL
static agent:
;
Executing the queue revise("Lars", "Pancakes!").then(read)
on a GameInstance
would produce the following output:
READ: You open Frankenstein, by Mary Shelley (with a forward by Lars).
READ: It begins, "Pancakes! To Mrs. Saville,..."
Both events, read
and revise
, activated NOVEL
independently of each other, yet the changes made in revise
were still there in read
. This is because the changes made to static agents are stored in the instance state. The static agent's properties that weren't changed (in this case, just the author) don't need to be stored in the state.
Static agents save space by storing only the changes made to their properties by a specific game instance in that instance's state.
If two different game instances reference the same static agent, their changes will not affect each other. This is because changes made to a static agent don't actually modify the static agent at all; they are simply stored in the instance state.
Randomness
Randomness is an essential part of many games, so the Regal Game Library provides a convenient way to generate random values through the GameInstance
.
Generating Random Values
GameInstance.random
contains an object of type InstanceRandom
, which is an interface for generating random values.
InstanceRandom
has methods for generating random integers, decimals, strings, and booleans.
;
Note: This example is available here.
Executing randos
on a GameInstance
would produce values like the following:
RANDOS: Boolean -> true
RANDOS: Int -> 5
RANDOS: Decimal -> 0.38769409744713784
RANDOS: String -> qj$4d-28DX
InstanceRandom.string()
has an optional charset
property that can be used to specify the characters that are chosen from when the string is generated.
For example, if charset
is "aeiou"
, then the random string will only contain vowels.
Charsets
is a static object that contains several common charset strings for this purpose.
; ;
Executing rstrings
on a GameInstance
would produce values like the following:
RSTRINGS: Alphanumeric -> AEeLn85uLT
RSTRINGS: Alphabet -> RoGfYDtwcL
RSTRINGS: Numbers -> 2132069253
RSTRINGS: Hex -> 69072CF9B5
RSTRINGS: Binary -> 1111011001
RSTRINGS: Old MacDonald had a farm, oeioe.
InstanceRandom.choice()
chooses a random element from an array without modifying anything. This works with arrays of primitives or agents.
; ;
Executing init.then(rpick, rpick, rpick)
on a GameInstance
would produce values like the following:
RPICK: You picked the Pigeon!
RPICK: You picked the Yo-Yo!
RPICK: You picked the Pigeon!
Deterministic Randomness
Regal games are by definition deterministic, meaning that they always return the same output when given the same input. The methods for generating random values described above might seem to disobey this principle, but they do not.
When a GameInstance
is created, it is given a special value called a seed. This seed value initializes the game instance's internal random number generator and has a direct influence on all random values that come out of it.
The seed value may be set manually as a configuration option. If no seed is set, one will be generated randomly at runtime.
A game instance's seed is considered part of its input. Therefore, it plays a factor in determining the game's output. If two game instances have the same seed, they will generate the exact same sequence of random values. If a GameInstance
is reset with an undo command, then its random value stream will be reset as well.
In order for Regal to work properly, all random values should be generated by InstanceRandom. JavaScript's Math.Random()
or other libraries for generating random values are not recommended.
Output
Up to this point, you've probably noticed the other examples calling game.output.write()
to send messages to the game's output. write()
is one of several methods provided by InstanceOutput
, the interface for controlling and emitting output through the GameInstance
.
InstanceOutput
is accessible through GameInstance.output
.
Output is handled line-by-line. An OutputLine
is modeled as a text string (data
) with a property that specifies its semantic meaning (type
). These types, which are stored as an enum called OutputLineType
, include NORMAL
, MAJOR
, MINOR
, DEBUG
, and SECTION_TITLE
.
InstanceOutput
contains a method for emitting each of these types of output.
;
Note: This example is available here.
Executing the out
event on a GameInstance
would produce the following output:
OUT: This is normal output. Most of your game's output will be this type.
OUT: InstanceOutput.write is just a shorthand for writeNormal!
OUT: This is major output. Save this for really important stuff.
OUT: This is minor output. Use this for repetitive/flavor text that isn't necessary for the player to see.
OUT: This is a title.
Notice that the line of debug output didn't show up. That's because output lines of type DEBUG
are only emitted when the debug
option is set to true, and it is set to false by default. Similarly, output lines of type MINOR
are only emitted when the showMinor
option is set to true, which is its default value.
Executing the event again with debug: true
and showMinor: false
in the game configuration produces the following output:
OUT: This is normal output. Most of your game's output will be this type.
OUT: InstanceOutput.write is just a shorthand for writeNormal!
OUT: This is major output. Save this for really important stuff.
OUT: This is debug output. It's only visible when the debug option is enabled.
OUT: This is a title.
Despite each of these lines having different types, they all look the same when printed via the default Regal client. This is because OutputLine.type
is left to the client for interpretation; it's up to the Regal client to decide how each type of output is handled. For more information, see Handling Responses.
GameApi
and API Hooks
Regal games are played through a GameApi
, which is the public interface for interacting with a Regal game. A client application will consume this API, using its methods to generate game instances, post commands, and receive output.
GameApi
has the following signature:
Each method is a different type of command that can be sent to the Regal game and returns some GameResponse
.
Three of these commands, postPlayer
, postUndo
, and postOption
, all require the current GameInstance
. The other two, getMetadata
and postStart
, do not.
Some of these commands will be described in more detail below. Consult the API Reference for a complete description.
Handling Commands with API Hooks
Out of the five game commands listed above, two of them must be handled explicitly by the game developer. These commands, postStart
and postPlayer
, are handled by the hook functions onStartCommand
and onPlayerCommand
respectively.
A hook function is used to control what happens when a command is received by the GameApi
.
onStartCommand
takes an EventFunction
as its only argument. This event function is executed when a postStart
command is sent to the GameApi
.
; onStartCommand;
With this hook in place, starting a new game will return the following output to the player:
START: Hello, world!
onPlayerCommand
is slightly more complicated. It takes a function which receives a string as input and outputs an EventFunction
. This string will be any text command sent by the player and the event will be executed when a postPlayer
command is sent to the GameApi
.
; onPlayerCommand;
With this hook in place, a player's input (like dance
) will return the following output:
INPUT: You wrote 'dance'!
Both onStartCommand
and onPlayerCommand
must be called exactly once from somewhere in your game's source.
Game
Using The Regal Game Library's global implementation of the extended game API is called Game
. The Game
object is used for external interaction with the game and shouldn't be accessed within the game itself.
Usually, you won't use Game
directly. Regal games should be bundled before they are used by clients, and these bundles have a different way of exposing a GameApi
. See bundling for more information.
However, there are certain cases where you might prefer to access Game
directly, such as for unit tests. In these situations, both Game
and your game's source need to be imported.
;; // Imports the game's root file, which has no exports
Before any command may be executed, Game.init()
must be called first. This method takes a GameMetadata
object, which must contain at least a name
and an author
.
Game.init;
Using the hooks defined earlier, a new game instance can be generated with Game.postStartCommand()
:
;
The resulting GameResponse
has two properties: instance
and output
. instance
refers to the current GameInstance
. output
contains all output generated by the last game cycle, which here would be:
Similarly, a player command can be executed with Game.postPlayerCommand()
. This method takes two arguments: instance
and command
. instance
refers to the current GameInstance
, and command
is a string containing the player's command.
;
This response contains a new GameInstance
, leaving the original instance unchanged, and the following output:
When a client plays a Regal game, all it has to do behind the scenes is send a series of these commands.
Note: This example is available here.
Undoing Player Commands
Because Regal games are deterministic, the effects of any command on the GameInstance
can be undone. Essentially, this "rolls back" the instance state to the state it was at before the reverted command was executed.
For example, here is a game that stores each command in an array in the state:
// undo-game-src.ts; // Print out the list of items in the state; onStartCommand; onPlayerCommand;
Note: This example is available here.
Executing a start command would produce the following output:
ITEMS: Items (0) -> []
Executing three player commands, cat
, dog
, and mouse
, would result in this output:
INPUT: Adding cat.
ITEMS: Items (1) -> [cat]
INPUT: Adding dog.
ITEMS: Items (2) -> [cat, dog]
INPUT: Adding mouse.
ITEMS: Items (3) -> [cat, dog, mouse]
A client can undo the last player or undo command executed on the GameInstance
with Game.postUndoCommand()
. This method takes only one argument, which is the current GameInstance
.
If the following line was executed:
;
Then newInstance
would contain the state of the instance from before the player's mouse
command was executed. oldInstance
would remain unchanged.
If another player command, goose
, was executed, then this would be the output:
Adding goose.
Items (3) -> [cat, dog, goose]
While being able to undo commands can be quite useful, it doesn't make sense for all games. Therefore, the game developer can control whether the GameApi
should allow clients to execute undo commands. This is accomplished with an API hook function, onBeforeUndoCommand
.
Like the other hook functions, onBeforeUndoCommand
controls the behavior of the Game API. Its only argument is a function that receives a GameInstance
and returns a boolean.
Whenever a postUndo
command is sent to the GameApi
, the beforeUndo
hook is executed on the current GameInstance
. If the function returns true, the undo will proceed. If it returns false, an error will throw instead.
To prevent all undo commands, simply make the beforeUndo
hook always return false:
; onBeforeUndoCommandfalse;
With this configuration, executing a postUndo
command would return an unsuccessful response with the following error:
RegalError: Undo is not allowed here.
onBeforeUndoCommand
can be used to allow or deny undo commands based on some condition of the game state. For instance, the following hook prevents our example from being undone when a previous player command was goose
:
onBeforeUndoCommand!game.state.items.includes"goose" // Block undo if there's a goose in the array;
A call to onBeforeUndoCommand
is not required, and will default to always allowing undo commands if not otherwise set.
Configuration
A Regal game's configuration consists of two types: GameMetadata
and GameOptions
.
GameMetadata
contains metadata about the game, such as its title and author. GameOptions
contains configurable options to control the game's behavior.
Using Configuration Files
Configuration for a Regal game is usually kept in the project's root directory, in a file called regal.json
. This file contains both metadata and options.
A game's regal.json
file might look something like this:
Note that a regal.json
file is optional. The essential properties of GameMetadata
, which are name
and author
, as well as others properties will be taken from those of the same keys in package.json
if they aren't specified in a regal.json
file.
A configuration loading tool like regal-bundler is needed if using regal.json
or the regal
property in package.json
. See bundling for more information.
Alternatively, metadata values can be passed explicitly via GameApiExtended.init()
. Either way, a metadata object with at least the name
and author
properties specified is required before a game can receive commands.
; Game.init;
Configuring Game Options
The Regal Game Library provides several options for configuring the behavior of game instances. These options are stored as an interface called GameOptions
.
When a game is initialized, either through GameApiExtended.init()
or loading a regal.json
file, any values provided in GameMetadata.options
will become the default values for every instance of that game.
Here is an example:
// options-demo.ts;; Game.init;
With this configuration, every instance of My Game
would start with its debug
option set to true instead of false, and its seed
set to "Hello!".
A convenient way to check the values of an instance's GameOptions
is by using GameInstance.options
, which is a read-only container for all current options in the instance. The property is of type InstanceOptions
.
This can be combined with InstanceOutput.writeDebug()
to produce some helpful information for development:
// options-game-src.tsonStartCommand;
Starting a new game instance using the configuration from earlier would yield the following output:
START: Startup successful.
START: Current seed -> Hello!
If you wanted to generate a GameInstance
with debug
disabled, you could pass in any option overrides to the optional argument of Game.postStartCommand()
.
// options-demo.ts;
With debug
set to false, res
would contain only the one line of output:
START: Startup successful.
To modify the options of a pre-existing GameInstance
, use Game.postOptionCommand()
.
res = Game.postOptionCommandres.instance, ;
This would disable showMinor
for all future game cycles of the GameInstance
(unless it got changed again).
Note: This example is available here.
The priority of GameOptions
from highest to lowest is:
- Instance-specific overrides (i.e. values passed in
Game.postStartCommand()
orGame.postOptionCommand()
). - Static overrides (i.e. values loaded from
regal.json
or set withGame.init()
). - The options' default values.
Bundling
In order for a Regal game to be played by clients, it must be bundled first. Bundling is the process of converting a Regal game's development source (i.e. the TypeScript source files that the game developer writes) into a game bundle, which is a self-contained file that contains all the code necessary to play the game via a single API.
Game bundles are the form through which Regal games are shared, downloaded, and played.
Using the CLI
The easiest way to bundle a game is with the Regal CLI.
Once you have the CLI installed, use the bundle
command to generate a game bundle.
$ regal bundle
Created a game bundle for 'my-first-game' at /Users/user/myDir/my-first-game.regal.js
This creates a JavaScript file, which contains all the game's dependencies (including the Game Library) and exports an implementation of GameApi
for playing the game. By default, the generated bundle file will be named my-game.regal.js
, where my-game
is a sanitized version of the game's name, as specified in GameMetadata
.
For a list of configuration options you can use, consult the CLI's documentation.
regal-bundler
Using You can access the bundling API directly with regal-bundler.
First, install regal-bundler
:
npm install --save-dev regal-bundler
Generate a game bundle with the asynchronous bundle()
function.
; bundle; // Creates a bundle file
bundle()
accepts an optional configuration object that can be used to control certain behaviors of the bundler, such as the location of input
or output
files, the module format
of the bundle, or whether minification should be done on the bundle after it's generated.
This configuration object can either be passed into the bundle()
function itself or included under the "bundler"
property in regal.json
.
Playing a Bundled Game
A standard game bundle exports an implementation of GameApi
, which is used to play the game.
The Regal CLI can be used to play a game bundle from the terminal via the play
command:
$ regal play my-first-game.regal.js
Now Playing: my-first-game by Your Name Here
Type :quit to exit the game.
Hello, World!
Alternatively, the bundle's GameApi
can be imported as a JavaScript module and accessed programmatically:
; ;resp = myGame.postPlayerCommandresp.instance, "Hello, World!";
Note: This example is available here.
Once your game is bundled, only the bundle file is needed to play it.
API Reference
Agent
AgentMeta
Charsets
EventFunction
EventQueue
Game
GameApi
GameApiExtended
GameEventBuilder
GameInstance
GameMetadata
GameOptions
GameResponse
GameResponseOutput
InstanceEvents
InstanceOptions
InstanceOutput
InstanceRandom
OutputLine
OutputLineType
PK
RegalError
TrackedEvent
enqueue
noop
nq
on
onBeforeUndoCommand
onPlayerCommand
onStartCommand
Agent
Class
A game object.
Description
A game object, or agent, is a JavaScript object that contains Regal game state. Every agent should inherit from the Agent
class.
Before an agent's properties can be accessed in a game cycle, the agent must be activated. Activating an agent registers the agent with the game instance, and can happen either explicitly or implicitly.
An inactive agent is activated explicitly with GameInstance.using()
.
;
Alternatively, an inactive agent that's contained in another agent's property can be activated implicitly when that owner agent is activated. Here is one way this can happen:
;owner.child = new CustomAgent"child"; ; // owner.child is activated as well
In total, there are five ways for an inactive agent (child
) to be activated implicitly by some agent (owner
):
child
is a property ofowner
whenowner
is activated.child
is set as a property ofowner
, which is already activated.child
is stored in an array that is a property ofowner
whenowner
is activated.child
is stored in an array that is set as a property ofowner
, which is already activated.child
is added to an array that is a property ofowner
, which is already activated.
Trying to read or modify a property of an agent that hasn't been activated will throw a RegalError
.
Constructor
Constructs a new Agent
. This constructor should almost never be called directly, but rather should be called with super()
from a subclass.
If called in the game's static context (i.e. outside of a game cycle), a static agent will be created, and an id will be reserved for this agent for all game instances.
Properties
Property | Description |
---|---|
meta: AgentMeta |
The agent's metadata, such as its agent id and prototype id. |
AgentMeta
Interface
A special object associated with every Agent
which contains important metadata related to that Agent
.
Description
Properties in AgentMeta
do not have their changes tracked through InstanceEvents
like all other agent properties.
Properties
Property | Description |
---|---|
id: PK<Agent> |
The agent's unique identifier in the context of the current game. |
protoId: PK<"AgentProto"> |
The unique identifier for the agent's prototype. |
Charsets
Const Object
Common charsets used for pseudo-random string generation.
Description
For use with InstanceRandom.string()
.
Readonly Properties
Property | Value | Description |
---|---|---|
EXPANDED_CHARSET: string |
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~!@#$%^&*()-_=+{}[]\|;:<>,.? |
Contains all letters (upper- and lower-case), numbers, and some special characters. |
ALHPANUMERIC_CHARSET: string |
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 |
Contains all letters (upper- and lower-case) and numbers. |
ALPHABET_CHARSET: string |
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz |
Contains all letters (upper- and lower-case). |
NUMBERS_CHARSET: string |
0123456789 |
Contains all numbers. |
EventFunction
Type Alias
A function that modifies a GameInstance
.
Subtypes
Generic Type Parameters
Parameter | Description |
---|---|
StateType = any |
The GameInstance state type. Optional, defaults to any . |
Parameters
Parameter | Description |
---|---|
game: GameInstance<StateType> |
The game instance to be modified. |
Returns
EventFunction<StateType> | void
: The next EventFunction
to be executed, or void
if there are no more events.
EventQueue
Interface
Contains a queue of TrackedEvent
s to be executed sequentially.
Description
An EventQueue
is a special type of TrackedEvent
that contains a sequence of TrackedEvent
s. When an EventQueue
is invoked, it immediately invokes every event in immediateEvents
sequentially. The events in delayedEvents
are added to the end of the game instance's internal queue and will be invoked sequentially once all other events are finished.
The EventQueue
/TrackedEvent
API is chainable and can be used to invoke a complicated sequence of events.
firstEvent.thensecondEvent, thirdEvent.enqueuelastEvent
Extends
Generic Type Parameters
Parameter | Description |
---|---|
StateType = any |
The GameInstance state type. Optional, defaults to any . |
Properties
Property | Description |
---|---|
immediateEvents: Array<TrackedEvent<StateType>> |
The events to be added to the beginning of the game's event queue. |
delayedEvents: Array<TrackedEvent<StateType>> |
The events to be added to the end of the game's event queue. |
Methods
enqueue()
Adds events to the end of the event queue.
enqueue...events: Array<TrackedEvent<StateType>>: EventQueue<StateType>
Parameters
Parameter | Description |
---|---|
...events: Array<TrackedEvent<StateType>> |
The events to be added. |
Returns
EventQueue<StateType>
: A new EventQueue
with the new events added to the queue.
nq()
Alias of EventQueue.enqueue()
.
nq...events: Array<TrackedEvent<StateType>>: EventQueue<StateType>
Game
Const Object
Global implementation of GameApiExtended
.
Description
The Game
object serves as an exportable API for playing the game. It is used for external interaction with the game, and shouldn't be accessed within the game itself.
Implements
GameApi
Interface
Public API for interacting with the Regal game.
Description
A client application will consume this API, using the endpoints to generate game instances and receive output.
Subtypes
Methods
getMetadataCommand()
Gets the game's metadata. Note that this is not specific to any GameInstance
, but refers to the game's static context.
getMetadataCommand: GameResponse
Returns
GameResponse
: A game response containing the game's metadata as output if the request was successful. Otherwise, the response will contain an error.
postPlayerCommand()
Submits a command that was entered by the player, usually to trigger some effects in the GameInstance
.
If the onPlayerCommand
hook has not been implemented, an error will be thrown.
postPlayerCommandinstance: GameInstance, command: string: GameResponse
Parameters
Parameter | Description |
---|---|
instance: GameInstance |
The current game instance (will not be modified). |
command: string |
The player's command. |
Returns
GameResponse
: A game response containing a new GameInstance
with updated values and any output logged during the game cycle's events if the request was successful. Otherwise, the response will contain an error.
postStartCommand()
Triggers the start of a new game instance.
If the onStartCommand
hook has not been implemented, an error will be thrown.
postStartCommandoptions?: Partial<GameOptions>: GameResponse
Parameters
Parameter | Description |
---|---|
options?: Partial<GameOptions> |
Any option overrides preferred for this specific instance, which must be allowed by the static configuration's allowOverrides option. |
Returns
GameResponse
: A game response containing a new GameInstance
and any output logged during the game cycle's events if the request was successful. Otherwise, the response will contain an error.
postUndoCommand()
Reverts the effects of the last player command on the game instance.
Calls the onBeforeUndoCommand
hook to determine if the undo is allowed. If the hook has not been implemented, undo is allowed by default.
postUndoCommandinstance: GameInstance: GameResponse
Parameters
Parameter | Description |
---|---|
instance: GameInstance |
The current game instance (will not be modified). |
Returns
GameResponse
: A game response containing a new GameInstance
with updated values if the request was successful. Otherwise, the response will contain an error.
postOptionCommand()
Updates the values of the named game options in the game instance.
postOptionCommandinstance: GameInstance, options: Partial<GameOptions>: GameResponse;
Parameters
Parameter | Description |
---|---|
instance: GameInstance |
The current game instance (will not be modified). |
options: Partial<GameOptions> |
The new option overrides, which must be allowed by the static configuration's allowOverrides option. |
Returns
GameResponse
: A game response containing a new GameInstance
with updated options if the request was successful. Otherwise, the response will contain an error.
GameApiExtended
Interface
Extended API for interacting with the Regal game.
Description
Contains the standard methods from GameApi
as well as additional methods for advanced control.
Extends
Object Implementations
Properties
Property | Value |
---|---|
readonly isInitialized: boolean |
Whether Game.init() has been called. |
Methods
init()
Initializes the game with the given metadata. This must be called before any game commands may be executed.
initmetadata: GameMetadata: void
Properties
Property | Value |
---|---|
metadata: GameMetadata |
The game's configuration metadata. |
reset()
Resets the game's static classes.
reset
GameEventBuilder
Type alias
Type alias for the on
function, which creates a TrackedEvent
.
Description
Used for situations where the game developer wants to refer to a parameterized version of on
as its own function.
;
For descriptions of the parameters and return value, see on
.
Generic Type Parameters
Parameter | Description |
---|---|
StateType = any |
The GameInstance state type. Optional, defaults to any . |
GameInstance
Interface
Represents a unique instance of a Regal game, containing the game's current state and all interfaces used to interact with the game during a game cycle.
Description
Instance state is a snapshot of a Regal game that is unique to a player, containing the modifications caused by all of the player's commands up to that point.
A game's static context is immutable data that is the same for every player regardless of their commands, whereas a GameInstance
is the player's unique instance of the game.
Generic Type Parameters
Parameter | Description |
---|---|
StateType = any |
The GameInstance state type. Optional, defaults to any . |
Properties
Property | Description |
---|---|
events: InstanceEvents |
The manager for all events in the instance. |
output: InstanceOutput |
The manager for all output in the instance. |
options: InstanceOptions |
Read-only container for all current options in the instance. |
random: InstanceRandom |
The manager for generating repeatable random numbers through the game instance. |
state: StateType |
Free-form agent to contain any instance state desired by the game developer. Properties set within this object are maintained between game cycles, so it should be used to store long-term state. |
Methods
using()
Activates one or more agents in the current game context.
usingresource: T: T
Description
All agents must be activated before they can be used. Activating an agent multiple times has no effect.
A single agent may be activated:
;
Or, an array of agents:
;
Or, an object where every property is an agent to be activated:
;
Generic Type Parameters
Parameter | Description |
---|---|
T |
The type of resource that is activated. |
Parameters
Parameter | Description |
---|---|
resource: T |
Either a single agent, an agent array, or an object where every property is an agent to be activated. |
Returns
T
: Either an activated agent, an agent array, or an object where every property is an activated agent, depending on the structure of resource
.
GameMetadata
Interface
Metadata about the game, such as its title and author.
Description
Metadata values can be specified in the optional regal.json
file or regal
property of package.json
, but are not required. If using regal.json
, the metadata properties should be placed in an object with the key game
.
Property Rules:
- If any of the metadata properties
name
,author
,description
,homepage
, orrepository
aren't specified, the values of each property with the same name inpackage.json
will be used. gameVersion
will be loaded frompackage.json
only.regalVersion
should not be specified, as it is set by the library automatically. If a value is passed forregalVersion
, an error will be thrown.
A configuration loading tool like regal-bundler is needed if using regal.json
or the regal
property in package.json
. Alternatively, metadata values can be passed explicitly via GameApiExtended.init()
. Either way, a metadata object with at least the name
and author
properties specified is required before a game can receive commands.
This metadata is defined in the game's static context, meaning that it is the same for all instances of the game.
Properties
Property | Description |
---|---|
name: string |
The game's title. |
author: string |
The game's author. |
headline?: 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?: Partial<GameOptions> |
Any options overrides for the game. |
regalVersion?: string |
The version of the Regal Game Library used by the game. |
gameVersion?: string |
The game's version. |
GameOptions
Interface
Configurable options for the game's behavior.
Child Interfaces
Properties
Property | Description |
---|---|
readonly allowOverrides: string[] \| boolean |
Game options that can be overridden by a Regal client. Can be an array of strings or a boolean. Defaults to true. If an array of strings, these options will be configurable by a Regal client. Note that allowOverrides is never configurable, and including it will throw an error. If true , all options except allowOverrides will be configurable. If false , no options will be configurable. |
readonly debug: boolean |
Whether output of type DEBUG should be returned to the client. Defaults to false. |
readonly showMinor: boolean |
Whether output of type MINOR should be returned to the client. Defaults to true. |
readonly trackAgentChanges: boolean |
Whether all changes to agent properties are tracked and returned to the client. Defaults to false. If false , only the values of each property at the beginning and end of each game cycle will be recorded. If true , all property changes will be recorded. |
readonly seed: string \| undefined |
Optional string used to initialize pseudorandom number generation in each game instance. When multiple instances have the same seed, they will generate the same sequence of random numbers through the InstanceRandom API. If left undefined, a random seed will be generated. |
GameResponse
Interface
Response object of every GameApi
method, which contains some output and a GameInstance
if applicable.
Properties
Property | Description |
---|---|
instance?: GameInstance |
The new instance state of the game. Will not be defined if an error occurred during the request or if getMetdataCommand was called. |
output: GameResponseOutput |
The output generated by the request, which will vary in structure depending on the request and if it was successful. |
GameResponseOutput
Interface
The output component of a response generated by a request to the GameApi
.
Properties
Property | Description |
---|---|
wasSuccessful: boolean |
Whether the request was executed successfully. |
error?: RegalError |
The error that was thrown if wasSuccessful is false. |
log?: OutputLine[] |
Contains any lines of output emitted because of the request. |
metadata?: GameMetadata |
Contains the game's metadata if getMetdataCommand was called. |
InstanceEvents
Interface
Manager for all events in a GameInstance
.
Description
Every event that occurs on a GameInstance
passes through this interface, although most of the time this happens internally.
Methods
invoke()
Executes the given event and all events caused by it.
invokeevent: TrackedEvent: void
Parameters
Parameter | Description |
---|---|
event: TrackedEvent |
The TrackedEvent to be invoked. |
InstanceOptions
Interface
Read-only container that provides an interface to view the game instance's current game options.
Description
Has an identical signature to GameOptions
.
Setting any properties of GameInstance.options
will throw a RegalError
.
Extends
InstanceOutput
Interface
Interface for managing and emitting output through a GameInstance
.
Description
Output is modeled as lines with properties specifying their semantic meaning. For more information, see OutputLine
.
Properties
Property | Description |
---|---|
readonly lineCount: number |
The number of OutputLine s that have been generated over the life of the GameInstance . |
lines: OutputLine[] |
The OutputLine s generated during the current game cycle. |
Methods
writeLine()
Writes a single line of output to the client.
writeLineline: string, lineType?: OutputLineType: void
Parameters
Parameter | Description |
---|---|
line: string |
The text string to be emitted. |
lineType?: OutputLineType |
The line's semantic meaning. (Defaults to OutputLineType.NORMAL ) |
write()
Writes one or more lines of type OutputLineType.NORMAL
to the output.
write...lines: string: void
Parameters
Parameter | Description |
---|---|
...lines: string[] |
The text to be emitted. |
writeNormal()
Writes one or more lines of type OutputLineType.NORMAL
to the output.
writeNormal...lines: string: void
Parameters
Parameter | Description |
---|---|
...lines: string[] |
The text to be emitted. |
writeMajor()
Writes one or more lines of type OutputLineType.MAJOR
to the output.
writeMajor...lines: string: void
Parameters
Parameter | Description |
---|---|
...lines: string[] |
The text to be emitted. |
writeMinor()
Writes one or more lines of type OutputLineType.MINOR
to the output.
writeMinor...lines: string: void
Parameters
Parameter | Description |
---|---|
...lines: string[] |
The text to be emitted. |
writeDebug()
Writes one or more lines of type OutputLineType.DEBUG
to the output.
writeDebug...lines: string: void
Parameters
Parameter | Description |
---|---|
...lines: string[] |
The text to be emitted. |
writeTitle()
Writes a line of type OutputLineType.SECTION_TITLE
to the output.
writeTitleline: string: void
Parameters
Parameter | Description |
---|---|
line: string |
The text to be emitted. |
InstanceRandom
Interface
Interface for generating deterministic, pseudo-random data for the game instance.
Description
The data are considered deterministic because any InstanceRandom
with some
identical seed
will generate the same sequence of pseudo-random values.
Properties
Property | Description |
---|---|
readonly seed: string |
The string used to initialize the pseudo-random data generator. |
Methods
int()
Generates a pseudo-random integer within the given inclusive range.
intmin: number, max: number: number
Parameters
Parameter | Description |
---|---|
min: number |
The minimum possible number (inclusive). |
max: number |
The maximum possible number (exclusive). |
Returns
number
: A pseudo-random integer within the given inclusive range.
decimal()
Generates a pseudo-random number between zero (inclusive) and one (exclusive).
decimal: number
Returns
number
: A pseudo-random number between zero (inclusive) and one (exclusive).
string()
Generates a string of pseudo-random characters (duplicate characters allowed).
stringlength: number, charset?: string: string
Parameters
Parameter | Description |
---|---|
length: number |
The length of the string to generate. |
charset?: string |
A string containing the characters to choose from when generating the string. Duplicates are okay, but the charset must have at least two unique characters. (Defaults to Charsets.EXPANDED_CHARSET ) |
Returns
string
: A string of pseudo-random characters.
choice()
Returns a pseudo-random element from the given array without modifying anything.
choicearray: T: T
Generic Type Parameters
Parameter | Description |
---|---|
T |
The type of element in the array. |
Parameters
Parameter | Description |
---|---|
array: T[] |
The array to select from. |
Returns
T
: A pseudo-random element from the given array.
boolean()
Generates either true
or false
pseudo-randomly.
boolean: boolean
Returns
boolean
: Either true
or false
.
OutputLine
Interface
A line of text that is sent to the client and is meant to notify the player of something that happened in the game.
Properties
Property | Description |
---|---|
id: PK<OutputLine> |
The OutputLine 's unique identifier. |
data: string |
The text string. |
type: OutputLineType |
The line's semantic type. (see OutputLineType ) |
OutputLineType
Enum
Conveys semantic meaning of an OutputLine
to the client.
Members
Member | Description |
---|---|
NORMAL = "NORMAL" |
Standard output line; presented to the player normally. (Default) Use for most game content. |
MAJOR = "MAJOR" |
Important line; emphasized to the player. Use when something important happens. |
MINOR = "MINOR" |
Non-important line; emphasized less than OutputLineType.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 = "DEBUG" |
Meant for debugging purposes; only visible when the debug option is enabled. |
SECTION_TITLE = "SECTION_TITLE" |
Signifies the start of a new section or scene in the game. (i.e. West of House) |
PK
Interface
Primary key for an indexed class, a class of which there are many instances that each need a unique identifier.
Description
The PK
interface is mainly for internal use, but as there are other interfaces within the public API which depend on PK
(such as Agent
and OutputLine
), it is part of the public API as well.
Generic Type Parameters
Parameter | Description |
---|---|
T |
An identifier for the class referenced by this PK type. |
Methods
plus()
Generates the primary key that would be generated n
keys after this one. The result of this function should never be used to assign a key to an object. It's only for comparison.
plusn: number: PK<T>
Parameters
Parameter | Description |
---|---|
n: number |
The number of times the returned key should be incremented |
Returns
PK<T>
: The generated primary key
minus()
Generates the primary key that would be generated n
keys before this one. The result of this function should never be used to assign a key to an object. It's only for comparison.
minusn: number: PK<T>
Parameters
Parameter | Description |
---|---|
n: number |
The number of times the returned key should be decremented |
Returns
PK<T>
: The generated primary key
equals()
Calculates whether this key is equivalent to the given one.
equalskey: PK<T>: boolean
Parameters
Parameter | Description |
---|---|
key: PK<T> |
The key to test |
Returns
boolean
: Whether they are equivalent
value()
Generates a string value representative of this key.
value: string
Description
This is used for the equals
method, which is strongly preferred for testing the equality of two keys.
Returns
string
: A hash value representative of the key.
index()
Returns the placement of this key in the list of all keys.
index: number
Description
For example, an index of 2 means this was the second key generated.
This includes reserved keys; if a set of reserved keys was used to generate this key's PKProvider
, the entry with the lowest value will have an index of 0.
Returns
number
: The index of this key.
RegalError
Class
Error that is thrown if a Regal function fails.
Extends
Error
Constructor
Constructs a RegalError
with the given message.
constructormessage: string
Parameters
Parameter | Description |
---|---|
message: string |
The error message, which will be prepended with "RegalError: ". |
TrackedEvent
Interface
An EventFunction
that is tracked by the game instance.
Description
In order for Regal to behave properly, all modifications of game state should take place inside tracked events.
Just like an EventFunction
, a TrackedEvent
can be invoked as a function by passing in a GameInstance
for its only argument.
Extends
Subtypes
Generic Type Parameters
Parameter | Description |
---|---|
StateType = any |
The GameInstance state type. Optional, defaults to any . |
Function Invocation
Parameters
Parameter | Description |
---|---|
game: GameInstance<StateType> |
The GameInstance to be modified. |
Returns
TrackedEvent<StateType> | EventFunction<StateType>
: The next TrackedEvent
or EventFunction
to be invoked on the GameInstance
.
Properties
Property | Description |
---|---|
eventName: string |
The name of the event. |
target: EventFunction<StateType> |
The EventFunction that is wrapped by the TrackedEvent . |
Methods
then()
Adds events to the front of the event queue.
then...events: Array<TrackedEvent<StateType>>: EventQueue<StateType>
Parameters
Parameter | Description |
---|---|
...events: Array<TrackedEvent<StateType>> |
The events to be added. |
Returns
An EventQueue
with the new events.
thenq()
Adds events to the end of the event queue.
thenq...events: Array<TrackedEvent<StateType>>: EventQueue<StateType>
Description
Equivalent to calling trackedEvent.then(nq(...events))
.
Parameters
Parameter | Description |
---|---|
...events: Array<TrackedEvent<StateType>> |
The events to be added. |
Returns
An EventQueue
with the new events.
enqueue
Function
Creates an EventQueue
that adds the events to the end of the game's internal queue, meaning they will be executed after all of the currently queued events are finished.
Description
If the events are EventQueue
s, any events in the queues' immediateEvents
collections will be concatenated, followed by any events in the queues' delayedEvents
.
Generic Type Parameters
Parameter | Description |
---|---|
StateType = any |
The GameInstance state type. Optional, defaults to any . |
Parameters
Parameter | Description |
---|---|
...events: Array<TrackedEvent<StateType>> |
The events to be added. |
Returns
EventQueue<StateType
: An EventQueue
that place all events in the delayedEvent
array when invoked.
noop
Const Object
Reserved TrackedEvent
that signals no more events.
Description
noop
is short for no operation.
Meant to be used in rare cases where an event cannot return void
(i.e. forced by the TypeScript compiler).
on"EVENT", game.state.someCondition ? otherEvent | noop; );
Implements
nq
Function
Alias of enqueue
.
on
Function
Constructs a TrackedEvent
, which is a function that modifies a GameInstance
and tracks all state changes as they occur.
Description
All modifications to game state within a Regal game should take place through a TrackedEvent
.
This function is the standard way to declare a TrackedEvent
.
;
Generic Type Parameters
Parameter | Description |
---|---|
StateType = any |
The GameInstance state type. Optional, defaults to any . |
Parameters
Parameter | Description |
---|---|
eventName: string |
The name of the TrackedEvent . |
eventFunc: EventFunction<StateType> |
The function that will be executed on a GameInstance . |
Returns
TrackedEvent<StateType>
: The generated TrackedEvent
.
onBeforeUndoCommand
Function
GameApi
hook that sets the function to be executed whenever GameApi.postUndoCommand
is called, before the undo operation is executed.
Description
If the handler function returns true
, the undo will be allowed. If it returns false
, the undo will not be allowed. If the hook is never set, all valid undo operations will be allowed.
May only be set once.
onBeforeUndoCommandgame.state.someCondition; // Allows undo if someCondition is true.
Parameters
Parameter | Description |
---|---|
handler: (game: GameInstance) => boolean |
Returns whether the undo operation is allowed, given the current GameInstance . |
onPlayerCommand
Function
GameApi
hook that sets the function to be executed whenever a player command is sent to the Game API via GameApi.postPlayerCommand
.
Description
May only be set once.
Example Usage:
onPlayerCommandon"GREET", ;
Parameters
Parameter | Description |
---|---|
handler: (command: string) => EventFunction |
A function that takes a string containing the player's command and returns an EventFunction . May be an EventFunction , TrackedEvent , or EventQueue . |
onStartCommand
Function
GameApi
hook that sets the function to be executed whenever a start command is sent to the Game API via GameApi.postStartCommand
.
Description
May only be set once.
Example Usage:
onStartCommandgame.output.write"Startup successful!";
Parameters
Parameter | Description |
---|---|
handler: EventFunction |
The EventFunction to be executed. May be an EventFunction , TrackedEvent , or EventQueue . |
Copyright (c) Joe Cowman