-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmyScript.js
More file actions
386 lines (323 loc) · 18.9 KB
/
myScript.js
File metadata and controls
386 lines (323 loc) · 18.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
/** Comments here are both for my own understanding and for anyone else who looks at the code later.
* I'm learning JavasScript as I do this so this code might not be the most efficient - Aaron */
// --- GOOGLE FIREBASE SETUP --- \\
// Import Firebase SDK (Software Development Kit) modules directly from Google's CDN
//This gives us functions to connect to our Firebase project and talk to the Realtime Database for the health tracker
import { initializeApp } from 'https://www.gstatic.com/firebasejs/12.8.0/firebase-app.js';
import { getDatabase, ref, onValue, set, update, remove } from 'https://www.gstatic.com/firebasejs/12.8.0/firebase-database.js';
import { getAnalytics } from "https://www.gstatic.com/firebasejs/12.8.0/firebase-analytics.js";
// Confirguration object for our specific Firebase project
// These values come from the Firebase console and identity which project we want to connect to
const firebaseConfig = {
apiKey: "AIzaSyAXUdeglgszb0WflC5o8VuXBuO1V3OzHWQ", // This is a public api key, it's not a secret and is safe to include. Ignore Githubs warning about this.
authDomain: "skuffletest.firebaseapp.com",
databaseURL: "https://skuffletest-default-rtdb.europe-west1.firebasedatabase.app",
projectId: "skuffletest",
storageBucket: "skuffletest.firebasestorage.app",
messagingSenderId: "656546117071",
appId: "1:656546117071:web:25437409b3c2e155fa8eba",
measurementId: "G-E5H5Q8STFE"
};
// Const are variables that we can't reassign
// Creates a Firebase app instance using the config from above
// From this, we can get analytics and a connection to our Realtime Database
const app = initializeApp(firebaseConfig);
const analytics = getAnalytics(app);//analytics setup (optional)
const database = getDatabase(app);// Main handle to interact with the Realtime Database
// Create a reference to the "players" node in our database
// All player data will be stored under this path: /players
const playersRef = ref(database, 'players');
// --- SOUND EFFECTS SETUP --- \\
// Preloads the audio effects for health increase and decrease so they can play instantly when triggered
const increaseSound = new Audio('audio/increase.mp3');
const decreaseSound = new Audio('audio/decrease.mp3');
// Expose a function on window (browser window) to play the "increase health" sound effect
// Reset currentTime to 0 so the sound starts from the beginning each time
window.playIncreaseSound = function() {
increaseSound.currentTime = 0;
increaseSound.play();
};
// Same as above but for the "decrease health" sound effect
window.playDecreaseSound = function() {
decreaseSound.currentTime = 0;
decreaseSound.play();
}
// --- HEALTH CHANGE LOGIC --- \\
// Change a player's health by a certain amount (positive or negative), can be rehashed for the special abilites later on
// playerId is the database key for that player
window.changeHealth = function(playerId, change) {
// Get a reference to the specific player in the database using their unique ID: /players/<playerId>
const playerRef = ref(database, 'players/' + playerId);
// Read the current player data once from the database
onValue(playerRef, (snapshot) => {
const player = snapshot.val();
// Calculate the new health by adding the change
let newHealth = player.health + change;
// Constrain the health so it can't go below 0 or above 20
newHealth = Math.max(0, Math.min(20, newHealth));
// Write the updated health value back to the database
update(playerRef, { health: newHealth });
}, { onlyOnce: true }); // onlyOnce: true means this listener will run once and then stop
};
// --- SPECIAL ABILITY BANK --- \\
const characterData = { // This stores the special ability info for each character to be called upon later
"Cowboy": {
specialName: "Bullets",
specialType: "number",
specialValue: 6, // Change to match the number of players in game
specialMin: 0,
specialMax: 6
},
"Were-Lobster": {
specialName: "Tide",
specialType: "toggle",
specialValue: "Low Tide", // Starting tide
toggleOptions: ["Low Tide", "High Tide"] // Swap between the two tides
},
"Goe Bling": {
specialName: "Steal",
specialType: "number",
specialValue: 0,
specialMin: 0,
specialMax: 10, // Arbitrary max for steals
},
"Salary Man": {
specialName: "Money",
specialType: "number",
specialValue: 0,
specialMin: 0,
specialMax: 100, // Arbitrary max for money
},
"Alien": {
specialName: "Dodge",
specialType: "number",
specialValue: 0,
specialMin: 0,
specialMax: 10, // Arbitrary max for dodges
},
"Margritte": { // Doesn't have special ability so everthing is null
specialName: null,
specialType: null,
specialValue: null,
specialMin: null,
specialMax: null,
}
}
// --- SPECIAL ABILITY LOGIC --- \\
// Change a player's special value by a certain amount (positive or negative)
// playerId is the database key for that player
window.changeSpecial = function(playerId, change) {
// Get a reference to the specific player in the database using their unique ID: /players/<playerId>
const playerRef = ref(database, 'players/' + playerId);
// Read the current player data once from the database
onValue(playerRef, (snapshot) => {
const player = snapshot.val();
if (player.specialType === 'number') {
let newSpecial = player.specialValue + change;
newSpecial = Math.max(player.specialMin, Math.min(player.specialMax, newSpecial)); //constrains the special values for each unique character
update(playerRef, { specialValue: newSpecial });
}
}, { onlyOnce: true }); // onlyOnce: true means this listener will run once and then stop
};
window.toggleSpecial = function(playerId, change) {
// Get a reference to the specific player in the database using their unique ID: /players/<playerId>
const playerRef = ref(database, 'players/' + playerId);
// Read the current player data once from the database
onValue(playerRef, (snapshot) => {
const player = snapshot.val();
if (player.specialType === 'toggle' && player.toggleOptions) {
const currentIndex = player.toggleOptions.indexOf(player.specialValue); // set the currentIndex to what the current value is, so for the Were-Lobster it starts with Low Tide
const nextIndex = (currentIndex + 1) % player.toggleOptions.length; // Moves along the index in the array to the next value, being high tide. Loops back after that.
const newSpecialValue = player.toggleOptions[nextIndex];
update(playerRef, { specialValue: newSpecialValue});
}
}, { onlyOnce: true }); // onlyOnce: true means this listener will run once and then stop
};
// --- CHARACTER SELECTION STATE --- \\
// This variable stores which character the user has currently picked in the dropdown
// It is used when creating a new player
let selectedCharacter = null;
// Attach click event listeners to all links in the character dropdown
// When a link is clicked, we remember the chosen character and update the button text
document.querySelectorAll('.character-option').forEach(a => {
a.addEventListener('click', (e) => {
e.preventDefault(); // Stop the link from navigating anywhere since it's just a dropdown option
// Read the character name from the element's data-character attribute, and assign it to the char variable (constant)
const char = a.dataset.character;
//Save the selected character in our selectedCharacter variable
selectedCharacter = char;
// Update the button label so the user sees which character they've chosen
document.getElementById('character-chosen').textContent = char;
});
});
// --- REAL-TIME DATABASE LISTENER --- \\
// Listen for any change under /players in the database
// This runs whenever players are added, updated (health/character), or removed
onValue(playersRef, (snapshot) => {
const data = snapshot.val();// snapshot.val() gets the current data at the 'players' location
displayPlayers(data);// Update the display with the new data
});
// --- RENDER PLAYERS ON THE PAGE --- \\
// Display all the players on the page inside the #players container div
function displayPlayers(players) {
const container = document.getElementById('players');
// Clear any previous player cards so we can re-render from scratch
container.innerHTML = '';
// If there are no players in the database yet, just stop here
if (!players) return;
// 1) Build a set of characters that are already in use
// This will allow us to disable those characters in the dropdown
const takenCharacters = new Set();
for (let id in players) {
const p = players[id];
if (p.character) takenCharacters.add(p.character);
}
// 2) Loop over each player and create a visual card for them
// "id" is the unique Firebase key (timestap when the player was added)
for (let id in players) {
const player = players[id]; //create a new div for this player
// Create a container element for this player's card
const playerDiv = document.createElement('div');
playerDiv.className = 'player'; // Use CSS class .player for styling
let specialHTML = '';
if (player.specialName && player.specialType) {
if (player.specialType === 'number') {
specialHTML = `
<div class="special-control">
<span class="special-name">${player.specialName}:</span>
<button onclick="changeSpecial('${id}', -1);">-</button>
<div class="special-value">${player.specialValue}</div>
<button onclick="changeSpecial('${id}', 1);">+</button>
</div>
`;
} else if (player.specialType === 'toggle') {
specialHTML = `
<div class="special-control">
<span class="special-label">${player.specialName}:</span>
<button class="toggle-button" onclick="toggleSpecial('${id}')">${player.specialValue}</button>
</div>
`;
}
}
// Build the inner html for this player card:
// - Show player's name and their character (if they have one)
// - Show buttons and current health value
playerDiv.innerHTML = `
<div>${player.name} ${player.character ? '(' + player.character + ')' : ''}</div>
<div class="health-control">
<button onclick="changeHealth('${id}', -1); playDecreaseSound()">-</button>
<div class="health-value">${player.health}</div>
<button onclick="changeHealth('${id}', 1); playIncreaseSound()">+</button>
</div>
${specialHTML}
`;
// Add the finished card into the main players container
container.appendChild(playerDiv);
}
// 3) Disable any characters in the dropdown that are already taken
// Visually fade them and prevent clicking so two players cannot pick the same character
document.querySelectorAll('.character-option').forEach(a => {
const char = a.dataset.character;
// If the character is in the takenCharacters set, disable it
a.style.pointerEvents = takenCharacters.has(char) ? 'none' : 'auto'; // Stops clicks if taken. If takenCharacters has the char, then set pointerEvents to none, otherwise set to auto to allow clicking. Ternary Operator
a.style.opacity = takenCharacters.has(char) ? '0.4' : '1'; // Make taken options look faded
});
}
// --- ADDING NEW PLAYERS --- \\
// When the "Add player" button is clicked, create a new player in the database
document.getElementById('add-player').addEventListener('click', () => {
const nameInput = document.getElementById('player-name');
const name = nameInput.value.trim(); // Remove any extra spaces from start/end of name
// Do not add is the name field is empty, and show an alert
if (!name) {
alert('Please enter a name.');
return;
}
// Enforce that a character must be chosen before adding a player
if (!selectedCharacter) {
alert('Please select a character first.');
return;
}
// Read the current list of players once so we can:
// - check how many players exsist
// - ensure the selected character is not already taken
onValue(playersRef, (snapshot) => {
const players = snapshot.val();
// Count how many players we currently have
// If players is null, ther are 0 players; otherwise count the keys
const playerCount = players ? Object.keys(players).length : 0;
// Enforce a maximum of 6 players in total
if (playerCount >= 6) {
alert('Maximum of 6 players has been reached! Please remove players before adding any more')
return; // Stops it here and doesn't add the new player
}
// If there are existing players, ensure the chosen character is still free
if (players) {
for (let id in players) {
if (players[id].character === selectedCharacter) {
alert ('This character is already taken! Please select a different character.');
return; // Stop if someone has already picked the character
}
}
}
// If we reach this point:
// - There are less than 6 players
// - The selected character is not taken
// Now we create a new player entry in the database
// Use Date.now() as a simple unique ID for the player node
const newPlayerRef = ref(database, 'players/' + Date.now());
// Lookup the special data for chosen Character
const specialData = characterData[selectedCharacter];
// Build the player data object to save in the realtime database
const newPlayerData = {
name: name,
health: 20,
character: selectedCharacter,
specialName: specialData.specialName,
specialType: specialData.specialType,
specialValue: specialData.specialValue
};
// Add type-specific data for the different abilites
if (specialData.specialType === 'number') {
newPlayerData.specialMin = specialData.specialMin;
newPlayerData.specialMax = specialData.specialMax;
} else if (specialData.specialType === 'toggle') {
newPlayerData.toggleOptions = specialData.toggleOptions;
}
// Save the new player object with player data
set(newPlayerRef, newPlayerData);
// Clear the name input and reset the character selection in the UI
nameInput.value = '';
selectedCharacter = null;
document.getElementById('character-chosen').textContent = 'Choose Character';
}, { onlyOnce: true}); // We only need to check this data once, not a continuous listener
});
// --- CLEAR ALL PLAYERS --- \\
// When the "Clear All Players" is clicked, remove every player from the database
document.getElementById('clear-all').addEventListener('click', () => {
// Ask for confirmation before deleting everything
if (confirm('Are you sure you want to clear all players? This cannot be undone and will permanently delete all player data.')) {
remove(playersRef)
.then(() => {
// Resets character selection state
selectedCharacter = null;
document.getElementById('character-chosen').textContent = 'Choose Character';
document.querySelectorAll('.character-option').forEach(a => {
a.style.pointerEvents = 'auto'; // Re-enables clicking
a.style.opacity = '1'; // Resets opacity
})
console.log('All players cleared successfully');
})
.catch((error) => {
console.error('Error clearing players:', error);
});
}
});
// navbar logic
const toggle = document.getElementById('navToggle');
const menu = document.getElementById('navMenu');
toggle.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
menu.classList.toggle('open');
});