To build a complete Tic-Tac-Toe game, we need to create an interactive web application that allows two players to take turns marking spaces on a 3x3 grid. Let's break down what we need to accomplish:
Just like any classic board game, our Tic-Tac-Toe implementation needs clear rules and structure:
Input:
Players interact with the game through clicks on:
Output:
The game needs to provide feedback through:
Let's examine each component of our solution:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tic-Tac-Toe</title>
<link rel="stylesheet" href="./main.css">
<script src="./tic-tac-toe.js"></script>
</head>
<body>
<div class="game-container">
<h1 id="game-status"></h1>
<div class="game-board">
<div class="square" id="square-0"></div>
<div class="square" id="square-1"></div>
<div class="square" id="square-2"></div>
<div class="square" id="square-3"></div>
<div class="square" id="square-4"></div>
<div class="square" id="square-5"></div>
<div class="square" id="square-6"></div>
<div class="square" id="square-7"></div>
<div class="square" id="square-8"></div>
</div>
<div class="button-container">
<button id="new-game" disabled>New Game</button>
<button id="give-up">Give Up</button>
</div>
</div>
</body>
</html>
The HTML structure creates a clear hierarchy of elements. Think of it like laying out the physical components of a board game - we need the board itself, spaces for players to mark, and control buttons to manage the game. Each element has a specific ID or class name that we'll use to connect it with our JavaScript code.
/* Main container styling */
.game-container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
text-align: center;
}
/* Game board grid */
.game-board {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
margin: 20px auto;
max-width: 300px;
}
/* Individual squares */
.square {
background-color: white;
border: 2px solid #333;
aspect-ratio: 1;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
/* Button styling */
.button-container {
display: flex;
justify-content: space-between;
max-width: 300px;
margin: 20px auto;
}
button {
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 5px;
transition: background-color 0.3s;
}
The CSS implementation uses modern layout techniques to create a responsive and visually appealing game board. CSS Grid is perfect for the game board because it naturally creates a 3x3 grid with equal-sized cells. The aspect-ratio property ensures our squares remain perfectly square regardless of screen size.
The JavaScript implementation uses a class-based approach to organize our game logic. Let's break down the key components:
class TicTacToe {
constructor() {
// Game state
this.currentPlayer = 'X';
this.board = Array(9).fill(null);
this.gameStatus = 'ongoing';
this.winner = null;
// Initialize game
this.loadGame();
this.setupEventListeners();
}
}
The constructor sets up our initial game state. Think of this like setting up a physical board game - we need to track whose turn it is, what moves have been made, and whether someone has won.
handleSquareClick(index) {
if (this.gameStatus !== 'ongoing' || this.board[index]) return;
this.board[index] = this.currentPlayer;
this.updateSquareDisplay(index);
this.checkGameStatus();
this.currentPlayer = this.currentPlayer === 'X' ? 'O' : 'X';
this.saveGame();
}
This method handles player moves. Just like in a physical game where you need to:
checkGameStatus() {
for (const combo of this.winningCombos) {
const [a, b, c] = combo;
if (this.board[a] &&
this.board[a] === this.board[b] &&
this.board[a] === this.board[c]) {
this.gameStatus = 'won';
this.winner = this.board[a];
break;
}
}
if (this.gameStatus !== 'won' && !this.board.includes(null)) {
this.gameStatus = 'tied';
}
this.updateGameStatus();
}
Win detection works by checking predefined winning combinations after each move. Think of it like having a referee who knows all the possible ways to win and checks after each play.
saveGame() {
const gameState = {
currentPlayer: this.currentPlayer,
board: this.board,
gameStatus: this.gameStatus,
winner: this.winner
};
localStorage.setItem('ticTacToeGame', JSON.stringify(gameState));
}
loadGame() {
const savedGame = localStorage.getItem('ticTacToeGame');
if (savedGame) {
const gameState = JSON.parse(savedGame);
// Restore game state...
}
}
Game state persistence uses localStorage to save the game state. This is like having a bookmark in a book - when you come back later, you can pick up right where you left off.
When building your own version of this game, consider these important points:
Using a class helps organize related code and state. Each method has a single responsibility, making the code easier to understand and maintain. For example, updateSquareDisplay only handles the visual update of a square, while handleSquareClick orchestrates the entire move process.
Keep game state centralized in your class properties. This makes it easier to track the game's progress and save/restore state. Think of it like having a game referee who keeps track of all the important information in one place.
Set up event listeners when the game initializes and remove them when appropriate. This prevents memory leaks and ensures proper game cleanup.
When handling clicks, make sure to check if the move is valid before processing it. This prevents players from making moves after the game is over or on already-filled squares.
Keep the visual board state and internal board state synchronized. Any time you update the internal board array, make sure to update the corresponding visual element.
When resetting the game, remember to clear both the visual board and the internal state. This is like wiping the board clean for a new game - you need to reset everything to its starting position.
Test your game thoroughly by:
Building a Tic-Tac-Toe game teaches fundamental concepts of web development, including:
These concepts form the foundation for building more complex web applications. The principles of state management, user interaction, and game logic can be applied to many other projects.