DEV Community

Cover image for Let's Make a Tic-React-Toe game with React!
christine
christine

Posted on • Updated on

Let's Make a Tic-React-Toe game with React!

When I started on my React journey, I started with a course on Udemy, and then another one on YouTube, and then another one on Coursera - and let's be honest, there are only so many videos that you can watch and follow along with before you start feeling unchallenged.

Yes, learning to code is super cool, but trying to do it on your own is pretty terrifying. That's why I ditched the videos, courses, and lengthy tutorials (not including documentation) and took the first steps towards coding React on my own: by making my own tic-tac-toe game.

Tic-Tac-Toe games are already so overdone, but when you're learning a new programming language, it is good to try anything that will teach you the basics and more. Practice does make perfect, and if not perfect, definitely better.

Now, let me share my knowledge with you - future React Master! 😊

All explanations for the project are in the code itself via the comments, but if you get stuck or want to view my CSS file, use the images or custom fonts, check it out on my GitHub Repository.

Want to test it before you make it? Test it out on Heroku.

Pre Setup - Installing Packages

To complete this project exactly as (or however you want) I did, you will need to do the following in your preferred command line:

npx create-react-app tic-tac-react
npm i react-bootstrap bootstrap --save
cd tic-tac-react
Enter fullscreen mode Exit fullscreen mode

Step 1 - Initial Setup

Set up your App.JS file to contain the following component files. This being said, you can create a components folder in your ./src folder for the following files: Board.js, Game.js, Square.js, Header.js.

//App.js

import React from 'react';
import Game from './components/Game';
import Header from './components/Header';
import 'bootstrap/dist/css/bootstrap.min.css';

//Exports the App function to be used in index.js
function App() {
  return (
    <div className="App">
      <Header /> 
      <Game />
    </div>
  );
}
//Exports App Component to be used in index.js`
export default App;
Enter fullscreen mode Exit fullscreen mode

Step 2 - Creating The Squares

To be able to select values in the board that we will create later on, we first need to render the Squares which will contain the "x" or "o" values.

We do the following in the Square.js file:

//Square.js
import React from 'react';

//The Square component function a single <button>
function Square(props) {
    return (
      //onClick handler that will re-render the Square value whenever the <button> is clicked.
      <button className="square" onClick={props.onClick}>
        {/* this will call the value passed by the renderSquare component into the square, x or o*/}
        {props.value}
      </button>
    );
  }

//Exports Square Component to be used in app.js
export default Square;
Enter fullscreen mode Exit fullscreen mode

Step 3 - Creating The Board

Now the board will be our main interface to the game, which we will make functional in the next step. We now need to render the square buttons created to the board of the game.

We do the following in the Board.js file:

//Board.js
import React from 'react';
import Square from './Square';
import {Container, Row} from 'react-bootstrap';

//Board renders 9 squares to compose the tic-tac-toe board
class Board extends React.Component {

    //Pass the props to render the square number value to the board
      renderSquare(i) {
        /* this will pass a value (x, o, or null) to the Square */
        return (
          <Square 
            value={this.props.squares[i]}
            onClick={() => this.props.onClick(i)}
          />
        );
      }

    //Board rendering with square value init
    render() {
      //this will set the render the board
      return (
        <Container>
          <Row>
              <div>
              <div className="board-row">
                {this.renderSquare(0)}
                {this.renderSquare(1)}
                {this.renderSquare(2)}
              </div>
              <div className="board-row">
                {this.renderSquare(3)}
                {this.renderSquare(4)}
                {this.renderSquare(5)}
              </div>
              <div className="board-row">
                {this.renderSquare(6)}
                {this.renderSquare(7)}
                {this.renderSquare(8)}
              </div>
            </div>
          </Row>
        </Container>

      );
    };
  }

//Exports Board Component to be used in app.js
export default Board;
Enter fullscreen mode Exit fullscreen mode

Step 4 - Creating the Final Game

Now that we have created the board and the squares, we need to add the functionality to our actual game.

We do the following in the Game.js file:

//Game.js
import React from 'react';
import Board from './Board';
import {Button, Container, Row, Col} from 'react-bootstrap';

//The Game component renders a board which adds functionality to the game
class Game extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        //sets the Board's initial state to contain an array of 9 nulls on 9 squares
        history: [{
          squares: Array(9).fill(null),
        }],
        //Indicates which step we’re currently viewing.
        stepNumber: 0,
        //xIsNext (a boolean) will be flipped to determine which player goes next and the game’s state will be saved
        xIsNext: true,
      }     
    }

    //sets the state of the clicked square to an X value
    handleClick(i) {
      //ensures we don’t get stuck showing the same move after a new one has been made.
      const history = this.state.history.slice(0, this.state.stepNumber + 1);
      const current = history[history.length - 1];
      const squares = current.squares.slice();
      //returns early by ignoring a click if someone has won the game or if a Square is already filled:
      if (calculateWinner(squares) || squares[i]) {
        return;
      }
      squares[i] = this.state.xIsNext ? 'X' : 'O';
      //will either set the state of the clicked block to be x, or negate it to o
      this.setState ({
        history: history.concat([{
          squares: squares
        }]),
        stepNumber: history.length,
        xIsNext: !this.state.xIsNext
      });
    }

    //update that stepNumber to its current step and that the number of the step is even
    jumpTo(step) {
      this.setState({
        stepNumber: step,
        xIsNext: (step % 2) === 0,
      })
    }

    render() {
      // uses the most recent history entry to determine and display the game’s status
      const history = this.state.history;
      const current = history[this.state.stepNumber];
      const winner = calculateWinner(current.squares);

      //For each move in the tic-tac-toe game’s history, we create a list item <li> which contains a button <button>.
      const moves = history.map((step, move) => {
        //display the current move and history upon click
        const desc = move ? 
        'Return To Move #' + move :
        'Reset Game Board ';
        return (
          //we assign a key to the move list to make each move unique so it cannot be re-ordered, deleted or inserted
          <li key={move}>
             <Button className="btn-prim" size="lg" onClick={() => this.jumpTo(move)}>{desc}</Button>
          </li>
        );
      });

      let status;
      if (winner) {
        status = 'Congrats! The winner is: ' + winner;
      } else {
        status = 'Player Turn: ' + (this.state.xIsNext ? 'X' : 'O');
      }

      return (
        <Container>
          <Row className="col-12">
            <div className="game-space"></div>
          </Row>
          <Row className="col-12">
            <div className="game">
              <Col className="col-12 col-md-6 col-lg-6"> 
                    <div className="game-board">
                      <Board 
                        squares={current.squares} 
                        onClick={i => this.handleClick(i)}
                      />
                    </div>
              </Col>
              <Col className="col-12 col-md-6 col-lg-6"> 
              <div className="game-info">
                  <div className="game-status">{status}</div>
                  <ol className="game-moves">{moves}</ol>
                </div>
              </Col>   
          </div>
          </Row>
        </Container>    
      );
    }
  }

  //This will calculate the winner of the game after all possible moves are used
function calculateWinner(squares) {
    //possible winning moves for array of 9 squares
    const lines = [
      [0, 1, 2],
      [3, 4, 5],
      [6, 7, 8],
      [0, 3, 6],
      [1, 4, 7],
      [2, 5, 8],
      [0, 4, 8],
      [2, 4, 6],
    ];
    //Given an array of 9 squares, this function will check for a winner and return 'X', 'O', or null as appropriate.
    for (let i = 0; i < lines.length; i++) {
      const [a, b, c] = lines[i];
      if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
        return squares[a];
      }
    }
    return null;
  };

//Exports Game Component to be used in app.js
export default Game;
Enter fullscreen mode Exit fullscreen mode

Step 5 - Final Touches

Now you can add the Header file, which is completely optional, but I prefer to separate my layout files from my functional components.

Instead of putting this one in the /components folder, you can also create a new folder called /layout and store your Header.js in there.

//Header.js
import React from 'react';
import {Container, Row} from 'react-bootstrap';

//The Header component which will be main ui of game
function Header() {
    return (
        <Container>
            <Row>
                <div className="Header">
                    <h4 className="pre-title">WELCOME TO</h4>
                    <h1 className="game-title">Tic Tac React!</h1>
                </div>
            </Row>
            <Row> </Row>
        </Container>
    );
  }

//Exports Header Component to be used in app.js
export default Header;
Enter fullscreen mode Exit fullscreen mode

Step 6 - CSS and Deploy!

Good job, you've completed the tutorial. Now you can test your game with the following command:

npm start
Enter fullscreen mode Exit fullscreen mode

Remember to add some CSS or styling to your application otherwise it's a definite uggo. You can see what I did on my CSS files in my GitHub repository listed above.

Your final project should look something like this:

image

As I said, I'm no React expert, but I hope I could show you something new/different today! Let me know, or teach me something new, I'd love to hear from you in the comments.😊

Top comments (0)