Skip to content

Commit

Permalink
Merge cb4fee4 into dc96b26
Browse files Browse the repository at this point in the history
  • Loading branch information
delucis committed Jan 17, 2021
2 parents dc96b26 + cb4fee4 commit 2534851
Show file tree
Hide file tree
Showing 59 changed files with 664 additions and 602 deletions.
27 changes: 15 additions & 12 deletions docs/documentation/api/Game.md
Expand Up @@ -8,7 +8,7 @@
// Function that returns the initial value of G.
// setupData is an optional custom object that is
// passed through the Game Creation API.
setup: (ctx, setupData) => G,
setup: ({ ctx, ...plugins }, setupData) => G,

// Optional function to validate the setupData before
// matches are created. If this returns a value,
Expand All @@ -18,12 +18,12 @@

moves: {
// short-form move.
A: (G, ctx, ...args) => {},
A: ({ G, ctx, playerID, events, random, ...plugins }, ...args) => {},

// long-form move.
B: {
// The move function.
move: (G, ctx, ...args) => {},
move: ({ G, ctx, playerID, events, random, ...plugins }, ...args) => {},
// Prevents undoing the move.
undoable: false,
// Prevents the move arguments from showing up in the log.
Expand Down Expand Up @@ -51,16 +51,19 @@
order: TurnOrder.DEFAULT,

// Called at the beginning of a turn.
onBegin: (G, ctx) => G,
onBegin: ({ G, ctx, events, random, ...plugins }) => G,

// Called at the end of a turn.
onEnd: (G, ctx) => G,
onEnd: ({ G, ctx, events, random, ...plugins }) => G,

// Ends the turn if this returns true.
endIf: (G, ctx) => true,
// Returning { next }, sets next playerID.
endIf: ({ G, ctx, events, random, ...plugins }) => (
true | { next: '0' }
),

// Called at the end of each move.
onMove: (G, ctx) => G,
onMove: ({ G, ctx, events, random, ...plugins }) => G,

// Ends the turn automatically after a number of moves.
moveLimit: 1,
Expand All @@ -86,13 +89,13 @@
phases: {
A: {
// Called at the beginning of a phase.
onBegin: (G, ctx) => G,
onBegin: ({ G, ctx, events, random, ...plugins }) => G,

// Called at the end of a phase.
onEnd: (G, ctx) => G,
onEnd: ({ G, ctx, events, random, ...plugins }) => G,

// Ends the phase if this returns true.
endIf: (G, ctx) => true,
endIf: ({ G, ctx, events, random, ...plugins }) => true,

// Overrides `moves` for the duration of this phase.
moves: { ... },
Expand All @@ -111,11 +114,11 @@

// Ends the game if this returns anything.
// The return value is available in `ctx.gameover`.
endIf: (G, ctx) => obj,
endIf: ({ G, ctx, events, random, ...plugins }) => obj,

// Called at the end of the game.
// `ctx.gameover` is available at this point.
onEnd: (G, ctx) => G,
onEnd: ({ G, ctx, events, random, ...plugins }) => G,

// Disable undo feature for all the moves in the game
disableUndo: true,
Expand Down
4 changes: 2 additions & 2 deletions docs/documentation/concepts.md
Expand Up @@ -41,7 +41,7 @@ immutability is handled by the framework.

```js
moves: {
drawCard: (G, ctx) => {
drawCard: ({ G, ctx }) => {
const card = G.deck.pop();
G.hand.push(card);
},
Expand Down Expand Up @@ -78,7 +78,7 @@ onClick() {

### Events

These are framework-provided functions that are analagous to moves, except that they work on `ctx`. These typically advance the game state by doing things like
These are framework-provided functions that are analogous to moves, except that they work on `ctx`. These typically advance the game state by doing things like
ending the turn, changing the game phase etc.
Events are dispatched from the client in a similar way to moves.

Expand Down
4 changes: 2 additions & 2 deletions docs/documentation/debugging.md
Expand Up @@ -25,8 +25,8 @@ It can sometimes be helpful to surface some metadata during a move.
You can do this by using the log plugin. For example,

```js
const move = (G, ctx) => {
ctx.log.setMetadata('metadata for this move');
const move = ({ log }) => {
log.setMetadata('metadata for this move');
};
```

Expand Down
7 changes: 4 additions & 3 deletions docs/documentation/events.md
Expand Up @@ -82,12 +82,13 @@ for more details.

You can trigger events from a move or code inside
your game logic (a phase’s `onBegin` hook, for example).
This is done through the `ctx.events` object:
This is done through the `events` API in the object passed
as the first argument to moves:

```js
moves: {
drawCard: (G, ctx) => {
ctx.events.endPhase();
drawCard: ({ G, ctx, events }) => {
events.endPhase();
};
}
```
Expand Down
10 changes: 6 additions & 4 deletions docs/documentation/immutability.md
Expand Up @@ -16,7 +16,7 @@ A traditional pure function just accepts arguments and then
returns the new state. Something like this:

```js
function move(G, ctx) {
function move({ G }) {
// Return new value of G without modifying the arguments.
return { ...G, hand: G.hand + 1 };
}
Expand All @@ -33,7 +33,7 @@ immutability principle. Both styles are supported interchangeably,
so use the one that you prefer.

```js
function move(G, ctx) {
function move({ G }) {
G.hand++;
}
```
Expand All @@ -42,7 +42,9 @@ function move(G, ctx) {
In fact, returning something while also mutating `G` is
considered an error.

!> `ctx` is a read-only object and is never modified in either style.
!> You can only modify `G`. Other values passed to your moves
are read-only and should never be modified in either style.
Changes to `ctx` can be made using [events](events.md).

### Invalid moves

Expand All @@ -57,7 +59,7 @@ Tic-Tac-Toe.
import { INVALID_MOVE } from 'boardgame.io/core';

moves: {
clickCell: function(G, ctx, id) {
clickCell: function({ G, ctx }, id) {
// Illegal move: Cell is filled.
if (G.cells[id] !== null) {
return INVALID_MOVE;
Expand Down
22 changes: 11 additions & 11 deletions docs/documentation/phases.md
Expand Up @@ -20,18 +20,18 @@ two moves:
- play a card from your hand onto the deck.

```js
function DrawCard(G, ctx) {
function DrawCard({ G, playerID }) {
G.deck--;
G.hand[ctx.currentPlayer]++;
G.hand[playerID]++;
}

function PlayCard(G, ctx) {
function PlayCard({ G, playerID }) {
G.deck++;
G.hand[ctx.currentPlayer]--;
G.hand[playerID]--;
}

const game = {
setup: ctx => ({ deck: 6, hand: Array(ctx.numPlayers).fill(0) }),
setup: ({ ctx }) => ({ deck: 6, hand: Array(ctx.numPlayers).fill(0) }),
moves: { DrawCard, PlayCard },
turn: { moveLimit: 1 },
};
Expand All @@ -58,7 +58,7 @@ list of moves, which come into effect during that phase:

```js
const game = {
setup: ctx => ({ deck: 6, hand: Array(ctx.numPlayers).fill(0) }),
setup: ({ ctx }) => ({ deck: 6, hand: Array(ctx.numPlayers).fill(0) }),
turn: { moveLimit: 1 },

phases: {
Expand Down Expand Up @@ -102,7 +102,7 @@ empty.
phases: {
draw: {
moves: { DrawCard },
+ endIf: G => (G.deck <= 0),
+ endIf: ({ G }) => (G.deck <= 0),
+ next: 'play',
start: true,
},
Expand Down Expand Up @@ -134,8 +134,8 @@ You can also run code automatically at the beginning or end of a phase. These ar
```js
phases: {
phaseA: {
onBegin: (G, ctx) => { ... },
onEnd: (G, ctx) => { ... },
onBegin: ({ G, ctx }) => { ... },
onEnd: ({ G, ctx }) => { ... },
},
};
```
Expand Down Expand Up @@ -170,7 +170,7 @@ You can also end a phase by returning a truthy value from its
phases: {
phaseA: {
next: 'phaseB',
endIf: (G, ctx) => true,
endIf: ({ G, ctx }) => true,
},
phaseB: { ... },
},
Expand All @@ -182,7 +182,7 @@ an object containing a `next` field from your `endIf`:
```js
phases: {
phaseA: {
endIf: (G, ctx) => {
endIf: ({ G, ctx }) => {
return { next: G.condition ? 'phaseB' : 'phaseC' }
},
},
Expand Down
10 changes: 4 additions & 6 deletions docs/documentation/plugins.md
Expand Up @@ -34,9 +34,9 @@ A plugin is an object that contains the following fields.
// wrapper can modify G before passing it down to
// the wrapped function. It is a good practice to
// undo the change at the end of the call.
fnWrap: (fn) => (G, ctx, ...args) => {
fnWrap: (fn) => ({ G, ...rest }, ...args) => {
G = preprocess(G);
G = fn(G, ctx, ...args);
G = fn({ G, ...rest }, ...args);
G = postprocess(G);
return G;
},
Expand Down Expand Up @@ -64,11 +64,9 @@ import { PluginA, PluginB } from 'boardgame.io/plugins';
const game = {
name: 'my-game',

moves: {
...
},

plugins: [PluginA, PluginB],

// ...
};
```

Expand Down
55 changes: 30 additions & 25 deletions docs/documentation/random.md
Expand Up @@ -10,45 +10,53 @@ This poses interesting challenges regarding the implementation.
- **AI**. Randomness makes games interesting since you cannot predict the future, but it
needs to be controlled in order for allowing games that can be replayed exactly (e.g. for AI purposes).

- **PRNG State**. The game runs on both the server and client.
- **<abbr title="Pseudo-Random Number Generator">PRNG</abbr> State**.
The game runs on both the server and client.
All code and data on the client can be viewed and used to a player's advantage.
If a client could predict the next random numbers that are to be generated, the future flow of a game stops being unpredictable.
The library must not allow such a scenario. The RNG and its state must stay at the server.
The library must not allow such a scenario. The RNG and its state must stay on the server.

- **Pure Functions**. The library is built using Redux. This is important for games since each move is a [reducer](https://redux.js.org/docs/basics/Reducers.html),
and thus must be pure. Calling `Math.random()` and other functions that
maintain external state would make the game logic impure and not idempotent.

### Using Randomness in Games

The object passed to moves and other game logic contains an object `random`,
which exposes a range of functions for generating randomness.

For example, the `random.D6` function is similar to rolling six-sided dice:

```js
{
moves: {
rollDie: (G, ctx) => {
G.dieRoll = ctx.random.D6();
rollDie: ({ G, random }) => {
G.dieRoll = random.D6(); // dieRoll = 1–6
},

rollThreeDice: ({ G, random }) => {
G.diceRoll = random.D6(3); // diceRoll = [1–6, 1–6, 1–6]
}
},
}
```

?> The PRNG state is maintained inside `ctx._random` by the `Random`
package automatically.
You can see details for all the available random functions below.

### Seed

The library uses a `seed` in `ctx._random` that is stripped before it
is sent to the client. All the code that needs randomness uses this
`seed` to generate random numbers.

You can override the initial `seed` like this:
You can set the initial `seed` used for the random number generator
on your game object:

```js
const game = {
seed: <somevalue>
...
seed: 42,
// ...
};
```

?> `seed` can be either a string or a number.

## API Reference

### 1. Die
Expand All @@ -67,10 +75,9 @@ The die roll value (or an array of values if `diceCount` is greater than `1`).
```js
const game = {
moves: {
move(G, ctx) {
const die = ctx.random.Die(6); // die = 1-6
const dice = ctx.random.Die(6, 3); // dice = [1-6, 1-6, 1-6]
...
move({ random }) {
const die = random.Die(6); // die = 1-6
const dice = random.Die(6, 3); // dice = [1-6, 1-6, 1-6]
},
}
};
Expand All @@ -85,9 +92,8 @@ Returns a random number between `0` and `1`.
```js
const game = {
moves: {
move(G, ctx) {
const n = ctx.random.Number();
...
move({ random }) {
const n = random.Number();
},
}
};
Expand All @@ -108,8 +114,8 @@ The shuffled array.
```js
const game = {
moves: {
move(G, ctx) {
G.deck = ctx.random.Shuffle(G.deck);
move({ G, random }) {
G.deck = random.Shuffle(G.deck);
},
},
};
Expand All @@ -129,9 +135,8 @@ const game = {
```js
const game = {
moves: {
move(G, ctx) {
const die = ctx.random.D6();
...
move({ random }) {
const die = random.D6();
},
}
};
Expand Down
4 changes: 2 additions & 2 deletions docs/documentation/secret-state.md
Expand Up @@ -78,8 +78,8 @@ These can be marked as server-only by setting `client: false` on move:
```js
moves: {
moveThatUsesSecret: {
move: (G, ctx) => {
...
move: ({ G, random }) => {
G.secret.value = random.Number();
},

client: false,
Expand Down

0 comments on commit 2534851

Please sign in to comment.