Skip to main content

TicTacToe Tutorial

What you are makingโ€‹

TicTacToe Final

tip

Play the final version. Playing the game will help you envision what the underlying logic will look like.

Ask yourself:

  • How do you define what happens when a player moves?
  • How can you tell if a player won the game?

Setupโ€‹

Generate the tictactoe tutorial locally:

npx @urturn/runner init --tutorial first-game # answer prompts
cd first-game

The UI is provided for you. Your goal is to implement the underlying logic (room functions) which determine the resulting state after any event (e.g. player move, joins, etc.).

File/Folder structureโ€‹

game
โ””โ”€โ”€โ”€src # room logic goes here
โ”‚ โ”‚ main.js # room functions (e.g. onRoomStart, onPlayerMove, etc.)
| | util.js # helper functions (e.g. evaluateBoard)
โ”‚
โ””โ”€โ”€โ”€frontend # Tictactoe UI code lives here
| โ”‚ ...frontend files
| ...other files # not important for this tutorial
info

There are several // TODO: statements scattered across the files, src/main.js and src/util.js, to help guide you.

Defining how state changesโ€‹

Start up the game and play around.

npm run dev
info

The runner will immediately open a new window.

You will see a console that let's you easily debug/inspect the global state of your game.

You will also notice that if you add, remove a player or try to make a move on the tictactoe board, none of the state will change. So let's fix that!

Initializing stateโ€‹

We need to define what the initial state of the room looks like.

All state related to a room is held within the RoomState. We modify this object by returning the RoomStateResult.

tip

The runner will automatically hot reload changes when make changes and save

info

Now, modify the onRoomStart function.

Implement the TODO statements in onRoomStart in the file src/main.js, and then check your work with the solution.

src/main.js
function onRoomStart() {
/**
* TODO: define the fields:
* state.status (hint: use Status enum)
* state.plrIdToPlrMark
* state.plrMoveIndex
* state.board
* state.winner
*/
const state = {};
return { state };
}
tip

You should see your modifications to the initial state show up in the console!

Players joiningโ€‹

info

Modify the onPlayerJoin function to handle when a player joins.

Implement the TODO statements in onPlayerJoin, and then check your work with the solution.

tip

While implementing, try adding players. Try adding 3 players to see what happens.

src/main.js
function onPlayerJoin(player, roomState) {
const { players, state } = roomState;
if (players.length === 2) { // enough players to play the game
// TODO: modify state to start the game
return {
// TODO: tell UrTurn to NOT allow anymore players in this room
// TODO: return the modified state
};
}

// still waiting on another player so make no modifications
return {};
}

Players leavingโ€‹

info

Modify the onPlayerQuit function to handle when a player quits.

Implement the TODO statements in onPlayerQuit, and then check your work with the solution.

tip

While implementing, try adding players and then removing them. Does the console show the state you expect?

src/main.js
function onPlayerQuit(player, roomState) {
const { state, players } = roomState;

state.status = Status.EndGame;
if (players.length === 1) {
// TODO: when a player quits and there is another player, we should default the winner to
// be the remaining player
return {
// TODO: properly tell UrTurn the room is over
// (hint: modify finished, state fields)
};
}
return {
// TODO: when a player quits and there was no other player, there is no winner but we should
// properly tell UrTurn the room is over
// (hint: modify finished)
};
}

Helper functionsโ€‹

Before we approach handling player moves, let's implement the helper function evaluateBoard for determining if the game is finished and who won.

info

Implement the TODO statements in evaluateBoard, and then check your work with the solution.

Read the doc string for the function to understand what we should return!

caution

There are many ways to implement tictactoe evaluation logic, so don't be discouraged if your implementation doesn't look exactly like ours.

src/util.js
export const evaluateBoard = (board, plrIdToPlrMark, players) => {
/**
* TODO: check for a winner (hint: make sure the mark is not null)
* - check rows
* - check columns
* - check diagonals
*/

// TODO: check for tie and return correct result

// TODO: return default not finished
};

Player Movesโ€‹

info

Implement the TODO statements in onPlayerMove, and then check your work with the solution.

src/main.js
function onPlayerMove(player, move, roomState) {
const { state, players, logger } = roomState;
const { plrMoveIndex, plrIdToPlrMark } = state;
const { x, y } = move;
// TODO: validate player move and throw sensible error messages
// 1. what if a player tries to make a move when the game hasn't started?
// 2. what if a player makes a move and its not their turn?
// 3. what if a player makes a move on the board where there was already a mark?
// TODO: mark the board

// check if the game is finished
const result = evaluateBoard(state.board, plrIdToPlrMark, players);
if (result?.finished) {
// TODO: handle different cases when game is finished, using the values calculated from
// evaluateBoard() call
// hint: winner is Player type (not string)
return {
// TODO: include state modifications so UrTurn updates the state
// TODO: tell UrTurn that the room is finished, which lets UrTurn display the room correctly
};
}

// TODO: Set the plr to move to the next player (hint: update state.plrMoveIndex)
return { state };
}
tip

That's it! Now try adding two players and play around with it.

What's Next?โ€‹

You can deploy your game to UrTurn in a couple of minutes! Immediately play with random people, or create private rooms and play with close friends!

tip

Notice that you didn't have to worry about:

  • How to communicate between two players
  • How to manage room creation, matchmaking, and scaling

With Urturn you get to focus on your game logic, without worrying about unnecessary infrastructure problems.

If you think you are up for the challenge. Try making a more advanced game, Chess, or start making your own game!