diff --git a/README.md b/README.md
index f3f584956d2576d5155cbc6750303f4c49f08aba..92ccded02c4fb216a6eb12ec0a1ea0b4eb445ca2 100644
--- a/README.md
+++ b/README.md
@@ -1,702 +1,714 @@
-# Boost Board Game Application (Fall 2020 Version)
-
-A progressive web app (PWA) for learning and playing the board game Boost.
-
-Boost is a turn-based abstract strategy board game like checkers, chess,
-Xiangqi, or Arimaa.  It was designed to be new and interesting for humans to
-play while still admitting a simple AI and supporting various homework
-assignments on algorithms and data structures in the SOFT 260 course at UNL.
-
-# Quick Start
-
-Recursively clone this repository and `cd` into the root folder:
-
-```
-$ git clone --recursive git@git.unl.edu:soft-core/soft-260/boost-board-game.git
-$ cd boost-board-game
-```
-
-(If you forget `--recursive` when cloning, you can `cd` into your clone and run
-`git submodule update --init --recursive` instead.)
-
-Install dependencies for each of the projects in order:
-
-```
-$ (cd eslint-config; npm install)
-$ (cd boost-game; npm install)
-$ (cd boost-puzzles; npm install)
-$ (cd boost-engine; npm install)
-$ (cd boost-app; npm install)
-```
-
-If you get the error "'@unlsoft/eslint-config@1.0.0' is not in the npm registry"
-during the third, fourth, or fifth steps, you can work around that bug in npm by
-using these commands instead:
-
-```
-$ (cd boost-puzzles; mv package-lock.json backup.json; npm install; cp backup.json package-lock.json; npm install; mv backup.json package-lock.json)
-$ (cd boost-engine; mv package-lock.json backup.json; npm install; cp backup.json package-lock.json; npm install; mv backup.json package-lock.json)
-$ (cd boost-app; mv package-lock.json backup.json; npm install; cp backup.json package-lock.json; npm install; mv backup.json package-lock.json)
-```
-
-Generate the game and engine code:
-
-```
-$ (cd boost-puzzles; npm run generate)
-$ (cd boost-engine; npm run build)
-$ (cd boost-app; npm run generate)
-```
-
-Optionally run the test suites to make sure everything so far is okay:
-
-```
-$ (cd boost-game; npm run test:js -- --watchAll=false)
-$ (cd boost-engine; npm run test:js -- --watchAll=false)
-$ (cd boost-app; npm run test:js -- --watchAll=false)
-```
-
-And then serve the application locally:
-
-```
-$ (cd boost-app; npm start)
-```
-
-Once the app is running, click on the "Rules" button to read the rules of the
-game.
-
-# Development Workflow
-
-The project includes a VSCode workspace named `boost-board-game.code-workspace`.
-Most development tasks can be completed from within VSCode, though command-line
-development is also possible.  The subsections below describe how to perform
-common development tasks.
-
-## Code Generation
-
-As described later in the architecture overview, some projects depend on code
-generated by other projects.  Normally this code generation happens
-automatically when you lint, test, or otherwise run the app, but if you need to
-force an update (for example, if you change the game or engine code while the
-app is still running), you can use project's `generate` script.  From VSCode,
-run the project's `generate` script from the "NPM SCRIPTS" tray.  Or, on the
-command line:
-
-```
-$ (cd …; npm run generate)
-```
-
-## Linting
-
-VSCode should automatically detect each project's ESLint configuration and
-dependencies and run them on the fly as you edit the code, though you may have
-to tell VSCode to trust the ESLint instance the first time you edit a project.
-
-Alternatively, you can also manually lint.  From VSCode, run the project's
-`lint` script from the "NPM SCRIPTS" tray.  Or, on the command line:
-
-```
-$ (cd …; npm run lint)
-```
-
-## Unit Testing
-
-The unit test suites are set up to run in "watch" mode, which means that you can
-start the test script once, and it will rerun the test suite every time you save
-a code change.  From VSCode, run the project's `test` script from the "NPM
-SCRIPTS" tray.  Or, on the command line:
-
-```
-$ (cd …; npm run test)
-```
-
-If you want to run the tests only once and not watch for code changes, you can
-pass the `--watchAll=false` option to the underlying script:
-
-```
-$ (cd …; npm run test:js -- --watchAll=false)
-```
-
-Likewise, if you want to only run tests whose names match some text, you can
-pass the standard `-t` option:
-
-```
-$ (cd …; npm run test:js -- -t '[some test-name text]')
-```
-
-Options can be combined:
-
-```
-$ (cd …; npm run test:js -- --watchAll=false -t '[some test-name text]')
-```
-
-## System Testing
-
-The main app is set up to run in "watch" mode, which means that you can start it
-once, and it will refresh the page in your browser every time you save a code
-change.  From VSCode, run the project's `start` script from the "NPM SCRIPTS"
-tray.  Or, on the command line:
-
-```
-$ (cd boost-app; npm run start)
-```
-
-These automatic refreshes will *not* clear any persisted Redux state.  Because
-the app uses the local storage engine from `redux-persist` for persistence, you
-can clear your data during development by running `localStorage.clear()` in the
-developer console and then manually refreshing the page.
-
-## Deployment
-
-The main app runs entirely client-side, so it can be deployed as a `build`
-folder to be placed on any hosting platform.  From VSCode, run the project's
-`build` script from the "NPM SCRIPTS" tray.  Or, on the command line:
-
-```
-$ (cd boost-app; npm run build)
-```
-
-At the end of the command's output you should see a link to further deployment
-instructions.
-
-# Architecture Overview
-
-The code for the Boost board game application is organized as follows:
-
-*   The project `@unlsoft/eslint-config` contains the ESLint configuration for
-    the coding style used across the other projects.  Per `create-react-app`
-    convention, in a development build of the main app, a separate, weaker
-    coding style also warns at runtime about likely bugs.
-
-*   The project `@unlsoft/boost-game` is responsible for representing positions
-    as bit boards and for features that rely on bit-board operations to run
-    acceptably fast, primarily move generation and static evaluation.  Because
-    so much of the bit-board logic can be precomputed and unrolled,
-    `@unlsoft/boost-game` does not implement game logic itself, but is actually
-    a parameterized code generator that writes game logic into separate
-    JavaScript files.
-
-*   The project `@unlsoft/boost-engine` contains the game-playing engine, the
-    application's AI.  Like other abstract strategy board game engines, this
-    engine is designed to run in a separate thread or process from any UI, and
-    it communicates with a controller using a protocol called BEI (see the
-    protocol documentation further below).  The engine has a library of games
-    that it knows how to play, and its build system automatically generates the
-    source code for each of these games using `@unlsoft/boost-game`.
-
-*   The project `@unlsoft/boost-app` is a `create-react-app` PWA that provides
-    the application's game controller and UI.  Like `@unlsoft/boost-engine` it
-    relies on a game library generated by `@unlsoft/boost-game`, and it also
-    includes `@unlsoft/boost-engine` as a suite of web workers via symlink.
-
-# Generating `Game` Objects with `boost-game`
-
-When `@unlsoft/boost-game` is installed as a development dependency, it provides
-a command `generate-boost-game` that writes a JavaScript implementation of a
-Boost game to standard out.  The exact code produced depends on a number of
-command-line options, described below.  You can also run `generate-boost-game
---help` to see a list of all of the above options, their short descriptions, and
-their default values.
-
-## Command-Line Options Affecting Game Rules
-
-There are six main command-line options that can be passed to
-`generate-boost-game` to control what kind of game is generated:
-
-*   `--board-width [number]` sets the width of the game board in points.  If
-    omitted, the default width of nine points is used.
-
-*   `--board-height [number]` sets the height of the game board in points.  If
-    omitted, the default height of nine points is used.
-
-*   `--player-count [number]` sets the number of players (and therefore the
-    number of non-dragon piece colors).  If omitted, the default of two players
-    is used.
-
-*   `--starting-population-limit [number]` sets the maximum number of starting
-    pawns given to each player in a standard board setup.  (It is only a maximum
-    because the game code may give players fewer starting pieces when the
-    board's perimeter is too crowded.)  If omitted, the default limit of eight
-    pawns is used.
-
-*   `--tower-limit [number]` sets the maximum number of towers that each player
-    may build.  (This limit only affects when construction moves are available
-    to a player; it does not preclude other code from placing extra towers on
-    the board.)  If omitted, the default limit of two towers is used.
-
-*   `--demo` overrides the usual rules to allows players to move their pieces
-    even when they are defeated.  This can be useful both in testing and
-    tutorials for keeping the number of pieces on the board down.
-
-Based on these six options, the game will be assigned a unique string of the
-form `boost-[width]-[height]-[players]-[population]-[towers]` or of the form
-`boost-[width]-[height]-[players]-[population]-[towers]-demo`, which is called
-its **game identifier**.  For example, the game identifier for a standard Boost
-game is `boost-9-9-2-8-2`.
-
-## Command-Line Options Affecting the AI
-
-Other command-line options are also available to tweak the weights in the static
-evaluation function, which is the function used by `@unlsoft/boost-engine` to
-estimate how favorable a position is for each player.  The defaults values, used
-when these options are omitted, were chosen by human intuition and are given in
-decitempi (tenths of an extra turn).  However, these defaults have not been
-empirically validated yet.  The command-line options for static evaluation
-weights are as follows:
-
-*   `--ai-pawn [number]` sets the estimated value of a pawn.  The default value
-    is 200 decitempi (20 extra turns).
-
-*   `--ai-knight [number]` sets the estimated value of a knight when a player
-    has all of their towers.  The default value is 220 decitempi (a pawn plus
-    two extra turns).
-
-*   `--ai-endgame-knight [number]` sets the estimated value of a knight when a
-    player does not have all of their towers.  The game also computes an
-    appropriate fixed penalty for a player not having all of their towers so
-    that the AI is not incentivized to sacrifice its towers to make its knights
-    more valuable.  The default value is 350 decitempi (a pawn plus 15 extra
-    turns).
-
-*   `--ai-knight-activity [number]` sets the estimated value of moving a knight
-    one point closer to the closest non-friendly piece (i.e., a dragon or a
-    foe).  The default value is 5 decitempi (an extra half turn).
-
-*   `--ai-zero-activity-distance [number]` sets the distance, in points, that
-    must lie between a knight and the closest non-friendly piece for that knight
-    to have an activity score of zero.  As a knight moves closer to the
-    non-friendly piece, its activity score becomes positive (at the rate given
-    by `--ai-knight-activity`), and as it moves farther away, its activity score
-    turns negative (at the same rate).  The main use of this parameter is to
-    control the relative value of pawns and inactive knights; because an
-    inactive knight prevents a player from promoting a better-positioned pawn,
-    the knight can actually be a liability.  The default value is four points
-    (so a knight eight points from the nearest non-friendly piece is estimated
-    to be worth the same as a pawn).
-
-*   `--ai-construction-site [number]` sets the estimated value of a completed
-    construction site (a circle of four friendly pieces around an empty point).
-    Partial construction sites are awarded partial points: 1/16 value for a
-    construction site with one piece in place, 4/16 value for a construction
-    site with two pieces in place, and 9/16 value for a construction site with
-    three pieces in place.  The number of construction sites that the AI can
-    score for each side is limited by the number of additional towers that the
-    player can usefully build, though that limit may be adjusted by
-    `--ai-extra-opening-sites`.  Additionally, construction sites are
-    ineligible for scoring if they are not within four points of four friendly
-    mobile pieces.  The default value is 80 decitempi (8 turns).
-
-*   `--ai-extra-opening-sites [number]` sets the number of extra construction
-    site candidates the AI can consider for a side if that side is in an
-    opening-like position (i.e., has the starting population and no towers).  In
-    some cases, like in a standard game, a lie to the engine like
-    `--ai-extra-opening-sites 2` can help low-depth search set up better for a
-    second tower even though that the construction is beyond its horizon.  The
-    default value is 0 extra candidates (no deception).
-
-*   `--ai-tower [number]` sets the estimated value of a tower when a player has
-    at least four mobile pieces.  The default value is 125 decitempi (12.5 extra
-    turns).
-
-*   `--ai-endgame-tower [number]` sets the estimated value of a tower when a
-    player does not have at least four mobile pieces.  The game also computes an
-    appropriate fixed penalty for a player not having four mobile pieces so that
-    the AI is not incentivized to sacrifice its pawns and knights to make its
-    towers more valuable.  The default value is 700 decitempi (70 extra turns).
-
-*   `--ai-dragon-proximity [number]` sets the estimated value of moving a dragon
-    one point closer to the closest friendly tower when there are at least four
-    dragons on the board.  The default value is 10 decitempi (an extra turn).
-
-*   `--ai-circle-dragon [number]` sets the estimated additional value of moving
-    a dragon next to a friendly tower when there are at least four dragons on
-    the board.  The default value is 100 decitempi (ten extra turns).
-
-*   `--ai-crowd-member [number]` sets the estimated additional value of a piece
-    that has at least three other friendly pieces at most four points away.  The
-    default value is 1 decitempo (one tenth of an extra turn).
-
-*   `--ai-defeat [number]` sets the estimated value of defeating an opponent.
-    The default value is 1,000,000 decitempi (100,000 extra turns).
-
-## Typical Usage
-
-By convention, one usually redirects the output of `generate-boost-game` to
-JavaScript files in a `…/src/games` folder and then writes code like
-
-```
-import …_GAME from './games/….js';
-import …_GAME from './games/….js';
-
-const GAME_LIBRARY = new Map([
-  …_GAME,
-  …_GAME,
-].map((game) => [game.identifier, game]));
-export default GAME_LIBRARY;
-```
-
-in `…/src/gameLibrary.js` so that other code can import the game library and
-look up games by their identifiers.
-
-# Using `Game` Objects from `@unlsoft/boost-game`
-
-`Game` objects from a game library provide the following fields and methods:
-
-## Constants from Game Generation
-
-These constants are numbers set at code generation time, as described earlier:
-
-*   `game.identifier` is the identifier for the game, as described in the
-    previous section.
-
-*   `game.boardWidth` is the width, in points, of the board on which the game is
-    played.
-
-*   `game.boardHeight` is the height, in points, of the board on which the game is
-    played.
-
-*   `game.playerCount` is the number of players in the game (and therefore the
-    number of non-dragon piece colors needed to play the game).
-
-*   `game.populationLimit` is the maximum number of starting pawns given to each
-    player in a standard board setup.
-
-*   `game.towerLimit` is the maximum number of towers that each player may
-    build.  (This limit only affects when construction moves are available to a
-    player; it does not preclude other code from placing extra towers on the
-    board.)
-
-## Piece Types
-
-These constants represent the different piece types, which are returned by
-`position.getColorAndPieceType` and passed to `position.modified` as described
-later:
-
-*   `game.dragon` is a constant value used to represent a dragon.
-
-*   `game.pawn` is a constant value used to represent a pawn.
-
-*   `game.knight` is a constant value used to represent a knight.
-
-*   `game.tower` is a constant value used to represent a tower.
-
-## Algebraic Notation
-
-The following fields and helper methods are useful for encoding and decoding
-algebraic notation for files, ranks, points, and moves:
-
-*   `game.prettifyFile(file)` takes a file as a zero-based 𝑥 coordinate and
-    returns the corresponding letter in algebraic notation.
-
-*   `game.unprettifyFile(file)` takes a file as a letter in algebraic notation
-    and returns the corresponding zero-based 𝑥 coordinate.
-
-*   `game.prettifyRank(rank)` takes a rank as a zero-based 𝑦 coordinate and
-    returns the corresponding digits in algebraic notation.
-
-*   `game.unprettifyRank(rank)` takes a rank as a string of digits in algebraic
-    notation and returns the corresponding zero-based 𝑦 coordinate.
-
-*   `game.noPoint` is the constant string representing no point at all in
-    algebraic notation.
-
-*   `game.prettifyPoint(file, rank)` takes a point as zero-based 𝑥 and 𝑦
-    coordinates and returns the corresponding algebraic notation.  If either
-    coordinate is `undefined`, it returns `game.noPoint`.
-
-*   `game.unprettifyPoint(point)` takes a point in algebraic notation and
-    returns the corresponding zero-based 𝑥 and 𝑦 coordinates in an `Array`.  If
-    the point is `game.noPoint`, both coordinates will be `undefined`.
-
-*   `game.pass` is the constant string representing a pass in algebraic
-    notation.
-
-*   `game.joinPoints(fromPoint, toPoint)` takes two points in algebraic notation
-    and returns the algebraic notation for a move from `fromPoint` to `toPoint`.
-    For constructions and promotions, one point may be omitted or set to
-    `game.noPoint`, or the two points may be set equal to each other.  (For
-    consistency with `game.splitMove` and the main app's UI, the preferred
-    practice is to give the same point for both arguments.)  For passes, both
-    points should be omitted or set to `game.noPoint`.  (For consistency with
-    `game.splitMove`, the preferred practice is to pass `game.noPoint` for both
-    arguments.)
-
-*   `game.splitMove(move)` takes a move in algebraic notation and returns the
-    algebraic notation for its "from" and "to" points in an `Array`.  For
-    constructions and promotions, the "from" point will be the same as the "to"
-    point.  For passes, both points will be `game.noPoint`.
-
-*   `prettifyMove(fromFile, fromRank, toFile, toRank)` takes a move as
-    zero-based 𝑥 and 𝑦 coordinates for its "from" and "to" points and returns
-    the algebraic notation for the move.  Coordinates may be omitted or
-    `undefined` for special moves in the same places where `game.joinPoints`
-    would take `game.noPoint`.
-
-*   `unprettifyMove(move)` takes a move in algebraic notation and returns the
-    corresponding zero-based 𝑥 and 𝑦 coordinates for its "from" and "to" points.
-    Coordinates will be repeated for constructions and promotions, and will be
-    `undefined` for passes.
-
-## Positions
-
-The following fields and helper method are useful for obtaining `Position`
-objects corresponding to the game:
-
-*   `game.blankPosition` is a position containing no pieces.
-
-*   `game.startingPosition` is the game's standard starting position without any
-    dragons.
-
-*   `game.deserializePosition(serialization)` deserializes a position previously
-    encoded as a string with `position.serialization`, which is described later.
-
-# Using `Position` Objects from `@unlsoft/boost-game`
-
-`Position` objects corresponding to a game provide the following fields and
-methods:
-
-## Encodings
-
-*   `position.signature` is the position encoded as a single `BigInt`.  This
-    encoding is meant to be used as a reasonably fast hash-table key.  (The
-    position itself cannot be a hash-table key because ES6 does not support
-    hash-by-value natively.)
-
-*   `position.nextSignature` is equivalent to `position.nextTurn.signature` (see
-    the description of `position.nextTurn` in the "Moves" subsection further
-    below), but runs faster because it does not actually advance the turn.  It
-    is mostly useful for checking candidate moves for repetitions.
-
-*   `position.serialization` is the position serialized as a string.  A position
-    can later be deserialized with `game.deserializePosition` as described
-    earlier.
-
-## Pieces
-
-*   `position.getColorAndPieceType(x, y)` returns the color and piece type of
-    the piece at the zero-based coordinates `x` and `y` as a two-element
-    `Array`.  Colors are represented by the number of turns until the
-    corresponding player will play (e.g., `0` for the player whose turn it is
-    and `1` for the other player in a two-player game), and piece types are
-    represented with the constants `game.dragon`, `game.pawn`, `game.knight`,
-    and `game.tower` described earlier.  Dragon's colors are always `undefined`.
-    If there is no piece at the given coordinates, both the color and piece type
-    will be `undefined`.
-
-*   `position.modified(x, y, color, pieceType)` returns a new position
-    (`Position` objects are immutable) where the piece at the zero-based
-    coordinates `x` and `y` has been replaced with a piece of the given color
-    and piece type.  As above, the color should be given as the number of turns
-    until the corresponding player moves next (e.g., `0` for the player whose
-    turn it is and `1` for the other player in a two-player game), and piece
-    types should be given as one of the constants `game.dragon`, `game.pawn`,
-    `game.knight`, or `game.tower`, which are also described earlier.  As
-    special cases, `color` should be `undefined` if `pieceType` is
-    `game.dragon`, and a point can be cleared by passing `undefined` for both
-    `color` and `pieceType`.
-
-## Static Evaluation
-
-*   `position.getStaticEvaluation(color)` is the static evaluation of the
-    position from the perspective of the player who is to play in `color` turns
-    (hence, the possible values for `color` are the same as the values returned
-    by `position.getColorAndPieceType` or passed to `position.modified`).
-    Higher static evaluation scores are more favorable for that player.  The
-    score `Infinity` means that the player to move next has already won, while
-    the score `-Infinity` means that some other player has won.  The static
-    evaluation is finite in all other situations, though it may still be
-    extremely positive or negative (such as when the player has been defeated,
-    but other players are still actively competing).
-
-*   `position.live` is `true` if the game is still ongoing, `false` if any
-    player has won.
-
-## Moves
-
-*   `position.children` is the list of positions that the current player can
-    move to, not considering the repetition rule.  Note that it is the same
-    player's turn in the children positions as in the parent position; a turn is
-    not considered complete until the code uses `position.nextTurn`.
-
-*   `position.getMoveTo(child)` returns the algebraic notation for the move from
-    `position` to `child`.
-
-*   `position.getChildByMove(move)` returns the child reached by playing the
-    move given in algebraic notation.
-
-*   `position.nextTurn` is the same as `position`, except that in
-    `position.nextTurn` it is the next player's turn.  So, for example,
-    `position.getChildByMove('a1a3').nextTurn` would be the position after the
-    current player played the move `a1a3`.
-
-# Using `Controller` Objects from `@unlsoft/boost-engine`
-
-When `@unlsoft/boost-game` is installed as a development dependency, it provides
-two minified files, `…/node_modules/@unlsoft/boost-engine/dist/engine.js` and
-`…/node_modules/@unlsoft/boost-engine/dist/engineThread.js` that implement the
-engine's web workers.  These files should be included as-is in any app that
-wants to use the engine; for a `create-react-app` app, the easiest approach is
-to symlink them to the `public` folder.
-
-For talking to these web workers, the default export from
-`@unlsoft/boost-engine` is a `Controller` class, where `Controller` objects
-provide the following methods:
-
-*   `new Controller(engineURL, engineThreadURL, gameIdentifier, propertyHandler,
-    moveHandler)` creates a controller for an engine whose web workers source
-    code is located at the given URLs and that will play the game identified by
-    `gameIdentifier`.  Two callbacks must be provided:
-    
-    *   `propertyHandler` will be called with a property name and a value
-        whenever the engine sends a description of itself; see the documentation
-        for the `id` BEI message below.
-
-    *   `moveHandler` will be called with a move in algebraic notation whenever
-        the engine makes a move.
-
-*   `controller.setStrength(strength)` sets the engine's strength as close as
-    possible to `strength` on a scale where `0` is the strength of a theoretical
-    player that can never win, `1500` is the strength of the average experienced
-    human player, and `2500` is the strength of a grandmaster.
-
-*   `controller.setLine(position, taboo)` sends a position and an array of
-    preceding positions (which are taboo under the repetition rule) to the
-    engine.
-
-*   `go()` tells the engine to choose a move to play in the last sent position.
-    The engine's reply will be sent to the `moveHandler` callback once the
-    engine is done thinking.
-
-*   `stop(wantMove)` tells the engine stop thinking early.  If `wantMove` is
-    `true`, the controller will still call `moveHandler` with the best move the
-    engine found so far; otherwise that move will be discarded.
-
-Under the hood, the `Controller` constructor creates associated web workers, and
-its other methods communicate with these web workers using BEI, a protocol
-described in the next section.
-
-# The Boost Engine Interface (BEI) Protocol
-
-BEI is a text-based protocol for communication between a controller (a program
-that wants to incorporate a Boost-playing AI) and an engine (a Boost-playing
-AI).  It is based on and simplified from the Arimaa Engine Interface (AEI),
-which in turn is based on the Universal Chess Interface (UCI).
-
-BEI is built on asynchronous bidirectional communication of short strings called
-**messages** between the controller and engine.  For example, a controller might
-send messages as lines of text to an engine's standard input and read messages
-from the engine's standard output, or the controller and engine might exchange
-BEI messages as string payloads in web worker messages.
-(`@unlsoft/boost-engine` uses the latter approach.)
-
-Generally the engine should not process later messages until it has finished
-processing earlier ones.  The two exceptions are pondering (speculatively
-searching ahead during an opponent's turn) and thinking (deciding what move to
-make during the engine's turn), long-running operations that should effectively
-happen "in the background" and not prevent the engine from responding to other
-communication.
-
-## Controller-to-Engine Messages
-
-The following messages may be sent by a controller to an engine:
-
-*   `bei [value] [value] …` is sent to initiate communication and optionally to
-    provide engine-specific startup arguments.  (It must be possible to
-    configure an engine to take no startup arguments so that the engine can be
-    used with an implementation-agnostic controller, but an engine may still opt
-    to take arguments in other settings.)  The engine will reply with a
-    `protocol` message, possibly some `id` messages, and then `beiok`.  The
-    controller must not send any other communication until it has confirmed that
-    it is using a compatible protocol version and has received a `beiok`.
-
-*   `isready` is sent to ping the engine and ensure that the engine has finished
-    processing any previous messages.  The engine will reply with `readyok`.
-
-*   `newgame [identifier]` is sent to start a new game with the given game
-    identifier.  The engine will reply with `known` if it can play that game,
-    `unknown` if it cannot.
-
-*   `setoption [option] [value]` is sent to set the named engine option to the
-    given value.  Currently the only supported option name is `strength`, which
-    should be followed by a rating on a scale where `0` is the strength of a
-    theoretical player that can never win, `1500` is the strength of the average
-    experienced human player, and `2500` is the strength of a grandmaster.  If
-    the engine strength is never set, the engine should default to its strongest
-    setting; if an engine cannot play at the requested strength, it should set
-    its strength as close as possible.
-
-*   `setline [serialization] [tabooSerialization] [tabooSerialization] …` is
-    sent to tell the engine about a new board position, which is given by the
-    first serialization, and all of the previous board positions that affect the
-    repetition rule, which are give by the following serializations.  The
-    serialization format is the same format as used by `position.serialization`
-    from `@unlsoft/boost-game`.
-
-*   `ponder [turns]` is sent to tell the engine that it will be playing after
-    the given number of turns and that it may ponder in the background.  The
-    `newgame`, `setline`, `go`, and `stop` messages all stop pondering.
-
-*   `go` is sent to tell the engine that it should start thinking in the
-    background about what move to play in the current position.  When it is done
-    thinking, the engine will respond with `move` message reporting the best
-    move that it was able to find.  The `newgame`, `setline`, `ponder`, and
-    `stop` messages all stop thinking immediately and trigger a `move` message,
-    even if the engine was not done considering the position.
-
-*   `stop` is sent to tell the engine that it should stop any pondering or
-    thinking.  If the engine is thinking, this will prompt it to reply with a
-    `move` message.
-
-*   `quit` is sent to tell the engine that no further messages will be sent and
-    that it may exit.
-
-## Engine-to-Controller Messages
-
-The following messages may be sent by an engine to a controller:
-
-*   `protocol [version]` is sent as the first message in response to a `bei`
-    message to tell the controller what version of BEI the engine uses.  The
-    version described here is `1.0.0`.
-
-*   `id [property] [value]` is sent in response to a `bei` message to describe
-    the engine to the controller.  Each property should only be sent once.
-    Three property names are supported:
-
-    *   The value for the `name` property is the name of the engine.
-    
-    *   The value for the `author` property is the name of the engine's author
-        (or the names of the engine's authors if there are more than one).
-
-    *   The value for the `version` property is the version number of the
-        engine.
-
-*   `beiok` is sent to indicate the end of responses to a `bei` message.
-
-*   `readyok` is sent to reply to an `isready` message.
-
-*   `known` is sent to reply to a `newgame` message when the engine is able to
-    load the specified game from its game library.
-
-*   `unknown` is sent to reply to a `newgame` message when the engine is unable
-    to load the specified game from its game library.
-
-*   `move [move]` is sent to report the best move the engine was able to find
-    whenever the engine stops thinking.  The engine should not assume that this
-    move will actually be made; if the move is mode, it will receive an
-    appropriate `setline` message.
-
-*   `log [text]` is sent to report a log message from the engine.
-
-# Footrace Generator Design
-
-The footrace generator necessitated the use of a dynamic programming algorithm rather than a greedy algorithm. If a greedy were used, it would 
-value the most movement north but would place the piece in situations that require more moves to reach the end. 
-`findStartingSituationAndFastestMoveSequence(position)` is the main dynamic programming algorithm.
-
-The code uses `ONE_LANE_GAME` and `TWO_LANE_GAME` to generate the puzzles. The algorithm is only used for the `ONE_LANE_GAME` to generate a path. 
-The `ONE_LANE_GAME` is then mirrored to make the `TWO_LANE_GAME`.
-
-When the dynamic programming algorithm is run, the board contains pawns and towers.
-
-Each vertex of the DAG is a `Situation`. These vertices are added to the DAG using `selectBetterBackpointer()`.
-
-Each edge of the DAG is a move. The algorithm finds the move leading to a `Situation` in the `predecessor` index of `backpointersTable`.
\ No newline at end of file
+# Boost Board Game Application (Fall 2020 Version)
+
+A progressive web app (PWA) for learning and playing the board game Boost.
+
+Boost is a turn-based abstract strategy board game like checkers, chess,
+Xiangqi, or Arimaa.  It was designed to be new and interesting for humans to
+play while still admitting a simple AI and supporting various homework
+assignments on algorithms and data structures in the SOFT 260 course at UNL.
+
+# Quick Start
+
+Recursively clone this repository and `cd` into the root folder:
+
+```
+$ git clone --recursive git@git.unl.edu:soft-core/soft-260/boost-board-game.git
+$ cd boost-board-game
+```
+
+(If you forget `--recursive` when cloning, you can `cd` into your clone and run
+`git submodule update --init --recursive` instead.)
+
+Install dependencies for each of the projects in order:
+
+```
+$ (cd eslint-config; npm install)
+$ (cd boost-game; npm install)
+$ (cd boost-puzzles; npm install)
+$ (cd boost-engine; npm install)
+$ (cd boost-app; npm install)
+```
+
+If you get the error "'@unlsoft/eslint-config@1.0.0' is not in the npm registry"
+during the third, fourth, or fifth steps, you can work around that bug in npm by
+using these commands instead:
+
+```
+$ (cd boost-puzzles; mv package-lock.json backup.json; npm install; cp backup.json package-lock.json; npm install; mv backup.json package-lock.json)
+$ (cd boost-engine; mv package-lock.json backup.json; npm install; cp backup.json package-lock.json; npm install; mv backup.json package-lock.json)
+$ (cd boost-app; mv package-lock.json backup.json; npm install; cp backup.json package-lock.json; npm install; mv backup.json package-lock.json)
+```
+
+Generate the game and engine code:
+
+```
+$ (cd boost-puzzles; npm run generate)
+$ (cd boost-engine; npm run build)
+$ (cd boost-app; npm run generate)
+```
+
+Optionally run the test suites to make sure everything so far is okay:
+
+```
+$ (cd boost-game; npm run test:js -- --watchAll=false)
+$ (cd boost-engine; npm run test:js -- --watchAll=false)
+$ (cd boost-app; npm run test:js -- --watchAll=false)
+```
+
+And then serve the application locally:
+
+```
+$ (cd boost-app; npm start)
+```
+
+Once the app is running, click on the "Rules" button to read the rules of the
+game.
+
+# Development Workflow
+
+The project includes a VSCode workspace named `boost-board-game.code-workspace`.
+Most development tasks can be completed from within VSCode, though command-line
+development is also possible.  The subsections below describe how to perform
+common development tasks.
+
+## Code Generation
+
+As described later in the architecture overview, some projects depend on code
+generated by other projects.  Normally this code generation happens
+automatically when you lint, test, or otherwise run the app, but if you need to
+force an update (for example, if you change the game or engine code while the
+app is still running), you can use project's `generate` script.  From VSCode,
+run the project's `generate` script from the "NPM SCRIPTS" tray.  Or, on the
+command line:
+
+```
+$ (cd …; npm run generate)
+```
+
+## Linting
+
+VSCode should automatically detect each project's ESLint configuration and
+dependencies and run them on the fly as you edit the code, though you may have
+to tell VSCode to trust the ESLint instance the first time you edit a project.
+
+Alternatively, you can also manually lint.  From VSCode, run the project's
+`lint` script from the "NPM SCRIPTS" tray.  Or, on the command line:
+
+```
+$ (cd …; npm run lint)
+```
+
+## Unit Testing
+
+The unit test suites are set up to run in "watch" mode, which means that you can
+start the test script once, and it will rerun the test suite every time you save
+a code change.  From VSCode, run the project's `test` script from the "NPM
+SCRIPTS" tray.  Or, on the command line:
+
+```
+$ (cd …; npm run test)
+```
+
+If you want to run the tests only once and not watch for code changes, you can
+pass the `--watchAll=false` option to the underlying script:
+
+```
+$ (cd …; npm run test:js -- --watchAll=false)
+```
+
+Likewise, if you want to only run tests whose names match some text, you can
+pass the standard `-t` option:
+
+```
+$ (cd …; npm run test:js -- -t '[some test-name text]')
+```
+
+Options can be combined:
+
+```
+$ (cd …; npm run test:js -- --watchAll=false -t '[some test-name text]')
+```
+
+## System Testing
+
+The main app is set up to run in "watch" mode, which means that you can start it
+once, and it will refresh the page in your browser every time you save a code
+change.  From VSCode, run the project's `start` script from the "NPM SCRIPTS"
+tray.  Or, on the command line:
+
+```
+$ (cd boost-app; npm run start)
+```
+
+These automatic refreshes will *not* clear any persisted Redux state.  Because
+the app uses the local storage engine from `redux-persist` for persistence, you
+can clear your data during development by running `localStorage.clear()` in the
+developer console and then manually refreshing the page.
+
+## Deployment
+
+The main app runs entirely client-side, so it can be deployed as a `build`
+folder to be placed on any hosting platform.  From VSCode, run the project's
+`build` script from the "NPM SCRIPTS" tray.  Or, on the command line:
+
+```
+$ (cd boost-app; npm run build)
+```
+
+At the end of the command's output you should see a link to further deployment
+instructions.
+
+# Architecture Overview
+
+The code for the Boost board game application is organized as follows:
+
+*   The project `@unlsoft/eslint-config` contains the ESLint configuration for
+    the coding style used across the other projects.  Per `create-react-app`
+    convention, in a development build of the main app, a separate, weaker
+    coding style also warns at runtime about likely bugs.
+
+*   The project `@unlsoft/boost-game` is responsible for representing positions
+    as bit boards and for features that rely on bit-board operations to run
+    acceptably fast, primarily move generation and static evaluation.  Because
+    so much of the bit-board logic can be precomputed and unrolled,
+    `@unlsoft/boost-game` does not implement game logic itself, but is actually
+    a parameterized code generator that writes game logic into separate
+    JavaScript files.
+
+*   The project `@unlsoft/boost-engine` contains the game-playing engine, the
+    application's AI.  Like other abstract strategy board game engines, this
+    engine is designed to run in a separate thread or process from any UI, and
+    it communicates with a controller using a protocol called BEI (see the
+    protocol documentation further below).  The engine has a library of games
+    that it knows how to play, and its build system automatically generates the
+    source code for each of these games using `@unlsoft/boost-game`.
+
+*   The project `@unlsoft/boost-app` is a `create-react-app` PWA that provides
+    the application's game controller and UI.  Like `@unlsoft/boost-engine` it
+    relies on a game library generated by `@unlsoft/boost-game`, and it also
+    includes `@unlsoft/boost-engine` as a suite of web workers via symlink.
+
+# Generating `Game` Objects with `boost-game`
+
+When `@unlsoft/boost-game` is installed as a development dependency, it provides
+a command `generate-boost-game` that writes a JavaScript implementation of a
+Boost game to standard out.  The exact code produced depends on a number of
+command-line options, described below.  You can also run `generate-boost-game
+--help` to see a list of all of the above options, their short descriptions, and
+their default values.
+
+## Command-Line Options Affecting Game Rules
+
+There are six main command-line options that can be passed to
+`generate-boost-game` to control what kind of game is generated:
+
+*   `--board-width [number]` sets the width of the game board in points.  If
+    omitted, the default width of nine points is used.
+
+*   `--board-height [number]` sets the height of the game board in points.  If
+    omitted, the default height of nine points is used.
+
+*   `--player-count [number]` sets the number of players (and therefore the
+    number of non-dragon piece colors).  If omitted, the default of two players
+    is used.
+
+*   `--starting-population-limit [number]` sets the maximum number of starting
+    pawns given to each player in a standard board setup.  (It is only a maximum
+    because the game code may give players fewer starting pieces when the
+    board's perimeter is too crowded.)  If omitted, the default limit of eight
+    pawns is used.
+
+*   `--tower-limit [number]` sets the maximum number of towers that each player
+    may build.  (This limit only affects when construction moves are available
+    to a player; it does not preclude other code from placing extra towers on
+    the board.)  If omitted, the default limit of two towers is used.
+
+*   `--demo` overrides the usual rules to allows players to move their pieces
+    even when they are defeated.  This can be useful both in testing and
+    tutorials for keeping the number of pieces on the board down.
+
+Based on these six options, the game will be assigned a unique string of the
+form `boost-[width]-[height]-[players]-[population]-[towers]` or of the form
+`boost-[width]-[height]-[players]-[population]-[towers]-demo`, which is called
+its **game identifier**.  For example, the game identifier for a standard Boost
+game is `boost-9-9-2-8-2`.
+
+## Command-Line Options Affecting the AI
+
+Other command-line options are also available to tweak the weights in the static
+evaluation function, which is the function used by `@unlsoft/boost-engine` to
+estimate how favorable a position is for each player.  The defaults values, used
+when these options are omitted, were chosen by human intuition and are given in
+decitempi (tenths of an extra turn).  However, these defaults have not been
+empirically validated yet.  The command-line options for static evaluation
+weights are as follows:
+
+*   `--ai-pawn [number]` sets the estimated value of a pawn.  The default value
+    is 200 decitempi (20 extra turns).
+
+*   `--ai-knight [number]` sets the estimated value of a knight when a player
+    has all of their towers.  The default value is 220 decitempi (a pawn plus
+    two extra turns).
+
+*   `--ai-endgame-knight [number]` sets the estimated value of a knight when a
+    player does not have all of their towers.  The game also computes an
+    appropriate fixed penalty for a player not having all of their towers so
+    that the AI is not incentivized to sacrifice its towers to make its knights
+    more valuable.  The default value is 350 decitempi (a pawn plus 15 extra
+    turns).
+
+*   `--ai-knight-activity [number]` sets the estimated value of moving a knight
+    one point closer to the closest non-friendly piece (i.e., a dragon or a
+    foe).  The default value is 5 decitempi (an extra half turn).
+
+*   `--ai-zero-activity-distance [number]` sets the distance, in points, that
+    must lie between a knight and the closest non-friendly piece for that knight
+    to have an activity score of zero.  As a knight moves closer to the
+    non-friendly piece, its activity score becomes positive (at the rate given
+    by `--ai-knight-activity`), and as it moves farther away, its activity score
+    turns negative (at the same rate).  The main use of this parameter is to
+    control the relative value of pawns and inactive knights; because an
+    inactive knight prevents a player from promoting a better-positioned pawn,
+    the knight can actually be a liability.  The default value is four points
+    (so a knight eight points from the nearest non-friendly piece is estimated
+    to be worth the same as a pawn).
+
+*   `--ai-construction-site [number]` sets the estimated value of a completed
+    construction site (a circle of four friendly pieces around an empty point).
+    Partial construction sites are awarded partial points: 1/16 value for a
+    construction site with one piece in place, 4/16 value for a construction
+    site with two pieces in place, and 9/16 value for a construction site with
+    three pieces in place.  The number of construction sites that the AI can
+    score for each side is limited by the number of additional towers that the
+    player can usefully build, though that limit may be adjusted by
+    `--ai-extra-opening-sites`.  Additionally, construction sites are
+    ineligible for scoring if they are not within four points of four friendly
+    mobile pieces.  The default value is 80 decitempi (8 turns).
+
+*   `--ai-extra-opening-sites [number]` sets the number of extra construction
+    site candidates the AI can consider for a side if that side is in an
+    opening-like position (i.e., has the starting population and no towers).  In
+    some cases, like in a standard game, a lie to the engine like
+    `--ai-extra-opening-sites 2` can help low-depth search set up better for a
+    second tower even though that the construction is beyond its horizon.  The
+    default value is 0 extra candidates (no deception).
+
+*   `--ai-tower [number]` sets the estimated value of a tower when a player has
+    at least four mobile pieces.  The default value is 125 decitempi (12.5 extra
+    turns).
+
+*   `--ai-endgame-tower [number]` sets the estimated value of a tower when a
+    player does not have at least four mobile pieces.  The game also computes an
+    appropriate fixed penalty for a player not having four mobile pieces so that
+    the AI is not incentivized to sacrifice its pawns and knights to make its
+    towers more valuable.  The default value is 700 decitempi (70 extra turns).
+
+*   `--ai-dragon-proximity [number]` sets the estimated value of moving a dragon
+    one point closer to the closest friendly tower when there are at least four
+    dragons on the board.  The default value is 10 decitempi (an extra turn).
+
+*   `--ai-circle-dragon [number]` sets the estimated additional value of moving
+    a dragon next to a friendly tower when there are at least four dragons on
+    the board.  The default value is 100 decitempi (ten extra turns).
+
+*   `--ai-crowd-member [number]` sets the estimated additional value of a piece
+    that has at least three other friendly pieces at most four points away.  The
+    default value is 1 decitempo (one tenth of an extra turn).
+
+*   `--ai-defeat [number]` sets the estimated value of defeating an opponent.
+    The default value is 1,000,000 decitempi (100,000 extra turns).
+
+## Typical Usage
+
+By convention, one usually redirects the output of `generate-boost-game` to
+JavaScript files in a `…/src/games` folder and then writes code like
+
+```
+import …_GAME from './games/….js';
+import …_GAME from './games/….js';
+
+const GAME_LIBRARY = new Map([
+  …_GAME,
+  …_GAME,
+].map((game) => [game.identifier, game]));
+export default GAME_LIBRARY;
+```
+
+in `…/src/gameLibrary.js` so that other code can import the game library and
+look up games by their identifiers.
+
+# Using `Game` Objects from `@unlsoft/boost-game`
+
+`Game` objects from a game library provide the following fields and methods:
+
+## Constants from Game Generation
+
+These constants are numbers set at code generation time, as described earlier:
+
+*   `game.identifier` is the identifier for the game, as described in the
+    previous section.
+
+*   `game.boardWidth` is the width, in points, of the board on which the game is
+    played.
+
+*   `game.boardHeight` is the height, in points, of the board on which the game is
+    played.
+
+*   `game.playerCount` is the number of players in the game (and therefore the
+    number of non-dragon piece colors needed to play the game).
+
+*   `game.populationLimit` is the maximum number of starting pawns given to each
+    player in a standard board setup.
+
+*   `game.towerLimit` is the maximum number of towers that each player may
+    build.  (This limit only affects when construction moves are available to a
+    player; it does not preclude other code from placing extra towers on the
+    board.)
+
+## Piece Types
+
+These constants represent the different piece types, which are returned by
+`position.getColorAndPieceType` and passed to `position.modified` as described
+later:
+
+*   `game.dragon` is a constant value used to represent a dragon.
+
+*   `game.pawn` is a constant value used to represent a pawn.
+
+*   `game.knight` is a constant value used to represent a knight.
+
+*   `game.tower` is a constant value used to represent a tower.
+
+## Algebraic Notation
+
+The following fields and helper methods are useful for encoding and decoding
+algebraic notation for files, ranks, points, and moves:
+
+*   `game.prettifyFile(file)` takes a file as a zero-based 𝑥 coordinate and
+    returns the corresponding letter in algebraic notation.
+
+*   `game.unprettifyFile(file)` takes a file as a letter in algebraic notation
+    and returns the corresponding zero-based 𝑥 coordinate.
+
+*   `game.prettifyRank(rank)` takes a rank as a zero-based 𝑦 coordinate and
+    returns the corresponding digits in algebraic notation.
+
+*   `game.unprettifyRank(rank)` takes a rank as a string of digits in algebraic
+    notation and returns the corresponding zero-based 𝑦 coordinate.
+
+*   `game.noPoint` is the constant string representing no point at all in
+    algebraic notation.
+
+*   `game.prettifyPoint(file, rank)` takes a point as zero-based 𝑥 and 𝑦
+    coordinates and returns the corresponding algebraic notation.  If either
+    coordinate is `undefined`, it returns `game.noPoint`.
+
+*   `game.unprettifyPoint(point)` takes a point in algebraic notation and
+    returns the corresponding zero-based 𝑥 and 𝑦 coordinates in an `Array`.  If
+    the point is `game.noPoint`, both coordinates will be `undefined`.
+
+*   `game.pass` is the constant string representing a pass in algebraic
+    notation.
+
+*   `game.joinPoints(fromPoint, toPoint)` takes two points in algebraic notation
+    and returns the algebraic notation for a move from `fromPoint` to `toPoint`.
+    For constructions and promotions, one point may be omitted or set to
+    `game.noPoint`, or the two points may be set equal to each other.  (For
+    consistency with `game.splitMove` and the main app's UI, the preferred
+    practice is to give the same point for both arguments.)  For passes, both
+    points should be omitted or set to `game.noPoint`.  (For consistency with
+    `game.splitMove`, the preferred practice is to pass `game.noPoint` for both
+    arguments.)
+
+*   `game.splitMove(move)` takes a move in algebraic notation and returns the
+    algebraic notation for its "from" and "to" points in an `Array`.  For
+    constructions and promotions, the "from" point will be the same as the "to"
+    point.  For passes, both points will be `game.noPoint`.
+
+*   `prettifyMove(fromFile, fromRank, toFile, toRank)` takes a move as
+    zero-based 𝑥 and 𝑦 coordinates for its "from" and "to" points and returns
+    the algebraic notation for the move.  Coordinates may be omitted or
+    `undefined` for special moves in the same places where `game.joinPoints`
+    would take `game.noPoint`.
+
+*   `unprettifyMove(move)` takes a move in algebraic notation and returns the
+    corresponding zero-based 𝑥 and 𝑦 coordinates for its "from" and "to" points.
+    Coordinates will be repeated for constructions and promotions, and will be
+    `undefined` for passes.
+
+## Positions
+
+The following fields and helper method are useful for obtaining `Position`
+objects corresponding to the game:
+
+*   `game.blankPosition` is a position containing no pieces.
+
+*   `game.startingPosition` is the game's standard starting position without any
+    dragons.
+
+*   `game.deserializePosition(serialization)` deserializes a position previously
+    encoded as a string with `position.serialization`, which is described later.
+
+# Using `Position` Objects from `@unlsoft/boost-game`
+
+`Position` objects corresponding to a game provide the following fields and
+methods:
+
+## Encodings
+
+*   `position.signature` is the position encoded as a single `BigInt`.  This
+    encoding is meant to be used as a reasonably fast hash-table key.  (The
+    position itself cannot be a hash-table key because ES6 does not support
+    hash-by-value natively.)
+
+*   `position.nextSignature` is equivalent to `position.nextTurn.signature` (see
+    the description of `position.nextTurn` in the "Moves" subsection further
+    below), but runs faster because it does not actually advance the turn.  It
+    is mostly useful for checking candidate moves for repetitions.
+
+*   `position.serialization` is the position serialized as a string.  A position
+    can later be deserialized with `game.deserializePosition` as described
+    earlier.
+
+## Pieces
+
+*   `position.getColorAndPieceType(x, y)` returns the color and piece type of
+    the piece at the zero-based coordinates `x` and `y` as a two-element
+    `Array`.  Colors are represented by the number of turns until the
+    corresponding player will play (e.g., `0` for the player whose turn it is
+    and `1` for the other player in a two-player game), and piece types are
+    represented with the constants `game.dragon`, `game.pawn`, `game.knight`,
+    and `game.tower` described earlier.  Dragon's colors are always `undefined`.
+    If there is no piece at the given coordinates, both the color and piece type
+    will be `undefined`.
+
+*   `position.modified(x, y, color, pieceType)` returns a new position
+    (`Position` objects are immutable) where the piece at the zero-based
+    coordinates `x` and `y` has been replaced with a piece of the given color
+    and piece type.  As above, the color should be given as the number of turns
+    until the corresponding player moves next (e.g., `0` for the player whose
+    turn it is and `1` for the other player in a two-player game), and piece
+    types should be given as one of the constants `game.dragon`, `game.pawn`,
+    `game.knight`, or `game.tower`, which are also described earlier.  As
+    special cases, `color` should be `undefined` if `pieceType` is
+    `game.dragon`, and a point can be cleared by passing `undefined` for both
+    `color` and `pieceType`.
+
+## Static Evaluation
+
+*   `position.getStaticEvaluation(color)` is the static evaluation of the
+    position from the perspective of the player who is to play in `color` turns
+    (hence, the possible values for `color` are the same as the values returned
+    by `position.getColorAndPieceType` or passed to `position.modified`).
+    Higher static evaluation scores are more favorable for that player.  The
+    score `Infinity` means that the player to move next has already won, while
+    the score `-Infinity` means that some other player has won.  The static
+    evaluation is finite in all other situations, though it may still be
+    extremely positive or negative (such as when the player has been defeated,
+    but other players are still actively competing).
+
+*   `position.live` is `true` if the game is still ongoing, `false` if any
+    player has won.
+
+## Moves
+
+*   `position.children` is the list of positions that the current player can
+    move to, not considering the repetition rule.  Note that it is the same
+    player's turn in the children positions as in the parent position; a turn is
+    not considered complete until the code uses `position.nextTurn`.
+
+*   `position.getMoveTo(child)` returns the algebraic notation for the move from
+    `position` to `child`.
+
+*   `position.getChildByMove(move)` returns the child reached by playing the
+    move given in algebraic notation.
+
+*   `position.nextTurn` is the same as `position`, except that in
+    `position.nextTurn` it is the next player's turn.  So, for example,
+    `position.getChildByMove('a1a3').nextTurn` would be the position after the
+    current player played the move `a1a3`.
+
+# Using `Controller` Objects from `@unlsoft/boost-engine`
+
+When `@unlsoft/boost-game` is installed as a development dependency, it provides
+two minified files, `…/node_modules/@unlsoft/boost-engine/dist/engine.js` and
+`…/node_modules/@unlsoft/boost-engine/dist/engineThread.js` that implement the
+engine's web workers.  These files should be included as-is in any app that
+wants to use the engine; for a `create-react-app` app, the easiest approach is
+to symlink them to the `public` folder.
+
+For talking to these web workers, the default export from
+`@unlsoft/boost-engine` is a `Controller` class, where `Controller` objects
+provide the following methods:
+
+*   `new Controller(engineURL, engineThreadURL, gameIdentifier, propertyHandler,
+    moveHandler)` creates a controller for an engine whose web workers source
+    code is located at the given URLs and that will play the game identified by
+    `gameIdentifier`.  Two callbacks must be provided:
+    
+    *   `propertyHandler` will be called with a property name and a value
+        whenever the engine sends a description of itself; see the documentation
+        for the `id` BEI message below.
+
+    *   `moveHandler` will be called with a move in algebraic notation whenever
+        the engine makes a move.
+
+*   `controller.setStrength(strength)` sets the engine's strength as close as
+    possible to `strength` on a scale where `0` is the strength of a theoretical
+    player that can never win, `1500` is the strength of the average experienced
+    human player, and `2500` is the strength of a grandmaster.
+
+*   `controller.setLine(position, taboo)` sends a position and an array of
+    preceding positions (which are taboo under the repetition rule) to the
+    engine.
+
+*   `go()` tells the engine to choose a move to play in the last sent position.
+    The engine's reply will be sent to the `moveHandler` callback once the
+    engine is done thinking.
+
+*   `stop(wantMove)` tells the engine stop thinking early.  If `wantMove` is
+    `true`, the controller will still call `moveHandler` with the best move the
+    engine found so far; otherwise that move will be discarded.
+
+Under the hood, the `Controller` constructor creates associated web workers, and
+its other methods communicate with these web workers using BEI, a protocol
+described in the next section.
+
+# The Boost Engine Interface (BEI) Protocol
+
+BEI is a text-based protocol for communication between a controller (a program
+that wants to incorporate a Boost-playing AI) and an engine (a Boost-playing
+AI).  It is based on and simplified from the Arimaa Engine Interface (AEI),
+which in turn is based on the Universal Chess Interface (UCI).
+
+BEI is built on asynchronous bidirectional communication of short strings called
+**messages** between the controller and engine.  For example, a controller might
+send messages as lines of text to an engine's standard input and read messages
+from the engine's standard output, or the controller and engine might exchange
+BEI messages as string payloads in web worker messages.
+(`@unlsoft/boost-engine` uses the latter approach.)
+
+Generally the engine should not process later messages until it has finished
+processing earlier ones.  The two exceptions are pondering (speculatively
+searching ahead during an opponent's turn) and thinking (deciding what move to
+make during the engine's turn), long-running operations that should effectively
+happen "in the background" and not prevent the engine from responding to other
+communication.
+
+## Controller-to-Engine Messages
+
+The following messages may be sent by a controller to an engine:
+
+*   `bei [value] [value] …` is sent to initiate communication and optionally to
+    provide engine-specific startup arguments.  (It must be possible to
+    configure an engine to take no startup arguments so that the engine can be
+    used with an implementation-agnostic controller, but an engine may still opt
+    to take arguments in other settings.)  The engine will reply with a
+    `protocol` message, possibly some `id` messages, and then `beiok`.  The
+    controller must not send any other communication until it has confirmed that
+    it is using a compatible protocol version and has received a `beiok`.
+
+*   `isready` is sent to ping the engine and ensure that the engine has finished
+    processing any previous messages.  The engine will reply with `readyok`.
+
+*   `newgame [identifier]` is sent to start a new game with the given game
+    identifier.  The engine will reply with `known` if it can play that game,
+    `unknown` if it cannot.
+
+*   `setoption [option] [value]` is sent to set the named engine option to the
+    given value.  Currently the only supported option name is `strength`, which
+    should be followed by a rating on a scale where `0` is the strength of a
+    theoretical player that can never win, `1500` is the strength of the average
+    experienced human player, and `2500` is the strength of a grandmaster.  If
+    the engine strength is never set, the engine should default to its strongest
+    setting; if an engine cannot play at the requested strength, it should set
+    its strength as close as possible.
+
+*   `setline [serialization] [tabooSerialization] [tabooSerialization] …` is
+    sent to tell the engine about a new board position, which is given by the
+    first serialization, and all of the previous board positions that affect the
+    repetition rule, which are give by the following serializations.  The
+    serialization format is the same format as used by `position.serialization`
+    from `@unlsoft/boost-game`.
+
+*   `ponder [turns]` is sent to tell the engine that it will be playing after
+    the given number of turns and that it may ponder in the background.  The
+    `newgame`, `setline`, `go`, and `stop` messages all stop pondering.
+
+*   `go` is sent to tell the engine that it should start thinking in the
+    background about what move to play in the current position.  When it is done
+    thinking, the engine will respond with `move` message reporting the best
+    move that it was able to find.  The `newgame`, `setline`, `ponder`, and
+    `stop` messages all stop thinking immediately and trigger a `move` message,
+    even if the engine was not done considering the position.
+
+*   `stop` is sent to tell the engine that it should stop any pondering or
+    thinking.  If the engine is thinking, this will prompt it to reply with a
+    `move` message.
+
+*   `quit` is sent to tell the engine that no further messages will be sent and
+    that it may exit.
+
+## Engine-to-Controller Messages
+
+The following messages may be sent by an engine to a controller:
+
+*   `protocol [version]` is sent as the first message in response to a `bei`
+    message to tell the controller what version of BEI the engine uses.  The
+    version described here is `1.0.0`.
+
+*   `id [property] [value]` is sent in response to a `bei` message to describe
+    the engine to the controller.  Each property should only be sent once.
+    Three property names are supported:
+
+    *   The value for the `name` property is the name of the engine.
+    
+    *   The value for the `author` property is the name of the engine's author
+        (or the names of the engine's authors if there are more than one).
+
+    *   The value for the `version` property is the version number of the
+        engine.
+
+*   `beiok` is sent to indicate the end of responses to a `bei` message.
+
+*   `readyok` is sent to reply to an `isready` message.
+
+*   `known` is sent to reply to a `newgame` message when the engine is able to
+    load the specified game from its game library.
+
+*   `unknown` is sent to reply to a `newgame` message when the engine is unable
+    to load the specified game from its game library.
+
+*   `move [move]` is sent to report the best move the engine was able to find
+    whenever the engine stops thinking.  The engine should not assume that this
+    move will actually be made; if the move is mode, it will receive an
+    appropriate `setline` message.
+
+*   `log [text]` is sent to report a log message from the engine.
+
+# Footrace Generator Design
+
+The design for the footrace generator is as follows:
+
+*   The footrace generator necessitated the use of a dynamic programming algorithm
+    rather than a greedy algorithm. If a greedy were used, it would value the most
+    movement north but would place the piece in situations that require more moves
+    to reach the end.
+
+*   `findStartingSituationAndFastestMoveSequence(position)` is the main dynamic
+    programming algorithm.
+
+*   The code uses `ONE_LANE_GAME` and `TWO_LANE_GAME` to generate the puzzles.
+    The algorithm is only used for the `ONE_LANE_GAME` to generate a path. 
+    The `ONE_LANE_GAME` is then mirrored to make the `TWO_LANE_GAME`.
+
+*   When the dynamic programming algorithm is run, the board contains pawns and towers.
+
+*   Each vertex of the DAG is a `Situation`. These vertices are added to the DAG using
+    `selectBetterBackpointer()`.
+
+*   Each edge of the DAG is a move. The algorithm finds the move leading to a `Situation`
+    in the `predecessor` index of `backpointersTable`.
+
+*   The topological order is determined by comparing the y values of two `Situation` objects.
+    If there is a tie, the x values are compared.
\ No newline at end of file