nicolodavis.com

boardgame.io - Phases and Stages

Oct 1, 2019

A new release (v0.33) of boardgame.io is finally out! The significant addition in this release is the ability to subdivide turns into Stages. What’s more, different players can be in different stages during a turn. Each stage can define its own set of moves.

In previous releases we only had the concept of Phases. This has served us well, but it was also a frequent source of confusion:

Are turns inside phases or are phases inside turns?

The answer:

Both!

didn’t particularly alleviate the confusion. It was true that a phase was a completely orthogonal concept to a turn, and the game could switch between phases at any time, but this frequently caused awkward interactions with turn orders and other turn related things.

This prompted the creation of a new concept (Stages) to represent phases inside turns and the old concept (Phases) has now been relegated to being a game phase inside which turns are played.

Along the way many other API details were refined, leading to a near rewrite of everything. The new API is more streamlined and intuitive in many ways.

Migration Guide

The Game() constructor is gone.

import { Game } from 'boardgame.io/core';

const game = Game({
  moves: { ... },
});

This was never really necessary and has been trimmed away. Now you can just do:

const game = {
  moves: { ... },
};

The flow section is gone.

{
  moves: { ... },

  flow: {
    phases: { ... },
  },
}

You can now just define things like phases in the top-level:

{
  moves: { ... },
  phases: { ... },
}

You can define moves inside phases!

The previous mechanism of restricting moves in a phase involved specifying allowedMoves.

{
  moves: { moveA, moveB, moveC },

  flow: {
    phases: {
      A: {
        allowedMoves: ['moveA'],
      }
    }
  }
}

moveA / moveB / moveC are functions whose definitions are not shown here

Now you can just specify the moves that will be used in a phase directly in the phase’s definition:

{
  moves: { moveB, moveC },

  phases: {
    A: {
      moves: { moveA },
    }
  }
}

If a phase does not contain a moves then the global moves section will be used.

onBegin / onEnd / endIf

The following hooks and triggers have been renamed:

  • onPhaseBegin —> onBegin
  • onTurnBegin —> onBegin
  • onPhaseEnd —> onEnd
  • onTurnEnd —> onEnd
  • endPhaseIf —> endIf
  • endTurnIf —> endIf
  • endGameIf —> endIf

Wait, how come some of them are now the same?

That’s because each one goes into a different section in the game object, so there is no longer any need to differentiate them by using a different name.

{
  endIf:  // endGameIf

  phases: {
    onBegin:
    onEnd:
    endIf:
  },

  turn: {
    onBegin:
    onEnd:
    endIf:
  },
}

A new turn section.

You saw this in the previous example already. This section contains everything that is turn related (including the Turn Order):

{
  turn: {
    order: TurnOrder.DEFAULT,  // previously turnOrder
    moveLimit: 1,  // previously movesPerTurn
  }
}

Phase semantics.

Phases are now game phases inside which turns are played. When a phase ends, it first ends the turn. There are also no “default” phase anymore. The game is either in a phase or not (in which case ctx.phase will contain null).

startingPhase is also now deprecated in favor of a start option inside the phase that you want the game to begin in (if you want the game to begin in a phase; this is optional):

{
  phases: {
    A: { start: true },
    B: {},
    C: {},
  },
}

Disabling events.

First, all players can call events (unlike the previous releases where only the currentPlayer could). Also, all events are enabled by default.

To disable an event from being called from the client, use the events section:

{
  events: {
    endTurn: false,
  }
}

setActionPlayers is gone

It is replaced by setActivePlayers. See here for more details.

The turn begins at 1 (and not 0).

This probably doesn’t matter too much, but just so you know.


Nicolo John Davis
Nicolo Davis