Multiplayer Tick-Tack-Toe
Neil Haddley • May 10, 2023
Creating a new firebase project
I asked ChatGPT for some help creating a multiplayer version of Tick-Tack-Toe.
ChatGPT
How would I write a multiplayer tick-tack-toe game with Firebase Realtime Database, HTML, CSS and JavaScript?
To write a multiplayer tic-tac-toe game with Firebase Realtime Database, HTML, CSS, and JavaScript, you can follow these general steps:
1. Set up a Firebase account and create a new project.
2. Create a new Firebase Realtime Database in your project.
3. Create an HTML file with the tic-tac-toe game board and UI elements. You can use CSS to style the board and UI elements.
4. Write JavaScript code to connect to your Firebase Realtime Database and set up the game logic.
5. When a player makes a move, update the game state in the database.
6. Listen for changes to the game state in the database and update the UI accordingly.
7. Implement game logic to detect a win or a tie and update the database and UI accordingly.
Here's an example implementation of the above steps:
...

project name

google analytics?

project is ready

add web app

sdk setup

Add Realtime Database

create databse

database location

security rules (authentication is not enabled yet)

Realtime Database is empty

example implementation

create a folder

Open with Live Server

code running locally

firebase init

firebase deploy

public folder has been uploaded to firebase

copy the html, css and javascript files to the public folder

code has been deployed to Firebase (access using https://haddley-tick-tack-toe.web.app/)
Adding authentication
How do we know which player is which?
To differentiate between players and determine which player made a move, you can incorporate user authentication into your tic-tac-toe game. Firebase Authentication can be utilized to handle user sign-up, sign-in, and tracking the current user.
Here's an updated version of the makeMove function that incorporates user authentication:
...

dashboard

authentication

Anonymous

Anonymous Enabled
ChatGPT's commentary
This code initializes and sets up a tic-tac-toe game using Firebase as the backend database. Here's an explanation of the code:
1. Firebase Configuration: The code defines the configuration object firebaseConfig that contains the necessary credentials and configuration options for connecting to the Firebase project.
2. Firebase Initialization: The code initializes Firebase using the provided configuration object by calling firebase.initializeApp(firebaseConfig).
3. Anonymous Authentication: The code signs in the user anonymously by calling firebase.auth().signInAnonymously(). This allows the user to play the game without requiring any authentication.
4. Database Reference: The code obtains a reference to the Firebase Realtime Database service by calling firebase.database(). This reference will be used to interact with the game state data in the database.
5. Game State: The code defines the initial game state and related constants. The gameState object stores information such as the current player, the game board, and the game status.
6. UI Elements: The code retrieves references to various elements in the HTML document that represent the game board and the status message.
7. Update UI: The updateUI() function is responsible for updating the user interface based on the current game state. It updates the game board cells and the status message.
8. Make Move: The makeMove(row, col) function is called when a user clicks on a game board cell. It checks if the game is still in progress, verifies the user's turn, and updates the game board if the move is valid. It also checks for a win or draw condition and updates the game state accordingly.
9. Win Condition: The hasPlayerWon(player) function checks if a player has won the game by examining the game board.
10. Board Full Condition: The isBoardFull() function checks if the game board is full and there is no winner, resulting in a draw.
11. Reset Game: The resetGame() function resets the game state to the default values and updates the database with the new state.
12. Initialize Game: The initGame() function sets up event listeners for the game board cells and the reset button. It also listens for changes in the game state data in the database and updates the local game state and UI accordingly.
13 DOMContentLoaded Event: The initGame() function is called when the DOM (Document Object Model) is fully loaded, ensuring that the game setup is performed when the page is ready.
Overall, this code provides the functionality for playing a tic-tac-toe game in a web browser using Firebase for real-time updates and synchronization of the game state between multiple players.

updated code

firebase deploy

deployed
SDK setup
TEXT
1<script type="module"> 2 // Import the functions you need from the SDKs you need 3 import { initializeApp } from "https://www.gstatic.com/firebasejs/9.21.0/firebase-app.js"; 4 // TODO: Add SDKs for Firebase products that you want to use 5 // https://firebase.google.com/docs/web/setup#available-libraries 6 7 // Your web app's Firebase configuration 8 const firebaseConfig = { 9 apiKey: "AIzaSyAw86TBoGMBkdTHnhs28zKCiVvvkNEcPqM", 10 authDomain: "haddley-tick-tack-toe.firebaseapp.com", 11 projectId: "haddley-tick-tack-toe", 12 storageBucket: "haddley-tick-tack-toe.appspot.com", 13 messagingSenderId: "762869781957", 14 appId: "1:762869781957:web:02d5bd65bd1d1661dc27ac" 15 }; 16 17 // Initialize Firebase 18 const app = initializeApp(firebaseConfig); 19</script>
index.html
TEXT
1<!DOCTYPE html> 2<html> 3 <head> 4 <title>Tic Tac Toe</title> 5 <link rel="stylesheet" href="style.css" /> 6 </head> 7 <body> 8 <h1>Tic Tac Toe</h1> 9 <div id="board"> 10 <div class="row"> 11 <div class="cell" id="cell00"></div> 12 <div class="cell" id="cell01"></div> 13 <div class="cell" id="cell02"></div> 14 </div> 15 <div class="row"> 16 <div class="cell" id="cell10"></div> 17 <div class="cell" id="cell11"></div> 18 <div class="cell" id="cell12"></div> 19 </div> 20 <div class="row"> 21 <div class="cell" id="cell20"></div> 22 <div class="cell" id="cell21"></div> 23 <div class="cell" id="cell22"></div> 24 </div> 25 </div> 26 <div id="status"></div> 27 <button id="reset">Reset</button> 28 29 <script src="https://www.gstatic.com/firebasejs/8.7.1/firebase-app.js"></script> 30 <script src="https://www.gstatic.com/firebasejs/8.7.1/firebase-database.js"></script> 31 <script src="app.js"></script> 32 </body> 33</html>
style.css
TEXT
1#board { 2 display: flex; 3 flex-direction: column; 4 } 5 6 .row { 7 display: flex; 8 flex-direction: row; 9 } 10 11 .cell { 12 width: 100px; 13 height: 100px; 14 border: 1px solid black; 15 display: flex; 16 justify-content: center; 17 align-items: center; 18 font-size: 48px; 19 cursor: pointer; 20 } 21 22 .cell:hover { 23 background-color: #f1f1f1; 24 } 25 26 #status { 27 margin-top: 20px; 28 font-size: 24px; 29 } 30 31 #reset { 32 margin-top: 20px; 33 }
app.js
TEXT
1// Initialize Firebase 2const firebaseConfig = { 3 apiKey: "AIzaSyAw86TBoGMBkdTHnhs28zKCiVvvkNEcPqM", 4 authDomain: "haddley-tick-tack-toe.firebaseapp.com", 5 projectId: "haddley-tick-tack-toe", 6 storageBucket: "haddley-tick-tack-toe.appspot.com", 7 messagingSenderId: "762869781957", 8 appId: "1:762869781957:web:02d5bd65bd1d1661dc27ac" 9}; 10 11firebase.initializeApp(firebaseConfig); 12 13// Get a reference to the database service 14const database = firebase.database(); 15 16// Game state 17let gameState = { 18 currentPlayer: 'X', 19 board: [ 20 ['', '', ''], 21 ['', '', ''], 22 ['', '', ''], 23 ], 24}; 25 26// Game board elements 27const boardCells = [ 28 document.getElementById('cell00'), 29 document.getElementById('cell01'), 30 document.getElementById('cell02'), 31 document.getElementById('cell10'), 32 document.getElementById('cell11'), 33 document.getElementById('cell12'), 34 document.getElementById('cell20'), 35 document.getElementById('cell21'), 36 document.getElementById('cell22'), 37]; 38 39 40// Update the UI with the current game state 41function updateUI() { 42 const { board, currentPlayer } = gameState; 43 44 // Update each cell on the board 45 for (let i = 0; i < 3; i++) { 46 for (let j = 0; j < 3; j++) { 47 const cell = document.getElementById(`cell${i}${j}`); 48 cell.textContent = board[i][j]; 49 } 50 } 51 52 // Update the status message 53 const statusElement = document.getElementById('status'); 54 statusElement.textContent = `Current Player: ${currentPlayer}`; 55} 56 57// Make a move 58function makeMove(row, col) { 59 const { board, currentPlayer } = gameState; 60 61 // Check if the selected cell is empty 62 if (board[row][col] === '') { 63 // Update the board with the current player's symbol 64 board[row][col] = currentPlayer; 65 66 // Check if the current player has won 67 if (hasPlayerWon(currentPlayer)) { 68 alert(`Player ${currentPlayer} has won!`); 69 resetGame(); 70 } else { 71 // Check if the board is full 72 if (isBoardFull()) { 73 alert('The game is a draw!'); 74 resetGame(); 75 } else { 76 // Switch to the other player 77 gameState.currentPlayer = currentPlayer === 'X' ? 'O' : 'X'; 78 } 79 } 80 81 // Update the game state in the database 82 database.ref('gameState').set(gameState); 83 } 84} 85 86// Check if a player has won 87function hasPlayerWon(player) { 88 const { board } = gameState; 89 90 // Check rows 91 for (let i = 0; i < 3; i++) { 92 if (board[i][0] === player && board[i][1] === player && board[i][2] === player) { 93 return true; 94 } 95 } 96 97 // Check columns 98 for (let j = 0; j < 3; j++) { 99 if (board[0][j] === player && board[1][j] === player && board[2][j] === player) { 100 return true; 101 } 102 } 103 104 // Check diagonals 105 if (board[0][0] === player && board[1][1] === player && board[2][2] === player) { 106 return true; 107 } 108 if (board[0][2] === player && board[1][1] === player && board[2][0] === player) { 109 return true; 110 } 111 112 return false; 113 } 114 115 116 117// Check if the game board is full 118function isBoardFull() { 119 const { board } = gameState; 120 121 // Check each cell on the board 122 for (let i = 0; i < 3; i++) { 123 for (let j = 0; j < 3; j++) { 124 if (board[i][j] === '') { 125 return false; 126 } 127 } 128 } 129 130 return true; 131 } 132 133 134// Reset the game 135function resetGame() { 136 // Reset the game state 137 gameState = { 138 currentPlayer: 'X', 139 board: [ 140 ['', '', ''], 141 ['', '', ''], 142 ['', '', ''], 143 ], 144 }; 145 146 // Clear the board in the database 147 database.ref('gameState').set(gameState); 148} 149 150// Initialize the game 151function initGame() { 152 // Attach click event listeners to each cell 153 const cells = document.getElementsByClassName('cell'); 154 for (let i = 0; i < cells.length; i++) { 155 const cell = cells[i]; 156 const row = Math.floor(i / 3); 157 const col = i % 3; 158 cell.addEventListener('click', () => makeMove(row, col)); 159 } 160 161 // Attach click event listener to the reset button 162 const resetButton = document.getElementById('reset'); 163 resetButton.addEventListener('click', resetGame); 164 165 // Listen for changes in the game state 166 database.ref('gameState').on('value', (snapshot) => { 167 const gameStateFromLiveDatabase = snapshot.val(); 168 if (gameStateFromLiveDatabase) { 169 gameState = gameStateFromLiveDatabase; 170 } 171 updateUI(); 172 }); 173} 174 175// Call the initGame function when the DOM is ready 176document.addEventListener('DOMContentLoaded', initGame);
Security Rules updated
TEXT
1{ 2 "rules": { 3 "gameState": { 4 ".read": "auth != null", 5 ".write": "auth != null" 6 } 7 } 8}
index.html updated
TEXT
1<!DOCTYPE html> 2<html> 3 4<head> 5 <title>Tic Tac Toe</title> 6 <link rel="stylesheet" href="style.css" /> 7</head> 8 9<body> 10 <h1>Tic Tac Toe</h1> 11 <div id="board"> 12 <div class="row"> 13 <div class="cell" id="cell00"></div> 14 <div class="cell" id="cell01"></div> 15 <div class="cell" id="cell02"></div> 16 </div> 17 <div class="row"> 18 <div class="cell" id="cell10"></div> 19 <div class="cell" id="cell11"></div> 20 <div class="cell" id="cell12"></div> 21 </div> 22 <div class="row"> 23 <div class="cell" id="cell20"></div> 24 <div class="cell" id="cell21"></div> 25 <div class="cell" id="cell22"></div> 26 </div> 27 </div> 28 <div id="whoami"></div> 29 <div id="status"></div> 30 <button id="reset">Reset</button> 31 32 <script src="https://www.gstatic.com/firebasejs/8.7.1/firebase-app.js"></script> 33 <script src="https://www.gstatic.com/firebasejs/8.7.1/firebase-auth.js"></script> 34 <script src="https://www.gstatic.com/firebasejs/8.7.1/firebase-database.js"></script> 35 <script src="app.js"></script> 36</body> 37 38</html>
style.css updated
TEXT
1#board { 2 display: flex; 3 flex-direction: column; 4} 5 6.row { 7 display: flex; 8 flex-direction: row; 9} 10 11.cell { 12 width: 100px; 13 height: 100px; 14 border: 1px solid black; 15 display: flex; 16 justify-content: center; 17 align-items: center; 18 font-size: 48px; 19 cursor: pointer; 20} 21 22.cell:hover { 23 background-color: #f1f1f1; 24} 25 26#status { 27 margin-top: 20px; 28 font-size: 24px; 29} 30 31#whoami { 32 margin-top: 20px; 33 font-size: 24px; 34} 35 36#reset { 37 margin-top: 20px; 38}
app.js updated
TEXT
1// Initialize Firebase 2const firebaseConfig = { 3 apiKey: "AIzaSyAw86TBoGMBkdTHnhs28zKCiVvvkNEcPqM", 4 authDomain: "haddley-tick-tack-toe.firebaseapp.com", 5 projectId: "haddley-tick-tack-toe", 6 storageBucket: "haddley-tick-tack-toe.appspot.com", 7 messagingSenderId: "762869781957", 8 appId: "1:762869781957:web:02d5bd65bd1d1661dc27ac" 9}; 10 11// Initialize Firebase 12firebase.initializeApp(firebaseConfig); 13 14// Sign in anonymously 15firebase.auth().signInAnonymously().catch((error) => { 16 var errorCode = error.code; 17 var errorMessage = error.message; 18 console.log(errorCode, errorMessage); 19}); 20 21// Get a reference to the database service 22const database = firebase.database(); 23 24// Game state 25let gameState; 26const states = { 27 Playing: "playing", 28 XWon: "xwon", 29 OWon: "owon", 30 Draw: "draw" 31}; 32 33const defaultGameState = { 34 hostUID: null, // Replace with actual host user's UID 35 currentPlayer: 'X', 36 board: [ 37 ['', '', ''], 38 ['', '', ''], 39 ['', '', ''], 40 ], 41 status: states.Playing 42}; 43 44// Game board elements 45const boardCells = [ 46 document.getElementById('cell00'), 47 document.getElementById('cell01'), 48 document.getElementById('cell02'), 49 document.getElementById('cell10'), 50 document.getElementById('cell11'), 51 document.getElementById('cell12'), 52 document.getElementById('cell20'), 53 document.getElementById('cell21'), 54 document.getElementById('cell22'), 55]; 56 57// Update the UI with the current game state 58function updateUI() { 59 const { board, currentPlayer, status, hostUID } = gameState; 60 61 // Update each cell on the board 62 for (let i = 0; i < 3; i++) { 63 for (let j = 0; j < 3; j++) { 64 const cell = document.getElementById(`cell${i}${j}`); 65 cell.textContent = board[i][j]; 66 } 67 } 68 69 // Update the status message 70 const statusElement = document.getElementById('status'); 71 switch (status) { 72 case states.XWon: 73 statusElement.textContent = `Player X has won!`; 74 break; 75 case states.OWon: 76 statusElement.textContent = `Player O has won!`; 77 break; 78 case states.Draw: 79 statusElement.textContent = `The game is a draw!`; 80 break; 81 case states.Playing: 82 default: 83 statusElement.textContent = `Current Player: ${currentPlayer}`; 84 } 85 86 const user = firebase.auth().currentUser; 87 const whoAmIElement = document.getElementById('whoami'); 88 if (user.uid === hostUID) { 89 whoAmIElement.textContent = `You are X`; 90 } else { 91 whoAmIElement.textContent = `You are O`; 92 } 93} 94 95// Make a move 96function makeMove(row, col) { 97 const { board, currentPlayer, hostUID, status } = gameState; 98 99 if (status !== states.Playing) { 100 // The game is over, ignore the move 101 return; 102 } 103 104 const user = firebase.auth().currentUser; 105 if (!user) { 106 // User is not authenticated, handle the authentication requirement 107 console.log("Please sign in or create an account to play."); 108 return; 109 } 110 111 if ((user.uid === hostUID && currentPlayer === 'X') || (user.uid !== hostUID && currentPlayer === 'O')) { 112 if (board[row][col] === '') { 113 board[row][col] = currentPlayer; 114 115 if (hasPlayerWon(currentPlayer)) { 116 console.log(`Player ${currentPlayer} has won the game!`); 117 gameState.status = currentPlayer === 'X' ? states.XWon : states.OWon; 118 } else if (isBoardFull()) { 119 console.log("It's a tie!"); 120 gameState.status = states.Draw; 121 } else { 122 gameState.currentPlayer = currentPlayer === 'X' ? 'O' : 'X'; 123 } 124 125 database.ref('gameState').set(gameState); 126 } 127 } else { 128 console.log("It's not your turn."); 129 } 130} 131 132// Check if a player has won 133function hasPlayerWon(player) { 134 const { board } = gameState; 135 136 for (let i = 0; i < 3; i++) { 137 if (board[i][0] === player && board[i][1] === player && board[i][2] === player) { 138 return true; 139 } 140 } 141 142 for (let j = 0; j < 3; j++) { 143 if (board[0][j] === player && board[1][j] === player && board[2][j] === player) { 144 return true; 145 } 146 } 147 148 if (board[0][0] === player && board[1][1] === player && board[2][2] === player) { 149 return true; 150 } 151 152 if (board[0][2] === player && board[1][1] === player && board[2][0] === player) { 153 return true; 154 } 155 156 return false; 157} 158 159// Check if the game board is full 160function isBoardFull() { 161 const { board } = gameState; 162 163 for (let i = 0; i < 3; i++) { 164 for (let j = 0; j < 3; j++) { 165 if (board[i][j] === '') { 166 return false; 167 } 168 } 169 } 170 171 return true; 172} 173 174// Reset the game 175function resetGame() { 176 gameState = defaultGameState; 177 gameState.hostUID = firebase.auth().currentUser.uid; 178 database.ref('gameState').set(gameState); 179} 180 181// Initialize the game 182function initGame() { 183 const cells = document.getElementsByClassName('cell'); 184 185 for (let i = 0; i < cells.length; i++) { 186 const cell = cells[i]; 187 const row = Math.floor(i / 3); 188 const col = i % 3; 189 cell.addEventListener('click', () => makeMove(row, col)); 190 } 191 192 const resetButton = document.getElementById('reset'); 193 resetButton.addEventListener('click', resetGame); 194 195 database.ref('gameState').on('value', (snapshot) => { 196 const gameStateFromLiveDatabase = snapshot.val(); 197 198 if (!gameStateFromLiveDatabase) { 199 gameState = defaultGameState; 200 gameState.hostUID = firebase.auth().currentUser.uid; 201 database.ref('gameState').set(gameState); 202 return; 203 } 204 205 gameState = gameStateFromLiveDatabase; 206 updateUI(); 207 }); 208} 209 210// Call the initGame function when the DOM is ready 211document.addEventListener('DOMContentLoaded', initGame);