Closed iliu closed 4 years ago
@iliu Glad you’re staying safe!
The inability to retrigger the same phase has come up a few times recently, so it might be something to look at refactoring for scenarios where you have some onBegin
and onEnd
logic you want to trigger, but only really one phase.
A quick question: does the “confirm score” action actually change how the game progresses or is it more of a UI thing? If it’s mainly adding the ability for players to see that round’s scores, click OK, and keep playing, you might not need a move for that.
If confirming the score does have some impact on the game, or you want to keep the UI logic in the game state, I think you could use a score cards phase:
const phases = {
play: {
start: true,
next: 'score',
// deal cards when the play phase begins
onBegin: (G, ctx) => dealCards(G, ctx),
// end the play phase once all the cards are played
endIf: G => G.allCardsArePlayed,
// every move check if it’s the end of a round, and rotate hand if necessary
onMove: (G, ctx) => {
if (ctx.playOrderPos === ctx.playOrder.length - 1) {
// is last player in round (assuming round starts with player 0)
rotateHand(G);
}
},
moves: {
// playing a card should end the player’s turn
playCard: (G, ctx) => ctx.events.endTurn(),
},
},
score: {
next: 'play',
// compute scores at the start of the score phase
onBegin: (G, ctx) => scoreRound(G, ctx),
// end the score phase once all players have confirmed
endIf: (G, ctx) => ctx.numMoves > 0 && ctx.activePlayers === null,
turn: {
// assuming here players can confirm in any order
activePlayers: { all: 'confirm' },
stages: {
confirm: {
moves: {
// remove the player from activePlayers when they confirm the score
confirmScore: (G, ctx) => ctx.events.endStage()
},
},
},
},
},
}
that's great. it never occurred to me that i didn't need to keep the confirmation in the game state. The tradeoff is if the confirm state is in the client, we wouldn't be able to show everyone who isn't "ready" for the next round. But i think that's a minor tradeoff. I can just show the score breakdown as a modal to be dismissed purely on the client side.
a couple questions about your code:
next
key to progress the phases work around it?Thanks for your responses!
The tradeoff is if the confirm state is in the client, we wouldn't be able to show everyone who isn't "ready" for the next round.
Yes, it depends on your use case. If you need to share a “waiting” indicator or whatever, you would likely want to keep this in the game state.
you keep track of entered phases, and if you try to go to an already entered phase the game doesn't progress to it
This applies to phases cycling while processing a single game action. If a game tries to enter a phase several times while making a move, the framework assumes there is some kind of infinite loop and doesn’t allow that. (That might happen if a move triggered some loop of endIf
conditions for example.) Returning to phases used earlier in a game is no problem.
is it possible to have next take in a function with game state and return the next state?
That isn’t currently possible. Although it’s an interesting idea. In the phase.onEnd
hook, the phase is already ending — potentially via a setPhase
call — so you shouldn’t setPhase
again there. Instead, you should centralise your phase ending logic. For example, if onMove
is ending the phase, you could figure out what the next phase should be there.
is that a place to possibly mutate game state? What's the intuition to use that instead of logic directly within the move itself?
Yes, you can mutate state in onMove
. And it’s a question of style what is move logic and what is hook logic. In theory, you could have moves set a field specifying which move to make in G
and then do all the processing in onMove
. (It’s not a very good idea though!) In general, I would use onMove
for generic code that it makes sense to run for every move. A good example might be calculating a score that might change no matter which move a player made.
Oh, and with regards to server vs client processing. In general, you can think of everything happening on both. The client tries to process the entire action and provide a speedy update to the UI. Meanwhile, the server will process the same action and return the canonical state. The main exceptions to this are moves that use the randomness API, which run on the server only. There’s also a “long-form” move syntax, which lets you declare that a move should only run on the server. (See the Game docs for details.)
thanks! this clarifies things alot and i think i have some ideas on how to better structure the game!
oh sorry 1 last question:
if i call the setPhase/setTurn/setStage events in onMove for example, it will override the next phase/turn/stage that's set in the next
key right? the game will not progress to the next
state?
Yes — you can think of next
as the value used when you call endPhase
, but it’s overwritten when using setPhase
. You don’t have to specify next
in your game configuration either — if you’re always using setPhase
, it’s superfluous.
Same for turns: turn.order.next
is called to work out who plays next, but if you call endTurn({ next: '2' })
(there is no setTurn
event), the next player will be player ID '2'
.
Stages are a little different because they are designed to allow players to be in different stages simultaneously. I’d recommend checking out the docs for more details on those.
First of all, I really enjoyed this framework! During shelter in place for covid this gives me a lot of joy to build games for friends to play!
I'm trying to build a simple sushi go game. The basic mechanics are: 3 rounds:
I was able to model the game currently by having just 1 move in turn to play the card, end the turn when everyone played a card, and use onEnd to score and re-deal if need. However, when the round is scored, i also want to show the breakdown, and have players confirm. So actually i want two types of turns, one for playing the card, and one for confirming the score after each round.
I tried a couple methods. Splitting the game into play card phase and score phase doesn't work because you can't go back to the same phase, so i can't use phases to model a round. I thought about using stages, but the round progresses globally for all players, and stages seem to be for an individual player. I tried using ctx.events.setActivePlayers to progress all players to state, but i'm calling it in onEnd, and it seems if i setStage or setPhase during onEnd, onEnd gets called twice because it ends the current phase.
The only solution i can think of is modeling my own "phase" in the game state and having these conditionals in the turn that checks the game "phase" and does different actions. But that seems like an architecture smell, and not an elegant solution in an otherwise elegant framework.
Is there anyway to model rounds and phases within the round that can be repeated?