Start learning free

LESSON 414 OF 414 · PROJECTS: OBJECT-ORIENTED

Tic Tac Toe - A Two-Player Game Class in Rust

Practice

Build a TicTacToe class for two players, X and O, on a 3 by 3 board. One object is one game: it remembers the board and whose turn it is, lets the players take turns, and tells who has won.

The board: rows and columns are numbered 0, 1 and 2. Cell (0, 0) is the top-left corner, (0, 2) the top-right, (2, 0) the bottom-left and (2, 2) the bottom-right. A cell holds 'X', 'O' or '.' when it is still empty.

Build:

  • A new game. Rust has no constructors, so TicTacToe::new() returns a new game: board: Vec<Vec<char>> with three rows of three '.' and player: char set to 'X'. All nine cells are empty and X moves first.
  • fn move_pos(&mut self, r: usize, c: usize) (named move_pos because move is a Rust keyword) plays one turn at the given row and column (always between 0 and 2, so you do not need to check the range).
    • If the cell is not empty, ignore the call: nothing changes and it is still the same player's turn.
    • Otherwise put the current player's mark in the cell, then give the turn to the other player (X becomes O, O becomes X).
  • fn winner(&self) -> char returns the winner as a char: 'X', 'O' or '.'.
    • A player wins by having the same mark in all three cells of a row, a column, or one of the two diagonals ((0,0), (1,1), (2,2) and (0,2), (1,1), (2,0)). Three empty cells in a line are not a win.
    • If nobody has three in a row, return '.'. That covers a game still in progress, an empty board and a draw.

You start with this code, which plays six games and prints the result of each one:

fn mainFunction() {
    let mut game = TicTacToe::new();
    game.move_pos(0, 0);
    game.move_pos(1, 0);
    game.move_pos(0, 1);
    game.move_pos(1, 1);
    game.move_pos(0, 2);
    println!("{} wins!", game.winner());
    game = TicTacToe::new();
    game.move_pos(0, 0);
    game.move_pos(0, 1);
    game.move_pos(0, 0);
    game.move_pos(1, 0);
    game.move_pos(1, 1);
    game.move_pos(2, 2);
    game.move_pos(2, 1);
    println!("{} wins!", game.winner());
    game = TicTacToe::new();
    game.move_pos(0, 0);
    game.move_pos(0, 1);
    game.move_pos(1, 1);
    game.move_pos(0, 2);
    game.move_pos(2, 2);
    println!("{} wins!", game.winner());
    game = TicTacToe::new();
    game.move_pos(0, 2);
    game.move_pos(0, 0);
    game.move_pos(1, 1);
    game.move_pos(0, 1);
    game.move_pos(2, 0);
    println!("{} wins!", game.winner());
    game = TicTacToe::new();
    game.move_pos(0, 0);
    game.move_pos(0, 1);
    game.move_pos(0, 2);
    game.move_pos(1, 1);
    game.move_pos(1, 0);
    game.move_pos(1, 2);
    game.move_pos(2, 1);
    game.move_pos(2, 0);
    game.move_pos(2, 2);
    println!("No winner: {}", game.winner());
    game = TicTacToe::new();
    println!("No winner: {}", game.winner());
}

Games played: X fills the top row; then a game where X tries to play on a cell that is already taken (ignored, and still X's turn) and O wins the middle column; X on the main diagonal; X on the other diagonal; a full board with no three in a row; and a brand-new game.

Expected output:

X wins!
O wins!
X wins!
X wins!
No winner: .
No winner: .

There are eight winning lines: three rows, three columns and two diagonals. A loop over i from 0 to 2 can check row i and column i together; the two diagonals come after the loop. Skip a line whose first cell is empty.

Check for a taken cell first and leave the move method before touching the board or the turn. Only a successful move passes the turn to the other player.

struct TicTacToe {
    board: Vec<Vec<char>>,
    player: char,
}
impl TicTacToe {
    fn new() -> TicTacToe {
        TicTacToe {
            board: vec![vec!['.'; 3]; 3],
            player: 'X',
        }
    }
    fn move_pos(&mut self, r: usize, c: usize) {
        if self.board[r][c] != '.' {
            return;
        }
        self.board[r][c] = self.player;
        self.player = if self.player == 'X' { 'O' } else { 'X' };
    }
    fn winner(&self) -> char {
        for i in 0..3 {
            if self.board[i][0] != '.'
                && self.board[i][0] == self.board[i][1]
                && self.board[i][1] == self.board[i][2]
            {
                return self.board[i][0];
            }
            if self.board[0][i] != '.'
                && self.board[0][i] == self.board[1][i]
                && self.board[1][i] == self.board[2][i]
            {
                return self.board[0][i];
            }
        }
        if self.board[0][0] != '.'
            && self.board[0][0] == self.board[1][1]
            && self.board[1][1] == self.board[2][2]
        {
            return self.board[0][0];
        }
        if self.board[0][2] != '.'
            && self.board[0][2] == self.board[1][1]
            && self.board[1][1] == self.board[2][0]
        {
            return self.board[0][2];
        }
        '.'
    }
}

STEP 1 OF 19
    Ctrl + Enter to check

    Tic Tac Toe in Rust

    Keep going with RustAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free