boardgame.io icon indicating copy to clipboard operation
boardgame.io copied to clipboard

Timer

Open nicolodavis opened this issue 6 years ago • 38 comments

This has come up a few times, so I think we should prioritize this.

Suggestions:

  • Some timers in ctx that capture the time at the beginning of a turn / phase.
  • Some hooks to automatically end the turn / phase when some amount of time has elapsed.

nicolodavis avatar Jan 26 '18 23:01 nicolodavis

Suggestions:

  • opportunity to pause a timer

akaguny avatar Jan 29 '18 17:01 akaguny

it's can be inplemented with simple timeout or react use specific as $timeout in angularjs?

akaguny avatar Feb 06 '18 04:02 akaguny

Yes, a simple timeout would also work (probably not necessary to record anything in ctx).

nicolodavis avatar Feb 06 '18 05:02 nicolodavis

@nicolodavis what api timer should take? when i create game i was need pause func(tur on/off activity of timer) and state variable, that take me: How much seconds left. But my time leav in React component and store state in this component https://github.com/akaguny/alias-boardgame/blob/master/src/App.js#L54

akaguny avatar Feb 10 '18 05:02 akaguny

I think we just need a simple setTimeout that is called at the beginning of the turn that ends the turn automatically when the callback is invoked. We can use a similar one for phases as well.

@nicolodavis i'm agree that we should make a simple one timer for first time. But when we configure it? Configure like maxMoves?

akaguny avatar Feb 10 '18 05:02 akaguny

i started work on this feature, please set label tacked or some familiar

akaguny avatar Feb 10 '18 09:02 akaguny

@akaguny Yes, let's start simple with something similar to movesPerTurn and expand it later. Will take a look at your PR. Thanks for working on this!

nicolodavis avatar Feb 12 '18 04:02 nicolodavis

Greetings, someone is woriking on this issue? If not I can take it

sedobrengocce avatar Mar 19 '18 13:03 sedobrengocce

@akaguny is busy right now, so feel free to take it.

nicolodavis avatar Mar 19 '18 13:03 nicolodavis

My thoughts on how this should be implemented:

  • At the beginning of the turn, call a setTimeout that results in the server sending an endTurn event to itself. This event should be routed through the transport interface (the socket.io handler).

I think it makes sense for this to wait for #251 so that we can create some hooks to send synthetic events that appear to come from a client, but are actually initiated from the server itself.

nicolodavis avatar Aug 15 '18 02:08 nicolodavis

The way heroiclabs.com handles this is by using a customizable 'tick rate', as explained: "Tick rate¶ The server will periodically call the match loop function even when there is no input waiting to be processed. The logic is able to advance the game state as needed. It can also validate incoming input and kick players who've been inactive for some time.

This periodic call is known as Tick Rate and represents a desired fixed frequency at which the match should update. Tick Rate is configurable and typical frequencies range from once per second for turn-based games to dozens of times per second for fast-paced gameplay.

All incoming client data messages are queued until each tick when they are handed off to the match loop to be processed. Tick Rate is expressed as a number representing desired executions per second. For example a rate of "10" represents 10 ticks to the match loop per second (on average once every 100ms)."

This allows them to implement fast paced 'realtime' turn based games, medium paced games (clash royale), and slow paced games (words with friends).

Why would this be better?

  1. Having a fixed secondsPerTurn or secondsPerPhase is very restrictive, imagine a blitz chess game implementation - turn times change sometimes depending on how much time you spent on your previous turns - maybe you played a bunch of sequential turns very quickly and should have more time per turn than your opponent. We would be forced to implement this on the client side (a cheating vector) if all we had on the server were fixed variables of secondsPerTurn or secondsPerPhase.

  2. Just because there is a high tick rate, if nothing changes, there's no need to send updates to the client for that tick.

  3. Being able to have the server run the game loop itself without input from the client is useful for detecting disconnected players, reconnected players, broadcasting newly joined players/spectators during someone's turn, and giving all players notifications of this, as well as the obvious case of turn timeouts. Imagine playing a game and getting notified that the opponent has disconnected - it doesn't make sense to sit there and wait for 2 hours for them to make a move. How would you know they disconnected if your 'setTimeout' for the turn is 2 weeks? Answer: by having the server notify you during the middle of your or their turn.

  4. What if you want to have a randomized event happen during player turns, such as unit drops? You could have unit drops for a strategy board game happen randomly every day, maybe 5 times per day, during all matches, no matter whose turn it is, all at the same time. Whoever wins the current match gets the unit drop (good way to encourage playing more). This is just one more use case that benefits from an active server loop rather than a passive one. (Unit drops do not have to affect the current game, but can affect the arsenal of units the player has for setting up the board for all future games).

lw1990 avatar Aug 15 '18 21:08 lw1990

I'm open to having a:

flow: {
  tickDelay: 1,
  onTick(G, ctx) { ... }
}

that's called at a configured frequency. We can launch it as an experimental feature to support some of these use-cases. Note that the framework is not designed for realtime games, so bad things will happen performance-wise if the tick delay is too low.

I still think this is orthogonal to secondsPerTurn. That doesn't need the tick mechanism to be implemented, and also avoids having the developer do any calculations to figure out if the turn has ended.

So, my suggestion would be to wait for #251, and then we can implement these as two separate changes.

Note that [3] is already possible today (look at the examples for how to check connects / disconnects).

nicolodavis avatar Aug 16 '18 02:08 nicolodavis

  • The quote was taken from here.
  • The tick timer is somehow embedded there, though I don't know how this works (on a technical level) for lack of Go knowledge.

Stefan-Hanke avatar Aug 16 '18 05:08 Stefan-Hanke

@nicolodavis my understanding is that isConnected only tells the player on their respective client if they are connected or disconnected from the server. While that's useful a good addition to this is knowing if your opponent(s) are connected/disconnected as well. Since there's only one isConnected prop I don't know how it can track them all. Knowing if your opponents are connected informs you that it may make sense to wait for them to make a move right then, or come back to the game later if they are disconnected. The server knowing if they are connected/disconnected can help force end the game (for example if they lost their internet connection for 10 minutes in a turn-based game that expects moves in under a minute).

Each client sending messages to the server onTick() per tickDelay would solve this problem though.

lw1990 avatar Aug 16 '18 18:08 lw1990

Why wouldn't we just use the same mechanism that we use currently to determine the connection status for everyone and not conflate it with onTick?

It's quite easy for the server to just broadcast the connection status to everyone each time someone connects / disconnects.

nicolodavis avatar Aug 17 '18 00:08 nicolodavis

Is that how the current mechanism works? If there are 10 players in a game, and player 7 disconnects, how do clients 1-6 and 8-10 know that it was player 7 who disconnected with a simple isConnected boolean and no ID? Do they have to play their turn (or wait for someone else to play their turn) before the server 'runs' and notifies them that someone is disconnected?

lw1990 avatar Aug 18 '18 13:08 lw1990

@lw1990 I believe the current mechanism can only be used to determine if the current client is connected to the server--not other players. What @nicolodavis meant was enhancing that mechanism to track connected status for all players and broadcast connection updates.

jorbascrumps avatar Aug 18 '18 17:08 jorbascrumps

Yep, what I'm saying is that connection status can be implemented without using onTick.

nicolodavis avatar Aug 19 '18 00:08 nicolodavis

If the current mechanism is the client polling the server for a connection (and the server doing nothing really), and you're going to change it so that the server does stuff when clients poll it (like notify other clients that a client is still connected or is disconnected), then this becomes a real-time game framework (which is exactly what is needed), but limiting it to connected/disconnected in real time defeats the purpose. Many turn based games need pseudo-real time turn timeouts as well.

If you're going to real-time poll the server and clients anyway for connection status/notification, you might as well just make it a server-driven framework with a turn-based feel, instead of implement a truly client-input-driven framework, with several real-time server-driven features that will probably be even more effort to implement tacked ontop of the client-driven framework for connection status and timers..

So basically, if the server can be configured to fire updates to all clients every X milliseconds, as well as handle all incoming messages from clients in a message queue (event-driven), then you have the necessary framework to handle turn-based games, real-time disconnect/connection/reconnection events, and real-time timers/timeouts and server-driven events (like unit drops). The server basically has to be in control at all times, if the framework waits for the client to give the server input before it does anything EXCEPT in the case of connection/disconnection or EXCEPT in the case of ontick, you just implemented 3 different multiplayer schemas when it could be a single real-time one.

lw1990 avatar Aug 19 '18 01:08 lw1990

I think we're talking past each other here. I'm in favor of exploring the onTick mechanism that you're proposing.

All I'm saying is that for connect / disconnects, socket.io already provides an asynchronous mechanism of handling it and we can just use that instead of polling manually.

nicolodavis avatar Aug 19 '18 01:08 nicolodavis

I understand your concern that it will be difficult to merge certain asynchronous mechanisms with an event driven queue on the server if we eventually go that route (in the case of connects, I would imagine that socket.io could just add another event to the message queue to be scooped up by the next tick so it is still possible).

We'll have to think about it carefully as it would change the paradigm of the framework significantly I think. For now, I think the easiest way to make some progress in that direction would be to introduce onTick and then build up from there.

In general I'm quite in favor of a message queue also for the sake of enabling horizontal scaling. If we have a global queue that server workers can just pull from, it becomes quite easy to add capacity by just increasing the number of workers. This will also become more or less necessary as we start to support real-time features.

nicolodavis avatar Aug 19 '18 01:08 nicolodavis

I'm keen to know if this is being roadmapped? I'm trying to work out if boardgame.io would suit a '1 hour per turn', 'total game time 24 hours', between 2 players sort of scenario? - ideally i also only want to write my game logic once and have the server enforce that logic through validation (minimise cheat vectors)

TakesTheBiscuit avatar Dec 18 '18 22:12 TakesTheBiscuit

It seems to me there needs to be two features implemented to get the time-based features we as game developers want:

  1. There needs to be a way to schedule an action on the server (like ending a turn) some time in the future when a turn starts. If a player makes a move before then, this scheduled action is discarded. If not, then the turn will end/game will end because the player didn't make a move in time.

  2. There needs to be a way to update other player clients when a player triggers an Action, such as hovering over a slot in a connect4 style game, which would allow the other player to see what the other user is about to do or may do - making it more engaging. A 'turn' doesn't really get used up, and the 'phase' thing won't update both clients if I understand correctly. It makes little sense to continually send out updates like a real-time game, it should just happen when a player actually triggers the action by hovering over a different slot. Furthermore these actions shouldn't be part of the move log or move history when undoing moves etc.

lw1990 avatar Dec 29 '18 19:12 lw1990

@lw1990 I'm not sure how #2 is related to this issue. #1 is correct.

@TakesTheBiscuit This is not on the radar for v1.

nicolodavis avatar Dec 30 '18 02:12 nicolodavis

@nicolodavis is there a simple way to end a phase after a certain period of time, triggered server-side?

jaxony avatar Jan 15 '19 08:01 jaxony

Not at the moment. We only support client side timers right now.

nicolodavis avatar Jan 15 '19 08:01 nicolodavis

can someone provide a tutorial on how we can implement turn timeouts triggered on the server, even if it's not part of the boardgame.io feature set yet? this issue has been open for over a year and a half, and I'm not knowledgeable enough to figure it out, but if someone could show us how it's done in a 'hack', maybe this would get things rolling for how it should actually work in boardgame.io later

lw1990 avatar Aug 13 '19 03:08 lw1990

@nicolodavis I've been thinking about server-authoritative multiplayer games and turn timeouts and I may have come up with a solution that doesn't require server-side async timers like this issue suggests, please let me know if this is a viable strategy

If both players have client-side timers when every turn begins, then when any client side turn timer expires it sends a message to the server to try to end the turn. If the server code also says it's time to end the turn, then it does so.

Even if one player is trying to cheat and modifies the client-side code not to 'poke' the server to execute its endTurn function, the other player's non-cheating client will still do so. On the flip side, if one player tries to tell the server to end the turn prematurely for the other player, the server side code will be unaffected and won't do it.

So in theory it should be possible to have 'server-side' authoritative timers by utilizing the fact that both players are virtually never going to try to cheat by making their turns last forever during the same turn.

This would also allow for any additional features, such as pausing turns, without being abusable.

lw1990 avatar Aug 13 '19 17:08 lw1990

@lw1990 I like that idea. We also need to consider what might happen if the client refreshes the game and resyncs (we would need to restart the client-side timer). Also we would need to account for small differences in the client timer vs. server (what happens if the client sends the 'poke' to the server 0.05 seconds early?)

jasonharrison avatar Aug 13 '19 17:08 jasonharrison

I'm also a bit concerned about the amount of co-ordination required to account to pull this off. There is inevitably going to be some retry logic on the client that will become complex. It might actually be simpler to just implement server-side timeouts.

nicolodavis avatar Aug 14 '19 06:08 nicolodavis