This page contains information for developers that want to incorporate the gameclock module into their program.
The gameclock is a timer that supports an arbitrary number of players. It is licensed under the MIT license. The gameclock module is written for Preact, but should work with React. The demo, however, has been written specifically for preact.
Assuming you must have npm, preact, and your bundler set up, use npm to install the module:
$ npm install @dbosst/gameclock
To use this module, require it as follows:
const {h} = require('preact')
const {gameclock} = require('@dbosst/gameclock')
Define variables with at least the minimum required props: clockMode
, mode
, numMoves
and initialTime
You can then load the component (preact style) in your component's render():
h(gameclock, {
clockMode: clockMode,
mode: mode,
numMoves: numMoves,
initialTime: initialTime,
})
Please make sure to include the css/gameclock.css
file in your HTML:
<link rel="stylesheet" href="path/to/gameclock-module/css/gameclock.css"/>
There are two components to the module: the main module gameclock
and its component playerclock
. gameclock
is responsible for loading one playerclock
for each player, and is mainly responsible for controlling which playerclock
is active. It also calculates the (estimated) player clock display width, as well as passes information to clocks in the case of manually adjusting a playerclock
, or, as in the case of hourglass mode, passing information between playerclock
s.
Will display any time up to 9007199254740992 seconds (2^52 seconds, or ~286 million years)
Preact has a limitation of not being able to return document fragments (at least as of yet), so at the moment all player clocks are bundled inside a div in gameclock. If this changes, in the future we may be able to simply return an array of document fragments (each containing a player clock). For now this is not possible, and to separate them requires a bit of javascript 'hacking' (see the demo's split player clock's code).
Generally, you must reset the clock after initializing it to make certain the time settings are correct. See the section on Initializing the Clock.
The clock displays in order of:
{playerText} ({period/phase #}) {period/phase move #} {delay time} {phase/main/period time}
The time displayed is in hours:minutes:seconds{:fractional-seconds}
where fractional seconds means the seconds can be shown with up to 4 decimal places. The time shown is always truncated to however many decimal places shown (not rounded). So, 0.04959 seconds will shown as 0
or 0.0
or 0.04
or 0.049
or 0.0495
for 0-4 decimal places shown, respectively.
Any of the fields except the time can be configured to displayed or not. The delay time is only (and always) shown in the 'delay' clockMode
Before initializing the clock, you should first pause the clock in case it is running. Then initialize the clock by setting the mode
to 'init' and the numMoves
to 0
. After initializing the clock you should then reset the clock by setting the mode
to 'reset'; the reason for this is if the initialTime
has not changed since the last initialization, the gameclock won't be able to distinguish that it has been initialized a second time. In general, you should always follow an 'init' with a 'reset'; see the next section for an example of the exception of this rule.
The mode
'init' and 'reset' behave slightly differently. The mode
'init' allows the parent component of gameclock to pass initialTime
without needing to differentiate whether initialTime
changed. For example, if the user is given an option to change the time setup of an already initialized clock, the parent component can first pause and then 'init' the gameclock with initialTime
without checking whether initialTime
has changed: if it hasn't changed the gameclock won't reset the clock, but if it has changed the gameclock will reset the clock. The mode
'reset' is similar to a forced update where we reset the clock to whatever the initialTime
is currently set to, whether initialTime
has changed or not.
The gameclock and each playerclock is assigned multiple classnames for css styling. The text in braces refers to the corresponding prop given to gameclock props.
Each player clock is given the following class names depending on the state:
- gameclock_{gameClockID}
- playerclock
- playerclock_{gameClockID}_{playerID}
- expired
- running
- infinitetime
- paused
- inactive
The game clock is given the following classnames:
- gameclock
- gameclock_{gameClockID}
Additionally the gameclock div has the following attributes:
- 'id': gameclockid_{gameClockID}
- 'data-numActive': the number of active clocks (not expired)
- 'data-numClocks': the total number of player clocks (expired or not)
There are many variables that can be overridden to customize the look. For instance, changing the .gameclock variable --gameclock-display-direction
to row
will change the clocks from vertically stacked to horizontally stacked.
Please see the gameclock.css for ideas.
A note on graphemes: the gameclock tries to keep the all the player clock's the same size by calculating how big the timer should be (this can't be done for increment modes since an infinite amount of time can be theoretically gained). This includes calculating the width of the playerText
which is done using the npm package grapheme-splitter
for better international support. However, for it to work properly, the "width" of what we would a character or, more properly, a grapheme, changes depending on which font you use. So if you wish to use playerText that has graphemes that are multiple bytes you should make sure you override the font-face chosen with a fixed-width font that displays each grapheme as one actual character (i.e. one that grapheme-splitter will count correctly).
The following list contains all props for the gameclock
component, grouped according to function (as in the demo). Bold indicates a required prop for any clockMode. The accepted types and values are listed after each :
.
- adjustAction:
String
- see how to in the adjusting the clock during play section
- adjustEventID:
Integer
- adjustPlayerID:
String
- adjustVal:
Float
orInteger
- dispInfoNumPeriods:
Boolean
- dispInfoPeriodMoves:
Boolean
- dispInfoPlayerText:
Boolean
- dispCountElapsedMainTime:
Boolean
- dispCountElapsedNumPeriods:
Boolean
- dispCountElapsedPeriodMoves:
Boolean
- dispCountElapsedPeriodTime:
Boolean
- dispFormatMainTimeFSNumDigits:
Integer
- dispFormatMainTimeFSLastNumSecs:
Integer
0
to disable, otherwise any positive integer (normally, like 5, 10, 20, 30, or 60)
- dispFormatMainTimeFSUpdateInterval:
Float
1
- if dispFormatMainTimeFSNumDigits is0
0.1
suggested if dispFormatMainTimeFSNumDigits is1
0.02
suggested if dispFormatMainTimeFSNumDigits is2
,3
, or4
, because normally 60 fps is the limit
- dispFormatPeriodTimeFSNumDigits:
Integer
- dispFormatPeriodTimeFSLastNumSecs:
Integer
- dispFormatPeriodTimeFSUpdateInterval:
Float
- dispOnExpired:
String
ornull
- Any short string, such as 'OT' or 'Expired'
null
- if you want to display the time instead of a string (in this case you need css or callback events to know the expired state of a clock)
- clockMode:
String
- See the clockMode prop section
- gameClockID:
String
- a short, unique string for the css (in case for whatever reason, you need multiple game clocks)
- minActiveClocks:
Integer
- usually set to the number of player clocks you have (which is 2 normally, as in go & chess). This number determines how many player clocks must not be in the expired state for other clocks to continue run, i.e. if you have 10 players and you want to stop all the clocks once you have a total of three player's clock expire, this would be set to 8.
- mode:
String
- 'init'
- set to init when all other options have been set
- 'pause'
- 'resume'
- 'reset'
- to reset all player clocks to their initial state and time
- 'init'
- numMoves:
Integer
0
- start with0
and increment by 1 to change to the next active player
- initialTime:
Array
ofObject
- See the initialTime prop section
- handleAdjust:
Function
reference- Called after a manual adjustment has been made. See the callback events section. With the exception of this event and
handleUpdated
, other clock events are suppressed when solely adjusting the clock.
- Called after a manual adjustment has been made. See the callback events section. With the exception of this event and
- handleElapsedMainTime:
Function
reference- Called when the main time ends, and when a phase ends in
clockMode
'incrementBefore', 'incrementAfter' and 'delay' - this is when 'mainTime' 'secondaryTime' or 'tertiaryTime' ends.
- Called when the main time ends, and when a phase ends in
- handleElapsedPeriod:
Function
reference- Called when: in
clockMode
'byo-yomi' when a period ends, orclockmode
'delay' when the delay ends.
- Called when: in
- handleInit:
Function
reference- called automatically whenever player clocks are initialized (from changing the initialTime or other core settings)
- handleMadeMove:
Function
reference - handlePaused:
Function
reference- Called whenever the playerclock is paused (either manually or from changing turns) (may fire more than once)
- handlePlayerClockExpired:
Function
reference - handleReset:
Function
reference- Called when a reset is performed, which is when both
numMoves
is set to 0 andmode
is set to 'reset'
- Called when a reset is performed, which is when both
- handleResumed:
Function
reference- Called whenever the playerclock is resumed (either manually or from changing turns)
- handleTenCount:
Function
reference- Called when there becomes 10 seconds (or less) left in the phase/main time or period time. If a
periodTime
or phase/main time is set to say 5 seconds, it will still be called when that period/phase starts with 5 seconds.
- Called when there becomes 10 seconds (or less) left in the phase/main time or period time. If a
- handleUpdated:
Function
reference- Only useful for the split player clocks hack (called whenever a playerclock is updated)
In only the increment/delay modes, phase refers to in the prop initialTime
either the mainTime
, secondaryTime
, or tertiaryTime
(i.e., three different sets of main time)
- 'absolutePerPlayer' - Each player is given
mainTime
before their time expires. - 'byo-yomi' - Each player is given main time. When the main time runs out they enter byo-yomi/overtime, where they have
periodTime
to makeperiodMoves
before the period elapses, of which they havenumPeriods
before their time expires. - 'delay' - Each player is given main time and optionally secondary time and optionally a tertiary time (three phases of time). When each player's turn comes up they have
periodTime
before their clock's (phase) time starts running. If the player makesperiodMoves
for that phase then the time left for that phase's main time is spilled over (added) to the next phases available time. When one phases' time runs out, the clock advances to the next phase. The clock expires when all phases' time expire. - 'incrementAfter' - Immediately after each player makes a move,
periodTime
is added to that player's clock.periodTime
is also added to each player's clock on initialization/reset. Otherwise it operates similarly to 'delay'. - 'incrementBefore' - Immediately before each player's turn,
periodTime
is added to that player's clock. Otherwise it operates similarly to 'delay'. - 'hourglass' - Each player starts with
mainTime
and the active time elapsed during that player's turn is then given to the next player. For example, if you use five seconds of time to make a move, your opponent will gain five seconds thinking time for the next move. - 'wordgame' - Each player starts with
mainTime
and when the time runs out, the clock does not expire, but instead starts counting negative time.
initialTime
is an Array
of Object
where each Object
will hold the time settings for each player's clock. To keep the number of props small, byo-yomi and the increment/delay modes share props.
- playerID:
String
- a short string, all lowercase, unique to each player (should have no spaces or special characters since it will be used as in the CSS classnames), i.e. 'b', or 'w', or '1' or '2' -- if its a number it still must be represented as a string. - playerText:
String
ornull
- an optional short string to display at the beginning of each player clock display, i.e. 'Black' or 'White' - mainTime:
Float
the main time in seconds for all modes, set to 0 if you do not want a main time, i.e. for byo-yomi you may not want any main time. - mainMoves:
Integer
forclockMode
delay and increment, the number of moves that must be made within the allotted time before advancing to the secondaryTime phase. If all remaining moves must be made within this phase (this is the last phase) or if you don't need this since you are using a different clockMode set this to0
. - secondaryTime:
Float
the second phase time in seconds, only used forclockMode
delay and increment; set to0
if you do not want a second phase of time or are using a differentclockMode
. - secondaryMoves:
Integer
forclockMode
delay and increment, the number of moves that must be made within the allotted time before advancing to the tertiaryTime phase. If all remaining moves must be made within this phase (this is the last phase) or if you don't need this since you are using a different clockMode set this to0
. - tertiaryTime:
Float
the second phase time in seconds, only used forclockMode
delay and increment; set to0
if you do not want a second phase of time or are using a differentclockMode
. - tertiaryMoves:
Integer
forclockMode
delay and increment, the number of moves that must be made within the allotted time before advancing to the tertiaryTime phase. If all remaining moves must be made within this phase (this is the last phase) or if you don't need this since you are using a different clockMode set this to0
. - numPeriods:
Integer
forclockMode
byo-yomi only. The number of byo-yomi periods that must elapse before the player's time expires. Set this to '0' if you are not using byo-yomi. - periodTime:
Float
- for
clockMode
byo-yomi, this corresponds to the amount of time in each period - for
clockMode
delay and increment, this corresponds to the bonus time given for every move from the first move.
- for
- periodMoves:
Integer
- for
clockMode
byo-yomi, this corresponds to the # of moves that must be made in the current period before the elapsedPeriodTime is reset. For real byo-yomi this is usually 1. For canadian overtime this can be any positive number, i.e. 10, where you must make 10 moves within the periodTime for the elapsed period time to reset to 0. - for
clockMode
delay and increment, this corresponds to the bonus time given for every move from the first move.
- for
To make a move (change the player's turn) you simply increment the numMoves
property by one.
You can also change the current player's turn the same way by pausing (setting mode
to 'paused'
With the exception of handleAdjust
(which additionally returns an adjustEventID
) and handleUpdated
(which returns nothing), all other events return an object with the following keys:
- 'playerID' - a
String
that refers to symbolically the player whose turn just ended from making a move, corresponding to theplayerID
for the player's clock ininitialTime
- 'clock' - an
Object
that is the playerclock state, see the clock state section - 'activePlayers' - an
Array
ofString
. The string is a correspondingplayerID
. Only contains clocks that are not expired (when a clock is expired, it is removed from the array). The order shows the turns for each player clock, i.e., the first in the array is either that player's current turn or will soon be their turn, and depends on the event type and player clock states:- for
madeMove
, which fires after the player who made a move's clock stops, usually theplayerID
will appear last in the array and the first will be the player whose turn is immediately next - after the time expires, i.e. for
handleExpired
, the first will be the player whose turn would be next (if the game has not ended)
- for
Most events return an object called clock
that is a single playerclock's state. Any time that elapses never includes time where the clocks are paused (such as with a manual pause of the clock).
- didTenCount:
Boolean
- Used only internally to track when the ten count has elapsed.
- elapsedMainTime:
Float
- The # of seconds elapsed for the current main time or the current phase for
clockMode
delay and increment.
- The # of seconds elapsed for the current main time or the current phase for
- elapsedMoveTime:
Float
- The # of seconds elapsed since the start of a player's turn and is reset ONLY if the player makes a move when their clock is running. This is not used in anyway to derive the clock state (information for events only). In other words, for example, if player Black's clock has been running for 5 minutes, the gameclock is then manually paused, then the
numMoves
is incremented to switch to the player White, the gameclock is manually resumed, and then White makes a move and so it becomes Black's turn again, the elapsedMoveTime continues to increment from where it left off for Black at 5 minutes. If you want different behavior, then you need to manually adjust the clock state, see the Adjusting the clock during play section.
- The # of seconds elapsed since the start of a player's turn and is reset ONLY if the player makes a move when their clock is running. This is not used in anyway to derive the clock state (information for events only). In other words, for example, if player Black's clock has been running for 5 minutes, the gameclock is then manually paused, then the
- elapsedNumPeriods:
Integer
- For
clockMode
byo-yomi it is the total number of periods elapsed. ForclockMode
delay or increment
- For
- elapsedTotalTime:
Float
- The total time elapsed for the current player clock (including all periods/phases/delays/increments etc.), i.e. the time the player spent thinking. This is not used in anyway to derive the clock state (information for events only).
- elapsedPeriodMoves:
Integer
- The # of moves made by a single player in the current period or phase. Note: in increment and delay
clockMode
when this # exceeds theinitialTime
periodMoves
it will only show on the clock display up to theinitialTime
periodMoves
and not the actual, since in these modes for chess we ostensibly only care if we reach the required number of moves for that phase, not if we go beyond it for the last phase.
- The # of moves made by a single player in the current period or phase. Note: in increment and delay
- elapsedPeriodTime:
Float
- The time elapsed in seconds since the start of the period. More time can be given for the current period than is available from the initialTime settings by making the value more negative.
- needIncrementBefore:
Boolean
- Used only internally by the playerclock for increment mode - not meaningful outside.
- resetPeriod:
Boolean
- Used only internally by the playerclock for making sure time is adjusted properly after making a move - not meaningful outside.
- state:
String
- 'running'
- set the clock is currently counting down time
- 'paused'
- set when the clock is manually paused or it is no longer the player's turn
- 'init'
- set after correctly setting the gameclock props and it's
mode
to 'init'
- set after correctly setting the gameclock props and it's
- 'preinit'
- the default when the clock has not yet been initialized (i.e. after mounting the component)
- 'running'
You can adjust any player clock state during play, by first pausing and then simultaneously setting the following props. When you receive the callback through handleAdjust
corresponding to the eventID
you are waiting for you can then safely resume. Note: other than handleAdjust
and handleUpdated
, other clock events suppressed during when the clock is adjusted. Note also that didTenCount
is reset whenever adjusting the clock.
- adjustAction - setElapsed... sets the value to
adjustVal
and incrElapsed... adds the current value byadjustVal
. It's possible to have negative values for the time. See the clock state section for info about what these mean.- 'incrElapsedMainTime'
- 'incrElapsedMoveTime'
- 'incrElapsedNumPeriods'
- 'incrElapsedPeriodMoves'
- 'incrElapsedPeriodTime'
- 'incrElapsedTotalTime'
- 'setElapsedMainTime'
- 'setElapsedMoveTime'
- 'setElapsedNumPeriods'
- 'setElapsedPeriodMoves'
- 'setElapsedPeriodTime'
- 'setElapsedTotalTime'
- adjustEventID - a counter which must be set initially to
0
for the first adjustment, and then incremented by 1 for every subsequent adjustment. - adjustPlayerID - the string corresponding to player ID's player clock you want to adjust
- adjustVal: for Time a
Float
seconds (that can be negative to give the player time) and anInteger
for numMoves, or numPeriods (which for delay mode corresponds to which phase we are in)