2
\$\begingroup\$

I wrote this tic tac toe game in react. I am learning react now. Feedback is welcome. I deliberately used single file, to focus on React and not other aspects.

<!-- 3x3 Tic tac toe implementation in React JS. -->

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8"/>
    <title>Tic Tac Toe</title>

    <!-- Import react -->
    <script src="https://unpkg.com/react@latest/dist/react.js"></script>
    <script src="https://unpkg.com/react-dom@latest/dist/react-dom.js"></script>

    <!-- Import Babel -->
    <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>

</head>
<body>
<div id="root"></div>
<script type="text/babel">



    /*
     * 3x3 Tic tac toe game component.
     *
     * */
    class TicTacToe extends React.Component {

        // Constructor for our component.
        constructor(props) {
            // Call parent constructor
            super(props);

            // Initially board is in uninitialized state.
            this.state = {
                board: [null, null, null, null, null, null, null, null, null]
            };

            // Bind handlers
            this.handleClick = this.handleClick.bind(this);
            this.restart = this.restart.bind(this);

            // Whose turn is it?
            this.turn = 0;

            // Indicates if game is finished or not.
            this.gameFinished = false;
        }

        // Handler when user clicks an image on the board.
        handleClick(id) {

            if (this.gameFinished)
                return;

            // there is something already on this cell, quit.
            if (this.state.board[id])
                return;

            // Copy state temporarily
            var clone = JSON.parse(JSON.stringify(this.state.board));

            // Store a move into the state.
            if (this.turn == 0) {
                clone[id] = 'X';
                this.setState({board: clone});
                this.turn = 1;
            }
            else {
                clone[id] = 'O';
                this.setState({board: clone});
                this.turn = 0;
            }


        }

        // Function to check if there is a winner.
        checkWinner() {

            var i = 0;

            // Horizontal check
            for(i = 0; i<9; i+=3)
            {
                if (this.state.board[i] == this.state.board[i+1] && this.state.board[i+1] == this.state.board[i+2] && this.state.board[i])
                    return this.state.board[i];

            }

            // Vertical check
            for(i = 0; i<3; i++)
            {
                if (this.state.board[i] == this.state.board[i+3] && this.state.board[i+3] == this.state.board[i+6] && this.state.board[i])
                   return this.state.board[i];

            }

            // diagonal check
            if (this.state.board[0] == this.state.board[4] && this.state.board[4] == this.state.board[8] && this.state.board[0])
                return this.state.board[0];
            if (this.state.board[2] == this.state.board[4] && this.state.board[4] == this.state.board[6] && this.state.board[2])
                return this.state.board[2]

            return -1;

        }

        // Restart the game
        restart()
        {
            // Clean board
            this.setState({
                board: [null, null, null, null, null, null, null, null, null]
            });

            this.turn = 0;
            this.gameFinished = false;
        }

        // Render
        render() {
            const that = this;
            var status = "Status: ";
            var winner;


            // Render will get called, when state is changed, so here we immediately check if there is a winner or not.
            winner = this.checkWinner()
            if (winner != -1) {
                status = "Status: we have a winner " + winner;
                this.gameFinished = true;
            }


            return (
                    <div>
                        <h2>{status}</h2>
                        {this.state.board.map(function (currentVal, index) {

                            // Draw each game cell.
                            return <BoardCell which={currentVal} id={index} click={that.handleClick}
                                              key={index}></BoardCell>


                        })}
                        <button onClick={this.restart}>Restart</button>
                    </div>
            );
        }

    };

    /*
     * Represents each cell in the game.
     * */
    class BoardCell extends React.Component {
        constructor(props) {
            // Call parent constructor
            super(props);

            this.click = this.click.bind(this);
        }

        click(e) {
            this.props.click(e.target.id);
        }


        render() {

            const which = this.props.which;
            const dimensions = {
                width: 150,
                height: 120
            };
            const id = this.props.id;

            if ((id + 1) % 3 == 0) {
                // If we are at the edge of right side, append also a new line
                return(
                        <span>
                            <img src={which == "X" ? "./resources/x.png" : which == "O" ? "./resources/o.png" : './resources/def.png'}
                                 style={dimensions} onClick={this.click} id={id}/>
                            <br/>
                        </span>
                );
            }
            else {
                // Just return image with corresponding data
                return (<img
                        src={which == "X" ? "./resources/x.png" : which == "O" ? "./resources/o.png" : './resources/def.png'}
                        style={dimensions} onClick={this.click} id={id}/>);
            }

        }

    };



    ReactDOM.render(
            <TicTacToe/>,
        document.getElementById('root')
    );

</script>
</body>
</html>
\$\endgroup\$

2 Answers 2

1
\$\begingroup\$

These are the improvements that came to my mind:

  1. Use component state to store turn and gameFinished variables so your component or it's sub components re-render when turn changes.
  2. To avoid cloning the state completely use awesome immutable.js library it is much faster and cleaner.
  3. You can use arrow functions () => in your map statement so you don't need const that = this anymore
  4. You don't need to set id attribute in your image element. In the BoardCell click method, you can simply read id from this.props.

click(e) {
    this.props.click(this.props.id);
}
\$\endgroup\$
1
\$\begingroup\$

These are the improvements that came to my mind:

Use component state to store turn and gameFinished variables so your component or it's sub components re-render when turn changes. To avoid cloning the state completely use awesome immutable.js library it is much faster and cleaner. You can use arrow functions () => in your map statement so you don't need const that = this anymore You don't need to set id attribute in your image element. In the BoardCell click method, you can simply read id from this.props.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy