Building a Vanilla JS (javascript) Tic-Tac-Toe Game: Solving Win Logic and Player Switch Issues
Recently, I decided to step away from the comfort of frameworks and dive back into some good old vanilla JavaScript. The goal was to build a simple Tic-Tac-Toe game—a project that’s great for practicing core JavaScript concepts like event handling, DOM manipulation, and game logic.
While the basic setup was fairly straightforward, I ran into a tricky issue when it came to implementing the win logic and switching between players. After some debugging and thought, I was able to solve the problem, and now I want to share my journey and how I fixed these issues.
The Setup
Before jumping into the logic, let’s take a look at the general setup for the Tic-Tac-Toe game. Here’s the HTML structure:
<!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>
</head>
<body>
<h1>Tic-Tac-Toe</h1>
<div id="board"></div>
<p id="status"></p>
<script src="tictactoe.js"></script>
</body>
</html>
The #board
element will hold our Tic-Tac-Toe grid, and the #status
element will display which player’s turn it is or announce the winner.
JavaScript Logic Breakdown
Now, let’s dive into the JavaScript part of the code. Here’s the outline of the game logic:
- Grid Setup: Create a 3×3 grid.
- Player Turns: Alternate between two players, “X” and “O.”
- Win Condition: Check if a player has won after every move.
- Switch Players: After each turn, switch to the next player.
Here’s the initial code I wrote:
const board = document.getElementById('board');
const status = document.getElementById('status');
let currentPlayer = 'X'; // Starting player
let gameBoard = ['', '', '', '', '', '', '', '', '']; // Empty grid
// Function to render the board
function renderBoard() {
board.innerHTML = '';
gameBoard.forEach((cell, index) => {
const div = document.createElement('div');
div.classList.add('cell');
div.textContent = cell;
div.dataset.index = index;
div.addEventListener('click', makeMove);
board.appendChild(div);
});
}
// Function to handle player moves
function makeMove(e) {
const index = e.target.dataset.index;
if (gameBoard[index] === '') {
gameBoard[index] = currentPlayer;
renderBoard();
if (checkWin()) {
status.textContent = `${currentPlayer} wins!`;
} else if (gameBoard.every(cell => cell !== '')) {
status.textContent = 'It\'s a draw!';
} else {
switchPlayer();
}
}
}
// Function to check for a winner
function checkWin() {
const winPatterns = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6]
];
return winPatterns.some(pattern => {
const [a, b, c] = pattern;
return gameBoard[a] === currentPlayer && gameBoard[b] === currentPlayer && gameBoard[c] === currentPlayer;
});
}
// Function to switch players
function switchPlayer() {
currentPlayer = currentPlayer === 'X' ? 'O' : 'X';
status.textContent = `Player ${currentPlayer}'s turn`;
}
// Initialize the game
renderBoard();
status.textContent = `Player ${currentPlayer}'s turn`;
The Issues
While the above code seemed like it would work, I quickly ran into a couple of issues:
- Win Logic Not Triggering: The win condition logic wasn’t being triggered even after a player completed a row, column, or diagonal. This could happen if the
checkWin()
function wasn’t properly detecting the combinations or ifmakeMove()
wasn’t being executed properly. - Player Switching Glitch: After a player wins, the turn switch should stop, but the player switch function was still firing, causing an unexpected player switch even after a win or draw.
Fixing the Win Logic
The first issue was related to how the win condition was checked. The win patterns were correct, but I realized I needed to ensure that after a player won, the game should stop further moves.
Solution for Win Logic:
I updated the makeMove()
function to stop the game once a player wins:
// Function to handle player moves
function makeMove(e) {
const index = e.target.dataset.index;
if (gameBoard[index] === '' && !status.textContent.includes('wins')) {
gameBoard[index] = currentPlayer;
renderBoard();
if (checkWin()) {
status.textContent = `${currentPlayer} wins!`;
} else if (gameBoard.every(cell => cell !== '')) {
status.textContent = 'It\'s a draw!';
} else {
switchPlayer();
}
}
}
By adding the condition !status.textContent.includes('wins')
, I ensured that players couldn’t continue making moves once someone had already won.
Fixing the Player Switching Logic
The issue with the player switch logic was that the game kept switching players even when the game had already ended. To fix this, I adjusted the switchPlayer function:
Solution for Player Switching Logic:
function switchPlayer() {
if (status.textContent.includes('wins') || status.textContent === 'It\'s a draw!') {
return;
}
currentPlayer = currentPlayer === 'X' ? 'O' : 'X';
status.textContent = `Player ${currentPlayer}'s turn`;
}
This ensures that the player will only switch if the game is still ongoing (i.e., no winner or draw has been declared).
Final Thoughts
Building a Tic-Tac-Toe game in vanilla JavaScript was a fun and rewarding project, but debugging the win and player switch logic was a good reminder of how even small issues can cause unexpected behavior. By carefully reviewing the game flow and adding the necessary conditions to stop the game after a win or draw, I was able to resolve the issues and get the game working smoothly.
If you’re building similar projects, remember to break the problem down step-by-step and don’t hesitate to revisit your logic when things don’t behave as expected. Happy coding!