Skip to main content

Flow of a Simple Game

TicTacToe

Let's walk through a simple TicTacToe game that you made:

1. Player Billy clicks Play

billy clicks play

UrTurn calls your room function implementation of onRoomStart to initialize the room state.

{
"joinable": true, // defaults to true, (when set to false, UrTurn prevents any new players from joining the room)
"finished": false, // defaults to false, (when set to true, UrTurn prevents any new moves from being made)
"state": { // this field can be any JSON object that you define. This was provided in the roomStateResult by the room function above.
"status": "preGame",
"board": [
[
null,
null,
null
],
[
null,
null,
null
],
[
null,
null,
null
]
],
"winner": null,
},
"version": 0, // metadata controlled by UrTurn; as each new `RoomState` gets created, this gets incremented by 1
"players": [], // metadata controlled by UrTurn; when a player joins the room, they are added here to the list. When they quit the room, they are removed.
}

UrTurn calls onPlayerJoin function with Billy player object and the previous roomState created earlier.

{
"joinable": true, // room should still let players join because there are not enough players to play tictactoe yet
"finished": false,
"state": {
"status": "preGame",
"board": [
[
null,
null,
null
],
[
null,
null,
null
],
[
null,
null,
null
]
],
"winner": null
},
"version": 1, // incremented because our room function successfully modified the state
"players": [ // new player is in the player list
{
"id": "id_0",
"username": "billy"
}
]
}

Updates are propagated to clients by sending an event to client.events.on('stateChanged') so your game frontend can update the view for the player.

billy waiting

2. Player Sarah clicks Play

UrTurn matchmaking system puts Sarah to the same room as Billy.

The same function onPlayerJoin is called with Sarah player object and the previous roomState, which produces the result (changes are highlighted):

{
"joinable": false, // no longer joinable as we have enough players!
"finished": false,
"state": {
"status": "inGame", // game is now in game and we can start playing!
"board": [
[
null,
null,
null
],
[
null,
null,
null
],
[
null,
null,
null
]
],
"winner": null,
"plrMoveIndex": 0
},
"version": 2,
"players": [
{
"id": "id_0",
"username": "billy"
},
{ // new player "sarah", added by UrTurn runner
"id": "id_1",
"username": "sarah"
}
]
}

Updates are propagated to all clients:

sarah joins

3. Billy puts X in top left corner

billy moves

Your frontend calls client.makeMove({ x: 0, y: 0 }) whenever it detected billy clicking a button on the board

info

The client.makeMove function takes any move JSON!

UrTurn calls onPlayerMove function to handle the arbitrary move:

Resulting roomState (changes are highlighted):

{
"joinable": false,
"finished": false,
"state": {
"status": "inGame",
"board": [
[
"X", // Billy’s move!
null,
null
],
[
null,
null,
null
],
[
null,
null,
null
]
],
"winner": null,
"plrMoveIndex": 1 // next move is Sarah’s
},
"version": 3,
"players": [
{
"id": "id_0",
"username": "billy"
},
{
"id": "id_1",
"username": "sarah"
}
]
}

4. Room functions are pure functions

Notice how all of the implemented room functions (onRoomStart, onPlayerJoin, etc.) takes in the current roomState and several other arguments and returns a roomStateResult.

Data should feel like a natural flow of transformations throughout time: pure functions diagram

This makes it easier to understand, debug, and test your room functions. More on pure functions.

Getting the hang of it?

tip

You just write code for how state changes based on various room events - player joins, quits, makes move, etc.

UrTurn takes care of the rest.

Try implementing the entire TicTacToe game and deploying it to production so anyone can play it!