This is a simple Tic-Tac-Toe (noughts and crosses) game built using React. Follow the instructions below to set up and run the project on your local machine.
Before you begin, make sure you have the following installed on your system:
- Node.js: Download and install from Node.js.
- npm: This is included with Node.js, so it should be installed automatically.
-
Clone this repository.
-
Install the dependencies:
npm install
-
Start the local server:
npm start
Follow the prompts to view the code running in a browser. This will typically open a new browser window with the game running at
http://localhost:3000
.
-
App.js:
- This file creates the main component of the application. In React, a component is a piece of reusable code that represents a part of a user interface. Components are used to render, manage, and update the UI elements in your application.
-
styles.css:
- This file defines the styles for your React app. The first two CSS selectors (
*
andbody
) define the style of large parts of your app while the.square
selector defines the style of any component where theclassName
property is set tosquare
. In your code, that would match the button from yourSquare
component.
- This file defines the styles for your React app. The first two CSS selectors (
-
index.js:
- This file serves as the bridge between the component you created in
App.js
and the web browser.
import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import "./styles.css"; import App from "./App";
- Lines 1-5 bring all the necessary pieces together: React, React’s library to talk to web browsers (React DOM), the styles for your components, and the component you created in
App.js
. - The remainder of the file brings all the pieces together and injects the final product into
index.html
in the public folder.
- This file serves as the bridge between the component you created in
Here is a detailed look at the main parts of the code:
import { useState } from "react";
function Square({ value, onSquareClick }) {
return (
<button className="square" onClick={onSquareClick}>
{value}
</button>
);
}
function Board({ xIsNext, squares, onPlay }) {
function handleClick(i) {
if (calculateWinner(squares) || squares[i]) {
return;
}
const nextSquares = squares.slice();
if (xIsNext) {
nextSquares[i] = "X";
} else {
nextSquares[i] = "O";
}
onPlay(nextSquares);
}
const winner = calculateWinner(squares);
let status;
if (winner) {
status = "Winner: " + winner;
} else {
status = "Next player: " + (xIsNext ? "X" : "O");
}
return (
<>
<div className="status">{status}</div>
<div className="board-row">
<Square value={squares[0]} onSquareClick={() => handleClick(0)} />
<Square value={squares[1]} onSquareClick={() => handleClick(1)} />
<Square value={squares[2]} onSquareClick={() => handleClick(2)} />
</div>
<div className="board-row">
<Square value={squares[3]} onSquareClick={() => handleClick(3)} />
<Square value={squares[4]} onSquareClick={() => handleClick(4)} />
<Square value={squares[5]} onSquareClick={() => handleClick(5)} />
</div>
<div className="board-row">
<Square value={squares[6]} onSquareClick={() => handleClick(6)} />
<Square value={squares[7]} onSquareClick={() => handleClick(7)} />
<Square value={squares[8]} onSquareClick={() => handleClick(8)} />
</div>
</>
);
}
export default function Game() {
const [history, setHistory] = useState([Array(9).fill(null)]);
const [currentMove, setCurrentMove] = useState(0);
const xIsNext = currentMove % 2 === 0;
const currentSquares = history[currentMove];
function handlePlay(nextSquares) {
const nextHistory = [...history.slice(0, currentMove + 1), nextSquares];
setHistory(nextHistory);
setCurrentMove(nextHistory.length - 1);
}
function jumpTo(nextMove) {
setCurrentMove(nextMove);
}
const moves = history.map((squares, move) => {
let description;
if (move > 0) {
description = "Go to move #" + move;
} else {
description = "Go to game start";
}
return (
<li key={move}>
<button onClick={() => jumpTo(move)}>{description}</button>
</li>
);
});
return (
<div className="game">
<div className="game-board">
<Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} />
</div>
<div className="game-info">
<ol>{moves}</ol>
</div>
</div>
);
}
function calculateWinner(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],
];
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;
}
Here are some ideas for improving the Tic-Tac-Toe game, listed in order of increasing difficulty:
- Current Move Indicator: For the current move only, show “You are at move #…” instead of a button.
- Dynamic Board Creation: Rewrite the
Board
component to use loops to create the squares instead of hardcoding them. - Move Sorting: Add a toggle button that lets you sort the moves in either ascending or descending order.
- Winning Highlight: When someone wins, highlight the three squares that caused the win (and when no one wins, display a message about the result being a draw).
- Move Location: Display the location for each move in the format (row, col) in the move history list.