Skip to content

Commit

Permalink
change phases syntax
Browse files Browse the repository at this point in the history
This is in preparation for an upcoming feature to quickly switch
to a temporary phase (and back after it is done).

OLD:
phases: [
  { name: 'A', ... }
  { name: 'B', ... }
]

NEW:
phases: {
  A: { ... },
  B: { ... },
}

This allows creation of different phase orderings that aren't
simple lists. Each phase can now point to a `next` phase:

phases: {
  A: { next: 'B' },
}

A 'default' phase is the catch-all next phase for phases that don't
specify `next`.

In order to emulate the old behavior, you can do something like:

phases: {
  A: { next: 'B' },
  B: { next: 'C' },
  C: { next: 'A' },
}
  • Loading branch information
nicolodavis committed Nov 9, 2018
1 parent bffc19e commit 0568857
Show file tree
Hide file tree
Showing 20 changed files with 449 additions and 348 deletions.
34 changes: 34 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,37 @@
#### Breaking Changes

1. The syntax for phases has changed:

```
// old
phases: [
{ name: 'A', ...opts },
{ name: 'B', ...opts },
]
// new
phases: {
'A': { ...opts },
'B': { ...opts },
}
```

2. There is no implicit ordering of phases. You can specify an
explicit order via `next`:

```
// new
phases: {
'A': { next: 'B' },
'B': { next: 'A' },
}
```

3. A phase called `default` is always created. This is the phase
that the game begins with. This is also the phase that the
game reverts to in case it detects an infinite loop of
`endPhase` events caused by a cycle.

## v0.26.3

#### Features
Expand Down
12 changes: 5 additions & 7 deletions docs/api/Game.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ game state and the moves. The moves are converted to a
of moves have been made.
* `flow.undoableMoves` (_array_): Enables undo and redo of listed moves.
Leave `undefined` if all moves should be undoable.
* `flow.phases` (_array_): Optional list of game phases. See
* `flow.phases` (_object_): Optional spec of game phases. See
[Phases](/phases) for more information.

### Returns
Expand Down Expand Up @@ -110,9 +110,8 @@ const game = Game({
},

flow: {
phases: [
{
name: 'A',
phases: {
A: {
endGameIf: ...
endTurnIf: ...
onTurnBegin: ...
Expand All @@ -122,8 +121,7 @@ const game = Game({
allowedMoves: ...
...
},
{
name: 'B',
B: {
endGameIf: ...
endTurnIf: ...
onTurnBegin: ...
Expand All @@ -133,7 +131,7 @@ const game = Game({
allowedMoves: ...
...
},
]
}
}
});
```
82 changes: 54 additions & 28 deletions docs/phases.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,14 @@ We'll ignore the rendering component of this game, but this is how it might look

Now let's say we want the game to work in two phases:

* the first phase where the player can only draw cards (until the deck is empty).
* a first phase where the player can only draw cards (until the deck is empty).
* a second phase where the player can only play cards (until their hand is empty).

In order to do this, we add a `flow` section to the `Game`
constructor (you should already be familiar with this as the location
In order to do this, we add a `flow` section to the game
spec (you should already be familiar with this as the location
where you placed `endGameIf` in the
[tutorial](#/tutorial?id=add-victory-condition)).

It can also contain a `phases` array, which defines different
phases in the game. Each phase can specify a list of `allowedMoves`,
[tutorial](#/tutorial?id=add-victory-condition)). It can also contain a `phases` object, which defines different phases in the game. Each phase can specify a list of `allowedMoves`,
which allows only those moves to be played during that phase.
They can also contain a `endPhaseIf` function that terminates
the phase automatically if a particular condition is met.

```js
const game = Game({
Expand All @@ -66,24 +61,58 @@ const game = Game({
},

flow: {
phases: [
{
name: 'draw phase',
startingPhase: 'draw',

phases: {
draw: {
allowedMoves: ['drawCard'],
endPhaseIf: G => G.deck <= 0,
next: 'play',
},
{
name: 'play phase',

play: {
allowedMoves: ['playCard'],
endPhaseIf: G => G.hand <= 0,
next: 'draw',
},
],
},
},
});
```

!> Phases can also be terminated manually by calling `props.events.endPhase()` from the
board React component (in response to a user action like clicking a button, for example).
!> Every game also has an implicit `default` phase, which is just
a phase with no specific options, so the example above actually
has three phases, even though we never use the `default` phase.

### Terminating a phase

A phase ends when one of the following happens:

###### 1. `endPhaseIf` triggers:

This is a simple boolean function that terminates the phase when
it returns `true` (see the example above).

###### 2. The `endPhase` event is dispatched:

This can happen either in the game logic or from the client
directly. See the [Events API](events.md) for more details
on how to dispatch events.

###### What happens when a phase terminates?

The game moves on to the "next" phase. This phase is determined by the
following in increasing order of precedence (i.e. if [2] and [4] are both
relevant, the result of [4] is used):

1. The `default` phase is chosen as the next phase if no other option is present.

2. If a phase specifies the `next` option (like our example above does), then that is
chosen as the next phase.

3. `endPhaseIf` can return the name of the next phase.

4. The `endPhase` event accepts the name of the next phase as an argument.

Watch our game in action now with phases. Notice that you can only draw cards in the first
phase, and you can only play cards in the second phase.
Expand All @@ -101,13 +130,12 @@ end of a phase. These are specified just like normal moves in `onPhaseBegin` and
`onPhaseEnd`.

```js
phases: [
{
name: '...',
phases: {
phaseA: {
onPhaseBegin: (G, ctx) => G,
onPhaseEnd: (G, ctx) => G,
},
];
};
```

#### Triggers / Hooks
Expand All @@ -116,7 +144,7 @@ The `flow` section can specify a number of automatic behaviors when a move is ma
or when the turn or phase is ended. These can also be overridden at the phase level.
Let's take a look at some of these:

!> For a more complete set of options, take a look
!> For an authoritative set of options, take a look
[here](https://github.com/nicolodavis/boardgame.io/blob/master/src/core/flow.js#L139).

```js
Expand All @@ -136,10 +164,8 @@ flow: {
// Run at the end of a move.
onMove: (G, ctx) => G

phases: [
{
name: 'A',

phases: {
A: {
// Ends the phase if this returns a truthy value.
endPhaseIf: (G, ctx) => {}

Expand All @@ -157,12 +183,12 @@ flow: {
onTurnEnd: ...
onMove: ...
}
]
}
}
```

!> An important point to note is that in a multiplayer game, all of the code under
`flow` is executed only on the server. The code under `moves`, in contrast, is
`flow` is executed only on the master. The code under `moves`, on the other hand, is
run on both client and server. It is run on the client in order to effect a
quick state transition without network lag, and run on the server to process
the authoritative version of the state.
2 changes: 1 addition & 1 deletion docs/react/boardgameio.min.js

Large diffs are not rendered by default.

18 changes: 10 additions & 8 deletions docs/react/phases-2.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,30 +41,32 @@
},

flow: {
phases: [
{
name: 'draw phase',
startingPhase: 'draw',

phases: {
draw: {
endPhaseIf: G => (G.deck <= 0),
allowedMoves: ['drawCard'],
next: 'play',
},
{
name: 'play phase',
play: {
allowedMoves: ['playCard'],
endPhaseIf: G => (G.hand <= 0),
next: 'draw',
}
],
},
}
});

class Board extends React.Component {
drawCard = () => {
if (this.props.ctx.phase != 'draw phase') return;
if (this.props.ctx.phase != 'draw') return;
this.props.moves.drawCard();
this.props.events.endTurn();
}

playCard = () => {
if (this.props.ctx.phase != 'play phase') return;
if (this.props.ctx.phase != 'play') return;
this.props.moves.playCard();
this.props.events.endTurn();
}
Expand Down
12 changes: 3 additions & 9 deletions docs/turn-order.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,15 +106,9 @@ Game({
},

flow: {
phases: [
{
name: 'A',
turnOrder: TurnOrder.ANY,
},
{
name: 'B',
turnOrder: TurnOrder.ONCE,
},
phases: {
A: { turnOrder: TurnOrder.ANY },
B: { turnOrder: TurnOrder.ONCE },
],
}
}
Expand Down
17 changes: 6 additions & 11 deletions examples/react/phases/diagram.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,12 @@ const game = Game({
moves: {},

flow: {
phases: [
{
name: 'A',
},
{
name: 'B',
},
{
name: 'C',
},
],
startingPhase: 'A',
phases: {
A: { next: 'B' },
B: { next: 'C' },
C: { next: 'A' },
},
},
});

Expand Down
17 changes: 9 additions & 8 deletions examples/react/phases/phases.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,19 @@ const game = Game({
},

flow: {
phases: [
{
name: 'take phase',
startingPhase: 'take',
phases: {
take: {
endPhaseIf: G => G.deck <= 0,
allowedMoves: ['takeCard'],
next: 'play',
},
{
name: 'play phase',
play: {
allowedMoves: ['playCard'],
endPhaseIf: G => G.hand <= 0,
next: 'take',
},
],
},
},
});

Expand All @@ -45,13 +46,13 @@ class Board extends React.Component {
};

takeCard = () => {
if (this.props.ctx.phase != 'take phase') return;
if (this.props.ctx.phase != 'take') return;
this.props.moves.takeCard();
this.props.events.endTurn();
};

playCard = () => {
if (this.props.ctx.phase != 'play phase') return;
if (this.props.ctx.phase != 'play') return;
this.props.moves.playCard();
this.props.events.endTurn();
};
Expand Down

0 comments on commit 0568857

Please sign in to comment.