Multiplayer Tick-Tack-Toe

Neil HaddleyMay 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

project name

google analytics?

google analytics?

project is ready

project is ready

add web app

add web app

sdk setup

sdk setup

Add Realtime Database

Add Realtime Database

create databse

create databse

database location

database location

security rules (authentication is not enabled yet)

security rules (authentication is not enabled yet)

Realtime Database is empty

Realtime Database is empty

example implementation

example implementation

create a folder

create a folder

Open with Live Server

Open with Live Server

code running locally

code running locally

firebase init

firebase init

firebase deploy

firebase deploy

public folder has been uploaded to firebase

public folder has been uploaded to firebase

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

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/)

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

dashboard

authentication

authentication

Anonymous

Anonymous

Anonymous Enabled

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

updated code

firebase deploy

firebase deploy

deployed

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);