Closed ghost closed 4 years ago
Copying suggestion from @syndesis_gitlab on Gitter:
Wouldn't
TurnOrder.CUSTOM
let you do this?
turn: {
order: {
// Get the initial value of playOrderPos.
// This is called at the beginning of the phase.
first: (G, ctx) => 0,
// Get the next value of playOrderPos.
// This is called at the end of each turn.
// The phase ends if this returns undefined.
next: (G, ctx) => (ctx.playOrderPos + 1) % ctx.numPlayers,
// OPTIONAL:
// Override the initial value of playOrder.
// This is called at the beginning of the game / phase.
playOrder: (G, ctx) => [...],
}
}
The
playOrder
piece.
There are a couple of approaches you could use I think. These all use the randomness API provided by boardgame.io to ensure predictable results across clients and server.
Pick a random player to start, but maintain the usual play order. Kind of like playing clockwise in a boardgame but starting with a random player:
turn: {
order: {
// Die returns values 1...n, but we need 0...n-1
first: (G, ctx) => ctx.random.Die(ctx.numPlayers) - 1,
},
}
Randomise the play order completely, so the game starts at the beginning of the play order, but the players have been randomly shuffled:
turn: {
order: {
playOrder: (G, ctx) => {
// generate an array of sequential player IDs ['0', '1', '2', ...]
const defaultPlayOrder = Array(ctx.numPlayers).fill().map((_, i) => i + '');
// return a shuffled version of the array
return ctx.random.Shuffle(defaultPlayOrder);
},
},
}
In these two cases, if you set turn.order
inside a phase configuration its first
or playOrder
will be run each time the phase begins, so if you return to that phase, the order will be randomised again.
If you only want to randomise the starting player once during game setup, you can adapt these approaches using your setup
function:
Random first player set during game set-up:
const game = {
setup: ctx => {
const startingPlayer = ctx.random.Die(ctx.numPlayers) - 1;
return { startingPlayer }
},
turn: {
order: {
first: G => G.startingPlayer,
},
},
}
Random play order created during game set-up (using the TurnOrder.CUSTOM_FROM
helper):
const { TurnOrder } = require('boardgame.io/core');
const game = {
setup: ctx => {
const defaultPlayOrder = Array(ctx.numPlayers).fill().map((_, i) => i + '');
const playOrder = ctx.random.Shuffle(defaultPlayOrder);
return { playOrder }
},
turn: {
order: TurnOrder.CUSTOM_FROM('playOrder'),
},
}
Thanks for the quick response @delucis!
I think option 3 is best for my case as it is only ever a 2 player game, and the randomization only needs to occur once during setup.
Does this option work when generating state across clients and a remote server? @nicolodavis raised this as a possible issue on Gitter.
turn: {
order: {
// Get the initial value of playOrderPos.
// This is called at the beginning of the phase.
first: (G, ctx) => 0,
}
}
https://boardgame.io/documentation/#/turn-order?id=creating-a-custom-turn-order
One question on the documentation. In the example above it says "at the beginning of the phase."
. Can I take that to mean the starting player for each turn?
Does this option work when generating state across clients and a remote server?
Yes. If you were using Math.random()
to generate randomness, it would produce different results on client and server and weirdness could follow. The ctx.random
functions use a pseudo-random number generator that will produce the same result on the server and the client. There’s more detail about the randomness API in the docs.
In the example above it says "at the beginning of the phase.". Can I take that to mean the starting player for each turn?
This applies if you’re using Phases, which provide a way to have different game configurations during different periods of your game. For example, if you have a game that has two different turn orders in different parts of it. Every time a phase begins — i.e. we move to it from another phase — it will call first
to get the first player in the phase.
If you’re not using phases, just a top-level turn
configuration, first
will be called once at the start of the game and then never again. After that, the next
function is called each turn to see who the next player is. Most often that’s just incrementing through the playOrder
step by step, but you can have whatever logic you want in next
.
If you’re not using phases, just a top-level turn configuration, first will be called once at the start of the game and then never again.
I didn't realise that you could have a top level property of first
in game.turn
. Something like:
const game = {
setup: ctx => {
const startingPlayer = ctx.random.Die(ctx.numPlayers) - 1;
return { startingPlayer }
},
turn: {
first: G => G.startingPlayer,
},
}
Ah, no, sorry, I meant a top-level turn
in the game:
const game = {
turn: {
order: {
first: G => G.startingPlayer,
},
},
}
As opposed to a turn configuration inside a phase:
const game = {
phases: {
'phase-name': {
turn: {
order: {
first: G => G.startingPlayer,
},
},
},
},
}
Awesome! Thanks for all the help 👍
As instructed to by @nicolodavis, raising an issue after first discussing this on Gitter.
Issue
Is there a mechanism for randomizing the starting play order of a game, so that a random player starts each game?