Time control
There are six time control systems on OGS, a start clock system, multiple ways to pause games, and latency compensation considerations when dealing with real-time games. While all of this may seem daunting at first, once you wrap your head around it it's not so bad - this document will help guide you through that process.
Applicable fields within the game object.
There are three fields of interest within a game data object. time_control
, clock
, and pause_control
(which won't exist unless the game has been paused at least once).
time_control
The time_control
field is used to describe the time control system being used in the game and does not change. This field takes on one of six structures, depending on what time control system is used
{
"time_control" : "fischer",
"initial_time" : 86400, /* in seconds */
"time_increment": 3600, /* in seconds */
"max_time" : 86400 /* in seconds */
}
{
"time_control": "byoyomi",
"main_time" : 86400, /* seconds */
"period_time" : 3600, /* seconds */
"periods" : 5 /* count */
}
{
"time_control": "simple",
"per_move" : 86400 /* seconds */
}
{
"time_control" : "canadian",
"main_time" : 86400, /* seconds */
"period_time" : 86400, /* seconds */
"stones_per_period": 10 /* count */
}
{
"time_control": "absolute",
"total_time" : 86400 /* seconds */
}
{
"time_control": "none"
}
Fischer: Clock begins with the initial_time
, after each move time_increment
is added up to a maximum of max_time
. Displays only need to show the current amount of time remaining, and optionally how much time will be added after each move.
Byo-Yomi: Clock begins with main_time
, after the main time is exhausted the clock is set to period_time
and the number of periods is decreased. If the player makes a move before the period is up, the clock is reset to period_time
. If the player exhausts the clock again and there are periods remaining, the clock is set to the period_time
again and the period counter is decreased. This continues until all periods have been used up, at which time a player loses on time.
DIsplays should show the base time, and the number of periods left, and optionally how long each period is.
Simple: Clocks begin with per_move
time. After each move the clock is reset to per_move
.
Canadian: Clocks being with main_time
and use this time until it is exhausted, at which point the clock is set to period_time
. Once in overtime, after each move the stones_per_period
counter is decreased until it reaches zero. Once this happens the clock is reset to period_time
and the stones_per_period
counter is reset. Displays should show the time remaining, and if the user is in overtime then the stones_per_move
counter should also be shown.
Absolute: The clock is set to total_time
and only decreases.
None: The simplest time control system, no clock is used or displayed.
clock
The clock
field contains the current state of the game clock and changes once per move.
{
black_time: {
period_time: 30, /* seconds */
periods: 5,
thinking_time: 1200 /* seconds */
},
white_time: {
period_time: 30, /* seconds */
periods: 5,
thinking_time: 360.90599999999995 /* seconds */
},
current_player: 12751,
last_move: 1416172879750, /* milliseconds since epoch */
now: 1416093910807, /* milliseconds since epoch */
start_mode: false, /* removed after a game begins */
/* These fields are used by the server, but are not
* necessary for clients to display clock information */
game_id: 989449,
black_player_id: 12751,
white_player_id: 1,
title: "friendly match",
paused_since: 1416093910, /* seconds since epoch */
expiration_delta: 1350000, /* milliseconds */
expiration: 1416174229750, /* milliseconds since epoch */
}
The black_time
and white_time
fields take on different forms depending on what time control system is being used
{
thinking_time: 12345, /* seconds */
/* Used by the server, unnecessary for the client */
skip_bonus: false
}
{
thinking_time: 12345, /* seconds */
periods : 3, /* periods left */
period_time : 30, /* seconds */
}
123455 /* this field is simply a number of seconds on the clock, will be 0 for the player not playing (this is subject to change, and may become number of seconds left on the clock for the last move instead of simply 0 in the future.) */
{
thinking_time : 12345, /* seconds */
/* applicable only when thinking_time == 0, however
* the fields will always exist */
moves_left : 20, /* moves left in this period */
block_time : 3600 /* seconds left in this period */
}
{
thinking_time: 12345 /* seconds */
}
# The `black_time` and `white_time` fields do not exist for the `none` time control.
Which is an ugly mess, and we apologize. If we had to do it over again, it'd be a lot different and notably cleaner (or at least that's what we'd like to think.)
Details on how to make use of these fields to turn the clock
into something you can show to your users will follow after the section on pause_control
pause_control
Games can be paused for several reasons, such as a player having clicked the pause button, the game has been paused for a system event, the game has been paused on the weekend (a correspondence game feature), users are on vacation, or because the game is in the stone removal phase.
If the game is paused the game object will contain the pause_control
field as an object and will contain one or more entries describing what is currently pausing the game. Note: if a game has been paused and unpaused, the pause_control
field may still be present in the game data object, but it will contain no fields.
{
"paused": {
"pauses_left": 4,
"pausing_player_id": 1
},
"weekend": true,
"system": true,
"vacation-{{player id}}": true,
"stone-removal": true
}
Putting it all together
Ultimately the goal of understanding the OGS time control system is so you can display a clock showing how much time is left before either a timeout or the start of your next byo-yomi / canadian period. Additionally you have to handle the start clock (which just tells the user how much time is left before the first move must be made), and when to display whether the game is paused or not. The following pseudo-code should help illustrate how to do this - at the end you'll be left with left
which is intended to be the number of milliseconds left before a timeout / period rollover.
if clock.start_mode:
Display time until clock.expiration
elif `pause_control` exists and is non-empty:
Display "Paused"
else:
let player_time be black_time or white_time appropriately
let now_delta = NOW() - clock.now
let base_time = clock.last_move + now_delta
if player_time is number:
# simple time
let time_left = player_time - NOW()
if player_time is object:
let left = base_time + player_time.thinking_time*1000 - now
if moves_left is a field in player_time: # canadian
if left < 0 or player_time.thinking_time == 0:
left = base_time + (player_time.thinking_time + \
player_time.block_time) * 1000 - now
if periods is a field in player_time: # byo yomi
if left < 0 or player_time.thinking_time == 0:
let period_offset = floor((-left / 1000) / player_time.period_time)
period_offset = max(0, period_offset)
while left < 0:
left += player_time.period_time * 1000
let periods_left = (player_time.periods - period_offset) - 1
if (player_time.periods - period_offset) - 1 < 0:
left = 0
# left now contains the number of milliseconds left
# on the clock before either a timeout or the start
# of the next byo-yomi or canadian period
# The number of milliseconds since Jan. 1, 1970 (Epoch)
Updated less than a minute ago