Cosmos Boulders
Version 0.2 | Play it online @ catseye.tc | See also: Bubble Escape ∘ Nested Modal Transducers

An arcade-style HTML5 minigame built with immutable data and reducers in ES5 JavaScript.
Goals
The main goals are to demonstrate:
- that nested state machines are a good way to describe video games structurally.
- that reducers are an acceptable way to implement nested extended state machines.
- that a simple but full-featured arcade game can be written in a few hundred lines of (JavaScript) code.
I say reducers are "acceptable" because I think Nested Modal Transducers would be better in several respects. The long-term plan is to write a version using them instead, to compare.
Non-goals
- It's not intended to be a very playable, very original game.
State machine diagram
This diagram was generated by State Machine Cat from the description in cosmos-boulders.smcat. While it does not capture every detail, it does show the overall structure.

Tour of the code
The code uses Immutable.js to implement immutable data, and Redux.js to implement reducers. Neither library is used so extensively that it couldn't be easily eliminated, with the relevant parts of the code written "from scratch".
The code was originally written as one big script file, but has since been split up into individual files formatted as ES6 modules.
Some utility functions are exposed in utils.js These should be fairly
self-explanatory. Some of them are designed to work with objects and actions
inside reducers.
The logic of the objects in the game is captured in the four modules
player.js, missile.js, boulder.js, and game.js.
Each of these modules contains
- (optionally) a "reset" function that takes an object and returns a version of that object which is reset to its starting state
- a "make" function that returns a new object of that type. It may take some arguments, which influence the created object. This function usually builds on the reset function.
- a reducer function that takes an object and an action and returns a new object.
- (optionally) other helper functions that the reducer function uses.
One action that all of the reducers for all of these kinds of objects take,
is STEP, which progresses the state of the entire game by one timeslice
(usually somewhere around 1/60th of a second).
When a state machine is implemented with a reducer, the "state variable"
which can take on a finite set of values, is not called state as might
be expected; instead it is called the mode of the object. This is to
avoid confusion between it and the state of the entire object.
The code tries to be very careful about not modifying game state data in
the reducer functions, to the extent that it does not even overwrite
local variables; instead it introduces a new local variables (called e.g.
game2, game3, etc.) and places the new value in it. (i.e., in ES6
JavaScript, these locals could all be declared const.)
The function for detecting collisions exists at the intersection of all of
the above game objects, and has a signficant complexity of its own, so it
is packaged in its own module, collision.js.
The input-context.js module may seem somewhat roundabout. Strictly
speaking, the InputContext is not necessary, but it is meant to illustrate
the differences between how an arcade machine and a web browser interface
with the world around them.
In a web browser, the DOM translates key presses into "key down" and "key up" events. But in an arcade cabinet, buttons are simply switches that are either make or break when the circuit reads them. The virtual hardware tries to simulate that. But! It's still useful for the game code to see "button pressed" as events that it reacts to. So it's the job of InputContext to synthesize those button level changes back into events for the game reducers.
The display.js module contains the logic for how each game object is
displayed on the video game hardware, separating content from presentation.
The hardware.js module simulates the basic circuitry of a video game
arcade cabinet, including the screen (as a canvas) and the controls (as switches
whose make or break state is reflected in a boolean value). It generates
events when a new video frame is ready to be displayed, and when a coin is
inserted.
Finally, the main.js module sets up the virtual hardware, hooking up the events
to a Redux store, and subscribing to the store to update the video display.
Further reading
- A couple of years ago (2017) I was motivated to finally write down my formulation of A Basic Theory of Video Games, which I had been thinking about on-and-off for many years beforehand.
- Earlier this year (2019) I tried to refine the ideas there into a more formal model that I called Nested Modal Transducers.
- Many years previous to these (2008), James Hague wrote a series of articles about Purely Functional Retrogames, which touches on a few of the same issues.
- John Earnest's Deep: A Postmortem (2011), also touches on a few of the same issues.
Commit History
@master
git clone https://git.catseye.tc/Cosmos-Boulders/
- Small cleanups. Chris Pressey 3 days ago
- Update README, in particular the tour of the code (for modules). Chris Pressey 7 days ago
- Refactor source code files into ES6 modules. Chris Pressey 7 days ago
- Minor cleanups. Chris Pressey 1 year, 5 months ago
- More factoring out. Chris Pressey 1 year, 5 months ago
- Refactor more. Chris Pressey 1 year, 5 months ago
- Factor out more functions. Chris Pressey 1 year, 5 months ago
- Factor some action-specific functions out of reducers. Chris Pressey 1 year, 5 months ago
- Feature: swallow down arrow to prevent unwanted page scrolling. Chris Pressey 1 year, 5 months ago
- Split JavaScript source into a multitude of smaller source files. Chris Pressey 1 year, 5 months ago