awacke1 commited on
Commit
3b226b3
·
verified ·
1 Parent(s): 5a86419

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +585 -18
index.html CHANGED
@@ -1,19 +1,586 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Yar's Revenge 3D: Brick Assault</title>
7
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
8
+ <style>
9
+ body { margin: 0; overflow: hidden; font-family: 'Arial', sans-serif; background-color: #000; color: #fff; }
10
+ canvas { display: block; }
11
+ #infoPanel {
12
+ position: absolute;
13
+ top: 10px;
14
+ left: 10px;
15
+ padding: 10px;
16
+ background-color: rgba(0,0,0,0.7);
17
+ border-radius: 8px;
18
+ color: #fff;
19
+ font-size: 16px;
20
+ display: flex;
21
+ flex-direction: column;
22
+ gap: 8px;
23
+ }
24
+ .player-info {
25
+ padding: 5px;
26
+ border-radius: 4px;
27
+ }
28
+ .player1 { background-color: rgba(0, 150, 255, 0.5); }
29
+ .player2 { background-color: rgba(255, 100, 0, 0.5); }
30
+ #gameOverScreen {
31
+ position: absolute;
32
+ top: 50%;
33
+ left: 50%;
34
+ transform: translate(-50%, -50%);
35
+ padding: 30px;
36
+ background-color: rgba(20, 20, 20, 0.9);
37
+ border: 2px solid #555;
38
+ border-radius: 15px;
39
+ text-align: center;
40
+ display: none; /* Hidden by default */
41
+ z-index: 100;
42
+ }
43
+ #gameOverScreen h2 { margin-top: 0; font-size: 28px; color: #ff4444; }
44
+ #gameOverScreen p { font-size: 18px; }
45
+ #gameOverScreen button {
46
+ padding: 12px 25px;
47
+ font-size: 18px;
48
+ color: #fff;
49
+ background-color: #007bff;
50
+ border: none;
51
+ border-radius: 8px;
52
+ cursor: pointer;
53
+ margin-top: 20px;
54
+ transition: background-color 0.3s ease;
55
+ }
56
+ #gameOverScreen button:hover { background-color: #0056b3; }
57
+ </style>
58
+ </head>
59
+ <body>
60
+ <div id="infoPanel">
61
+ <div id="player1Info" class="player-info player1">Player 1 (WASD, E): Score 0 | Lives 3</div>
62
+ <div id="player2Info" class="player-info player2">Player 2 (IJKL, U): Score 0 | Lives 3</div>
63
+ <div id="qotileInfo">Qotile Health: 100</div>
64
+ </div>
65
+
66
+ <div id="gameOverScreen">
67
+ <h2>Game Over!</h2>
68
+ <p id="gameOverMessage"></p>
69
+ <button id="restartButton">Restart Game</button>
70
+ </div>
71
+
72
+ <script>
73
+ let scene, camera, renderer, clock;
74
+ let players = [];
75
+ let playerProjectiles = [];
76
+ let advancingBricks = []; // Renamed from neutralZoneBlocks
77
+ let qotile;
78
+
79
+ const keysPressed = {};
80
+ const gameSettings = {
81
+ playerSpeed: 10,
82
+ projectileSpeed: 30,
83
+ playerSize: 1,
84
+ projectileSize: 0.2,
85
+ brickSize: 2, // Size of individual bricks
86
+ brickDepth: 1, // Thickness of bricks (Z-dimension)
87
+ qotileSize: 4,
88
+ playAreaWidth: 30,
89
+ playAreaHeight: 20,
90
+ playerShootCooldown: 0.2,
91
+ initialPlayerLives: 3,
92
+ qotileInitialHealth: 100,
93
+ pointsPerBrick: 10, // Renamed from pointsPerNeutralBlock
94
+ pointsPerQotileHit: 50,
95
+ brickLayerSpeed: 1.5, // How fast layers move towards player
96
+ newLayerSpawnInterval: 5.0, // Seconds between new layers
97
+ brickSpawnStartZ: -12, // Z-position where new brick layers spawn (behind Qotile or far back)
98
+ initialBrickLayers: 3, // Number of layers to start with
99
+ rowsPerLayer: 5, // Bricks vertically in a layer
100
+ colsPerLayer: 10, // Bricks horizontally in a layer
101
+ gapBetweenInitialLayers: 2.5, // Z-gap for initial setup
102
+ playerInvincibilityDuration: 1.5, // seconds
103
+ };
104
+ let gameActive = true;
105
+ let newLayerTimer = 0;
106
+
107
+ // Initialization function
108
+ function init() {
109
+ gameActive = true;
110
+ newLayerTimer = 0;
111
+ scene = new THREE.Scene();
112
+ scene.background = new THREE.Color(0x111122);
113
+
114
+ camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
115
+ camera.position.set(0, 5, 25);
116
+ camera.lookAt(0, 0, 0);
117
+
118
+ renderer = new THREE.WebGLRenderer({ antialias: true });
119
+ renderer.setSize(window.innerWidth, window.innerHeight);
120
+ document.body.appendChild(renderer.domElement);
121
+
122
+ const ambientLight = new THREE.AmbientLight(0x606060);
123
+ scene.add(ambientLight);
124
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
125
+ directionalLight.position.set(5, 10, 7.5);
126
+ scene.add(directionalLight);
127
+
128
+ clock = new THREE.Clock();
129
+
130
+ createPlayers();
131
+ createInitialBrickLayers(); // New function to set up starting bricks
132
+ createQotile();
133
+
134
+ document.addEventListener('keydown', onKeyDown);
135
+ document.addEventListener('keyup', onKeyUp);
136
+ window.addEventListener('resize', onWindowResize);
137
+ document.getElementById('restartButton').addEventListener('click', restartGame);
138
+
139
+ document.getElementById('gameOverScreen').style.display = 'none';
140
+ updateUI();
141
+ animate();
142
+ }
143
+
144
+ function createPlayers() {
145
+ players = [];
146
+ playerProjectiles = [];
147
+
148
+ const playerGeometry = new THREE.BoxGeometry(gameSettings.playerSize, gameSettings.playerSize, gameSettings.playerSize);
149
+
150
+ const player1Material = new THREE.MeshStandardMaterial({ color: 0x0099ff });
151
+ const player1Mesh = new THREE.Mesh(playerGeometry, player1Material);
152
+ player1Mesh.position.set(-gameSettings.playAreaWidth / 4, 0, 15); // Player Z position
153
+ scene.add(player1Mesh);
154
+ players.push({
155
+ mesh: player1Mesh,
156
+ isPlayer2: false,
157
+ controls: { up: 'KeyW', down: 'KeyS', left: 'KeyA', right: 'KeyD', shoot: 'KeyE' },
158
+ shootCooldownTimer: 0,
159
+ score: 0,
160
+ lives: gameSettings.initialPlayerLives,
161
+ projectiles: [],
162
+ invincibleTimer: 0,
163
+ originalColor: player1Material.color.getHex()
164
+ });
165
+
166
+ const player2Material = new THREE.MeshStandardMaterial({ color: 0xff6600 });
167
+ const player2Mesh = new THREE.Mesh(playerGeometry, player2Material);
168
+ player2Mesh.position.set(gameSettings.playAreaWidth / 4, 0, 15); // Player Z position
169
+ scene.add(player2Mesh);
170
+ players.push({
171
+ mesh: player2Mesh,
172
+ isPlayer2: true,
173
+ controls: { up: 'KeyI', down: 'KeyK', left: 'KeyJ', right: 'KeyL', shoot: 'KeyU' },
174
+ shootCooldownTimer: 0,
175
+ score: 0,
176
+ lives: gameSettings.initialPlayerLives,
177
+ projectiles: [],
178
+ invincibleTimer: 0,
179
+ originalColor: player2Material.color.getHex()
180
+ });
181
+ }
182
+
183
+ // Creates the initial set of brick layers at the start of the game
184
+ function createInitialBrickLayers() {
185
+ advancingBricks.forEach(brick => {
186
+ if (brick && scene.getObjectById(brick.id)) scene.remove(brick);
187
+ if (brick && brick.geometry) brick.geometry.dispose();
188
+ if (brick && brick.material) brick.material.dispose();
189
+ });
190
+ advancingBricks = [];
191
+ for (let i = 0; i < gameSettings.initialBrickLayers; i++) {
192
+ // Stagger initial layers further back
193
+ const spawnZ = gameSettings.brickSpawnStartZ - (i * gameSettings.gapBetweenInitialLayers);
194
+ spawnNewBrickLayer(spawnZ);
195
+ }
196
+ }
197
+
198
+ // Spawns a single new layer of bricks at a given Z depth
199
+ function spawnNewBrickLayer(spawnZ) {
200
+ const brickGeometry = new THREE.BoxGeometry(
201
+ gameSettings.brickSize,
202
+ gameSettings.brickSize,
203
+ gameSettings.brickDepth
204
+ );
205
+ // Give bricks varied, but generally dull/rocky colors
206
+ const colors = [0x888888, 0x777777, 0x666666, 0x807060, 0x706050];
207
+
208
+ const layerWidth = gameSettings.colsPerLayer * gameSettings.brickSize;
209
+ const layerHeight = gameSettings.rowsPerLayer * gameSettings.brickSize;
210
+
211
+ for (let r = 0; r < gameSettings.rowsPerLayer; r++) {
212
+ for (let c = 0; c < gameSettings.colsPerLayer; c++) {
213
+ const randomColor = colors[Math.floor(Math.random() * colors.length)];
214
+ const brickMaterial = new THREE.MeshStandardMaterial({
215
+ color: randomColor,
216
+ roughness: 0.8,
217
+ metalness: 0.2
218
+ });
219
+ const brick = new THREE.Mesh(brickGeometry.clone(), brickMaterial.clone());
220
+
221
+ brick.position.set(
222
+ (c - gameSettings.colsPerLayer / 2 + 0.5) * gameSettings.brickSize,
223
+ (r - gameSettings.rowsPerLayer / 2 + 0.5) * gameSettings.brickSize,
224
+ spawnZ
225
+ );
226
+ brick.userData = { type: 'brick' }; // Identify object type
227
+ scene.add(brick);
228
+ advancingBricks.push(brick);
229
+ }
230
+ }
231
+ }
232
+
233
+ function createQotile() {
234
+ if (qotile && qotile.mesh) {
235
+ if (scene.getObjectById(qotile.mesh.id)) scene.remove(qotile.mesh);
236
+ if (qotile.mesh.geometry) qotile.mesh.geometry.dispose();
237
+ if (qotile.mesh.material) qotile.mesh.material.dispose();
238
+ }
239
+
240
+ const qotileGeometry = new THREE.BoxGeometry(gameSettings.qotileSize, gameSettings.qotileSize, gameSettings.qotileSize);
241
+ const qotileMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000, emissive: 0x330000 });
242
+ const qotileMesh = new THREE.Mesh(qotileGeometry, qotileMaterial);
243
+ qotileMesh.position.set(0, 0, -15); // Qotile is further back
244
+ scene.add(qotileMesh);
245
+ qotile = {
246
+ mesh: qotileMesh,
247
+ health: gameSettings.qotileInitialHealth,
248
+ hitTimer: 0
249
+ };
250
+ }
251
+
252
+ function handlePlayerMovement(player, delta) {
253
+ if (!player.mesh.visible) return; // Cannot move if not visible (e.g. after losing life briefly)
254
+ const moveDistance = gameSettings.playerSpeed * delta;
255
+ if (keysPressed[player.controls.up]) player.mesh.position.y += moveDistance;
256
+ if (keysPressed[player.controls.down]) player.mesh.position.y -= moveDistance;
257
+ if (keysPressed[player.controls.left]) player.mesh.position.x -= moveDistance;
258
+ if (keysPressed[player.controls.right]) player.mesh.position.x += moveDistance;
259
+
260
+ const halfWidth = gameSettings.playAreaWidth / 2 - gameSettings.playerSize / 2;
261
+ const halfHeight = gameSettings.playAreaHeight / 2 - gameSettings.playerSize / 2;
262
+ player.mesh.position.x = Math.max(-halfWidth, Math.min(halfWidth, player.mesh.position.x));
263
+ player.mesh.position.y = Math.max(-halfHeight, Math.min(halfHeight, player.mesh.position.y));
264
+ }
265
+
266
+ function updatePlayerInvincibility(player, delta) {
267
+ if (player.invincibleTimer > 0) {
268
+ player.invincibleTimer -= delta;
269
+ // Blink effect
270
+ const blinkSpeed = 10; // Blinks per second
271
+ player.mesh.visible = Math.floor(player.invincibleTimer * blinkSpeed) % 2 === 0;
272
+ if (player.invincibleTimer <= 0) {
273
+ player.mesh.visible = true; // Ensure visible
274
+ player.mesh.material.color.setHex(player.originalColor); // Restore color
275
+ player.invincibleTimer = 0; // Clamp to zero
276
+ }
277
+ } else {
278
+ if(player.lives > 0) player.mesh.visible = true; // Ensure player is visible if they have lives
279
+ }
280
+ }
281
+
282
+
283
+ function handlePlayerShooting(player, delta) {
284
+ if (!player.mesh.visible) return;
285
+ if (player.shootCooldownTimer > 0) {
286
+ player.shootCooldownTimer -= delta;
287
+ }
288
+ if (keysPressed[player.controls.shoot] && player.shootCooldownTimer <= 0) {
289
+ player.shootCooldownTimer = gameSettings.playerShootCooldown;
290
+ createProjectile(player);
291
+ }
292
+ }
293
+
294
+ function createProjectile(player) {
295
+ const projectileGeometry = new THREE.SphereGeometry(gameSettings.projectileSize, 8, 8);
296
+ const projectileMaterial = new THREE.MeshBasicMaterial({ color: player.isPlayer2 ? 0xffaa33 : 0x66ccff });
297
+ const projectile = new THREE.Mesh(projectileGeometry, projectileMaterial);
298
+
299
+ projectile.position.copy(player.mesh.position);
300
+ projectile.position.z -= gameSettings.playerSize / 2 + gameSettings.projectileSize;
301
+
302
+ projectile.userData = {
303
+ owner: player,
304
+ velocity: new THREE.Vector3(0, 0, -gameSettings.projectileSpeed)
305
+ };
306
+
307
+ scene.add(projectile);
308
+ playerProjectiles.push(projectile);
309
+ }
310
+
311
+ function updateProjectiles(delta) {
312
+ for (let i = playerProjectiles.length - 1; i >= 0; i--) {
313
+ const projectile = playerProjectiles[i];
314
+ if (!projectile || !projectile.userData) {
315
+ if (projectile && scene.getObjectById(projectile.id)) scene.remove(projectile);
316
+ playerProjectiles.splice(i, 1);
317
+ continue;
318
+ }
319
+ projectile.position.addScaledVector(projectile.userData.velocity, delta);
320
+
321
+ if (projectile.position.z < -gameSettings.playAreaWidth || projectile.position.z > gameSettings.playAreaWidth * 2 || // Extended bounds
322
+ Math.abs(projectile.position.x) > gameSettings.playAreaWidth ||
323
+ Math.abs(projectile.position.y) > gameSettings.playAreaHeight) {
324
+ scene.remove(projectile);
325
+ if (projectile.geometry) projectile.geometry.dispose();
326
+ if (projectile.material) projectile.material.dispose();
327
+ playerProjectiles.splice(i, 1);
328
+ continue;
329
+ }
330
+ checkProjectileCollisions(projectile, i);
331
+ }
332
+ }
333
+
334
+ function checkProjectileCollisions(projectile, projectileIndex) {
335
+ if (!projectile || !projectile.userData || !projectile.userData.owner) {
336
+ if (projectile && scene.getObjectById(projectile.id)) scene.remove(projectile);
337
+ if(playerProjectiles[projectileIndex] === projectile) playerProjectiles.splice(projectileIndex, 1);
338
+ return;
339
+ }
340
+ const projectileBox = new THREE.Box3().setFromObject(projectile);
341
+
342
+ // Collision with Advancing Bricks
343
+ for (let j = advancingBricks.length - 1; j >= 0; j--) {
344
+ const brick = advancingBricks[j];
345
+ if (!brick || !brick.geometry) continue; // Skip if brick already removed or invalid
346
+ const brickBox = new THREE.Box3().setFromObject(brick);
347
+ if (projectileBox.intersectsBox(brickBox)) {
348
+ scene.remove(brick); // Remove brick from scene
349
+ if (brick.geometry) brick.geometry.dispose();
350
+ if (brick.material) brick.material.dispose();
351
+ advancingBricks.splice(j, 1); // Remove brick from array
352
+
353
+ scene.remove(projectile); // Remove projectile from scene
354
+ if (projectile.geometry) projectile.geometry.dispose();
355
+ if (projectile.material) projectile.material.dispose();
356
+ playerProjectiles.splice(projectileIndex, 1); // Remove projectile from array
357
+
358
+ projectile.userData.owner.score += gameSettings.pointsPerBrick;
359
+ updateUI();
360
+ return; // Projectile is gone, stop further checks for it
361
+ }
362
+ }
363
+
364
+ // Collision with Qotile
365
+ if (qotile && qotile.mesh && qotile.health > 0) {
366
+ const qotileBox = new THREE.Box3().setFromObject(qotile.mesh);
367
+ if (projectileBox.intersectsBox(qotileBox)) {
368
+ scene.remove(projectile);
369
+ if (projectile.geometry) projectile.geometry.dispose();
370
+ if (projectile.material) projectile.material.dispose();
371
+ playerProjectiles.splice(projectileIndex, 1);
372
+
373
+ qotile.health -= 10;
374
+ qotile.mesh.material.emissive.setHex(0xffffff);
375
+ qotile.hitTimer = 0.1;
376
+ projectile.userData.owner.score += gameSettings.pointsPerQotileHit;
377
+ updateUI();
378
+
379
+ if (qotile.health <= 0) {
380
+ const winnerName = projectile.userData.owner.isPlayer2 ? "Player 2" : "Player 1";
381
+ endGame(winnerName + " destroyed the Qotile! Players win!");
382
+ }
383
+ return;
384
+ }
385
+ }
386
+ }
387
+
388
+ // Moves bricks and checks for collisions with players
389
+ function updateBricks(delta) {
390
+ for (let i = advancingBricks.length - 1; i >= 0; i--) {
391
+ const brick = advancingBricks[i];
392
+ if (!brick || !brick.geometry) { // Safety check / already removed
393
+ advancingBricks.splice(i,1);
394
+ continue;
395
+ }
396
+
397
+ brick.position.z += gameSettings.brickLayerSpeed * delta;
398
+
399
+ // Brick is past the player's general Z plane - remove it
400
+ // Player Z is 15. Brick depth is 1. Player Size is 1.
401
+ // If brick's front (brick.position.z - brickDepth/2) is > player's back (15 + playerSize/2)
402
+ if (brick.position.z - gameSettings.brickDepth / 2 > players[0].mesh.position.z + gameSettings.playerSize / 2 + 2) { // Added buffer
403
+ scene.remove(brick);
404
+ if (brick.geometry) brick.geometry.dispose();
405
+ if (brick.material) brick.material.dispose();
406
+ advancingBricks.splice(i, 1);
407
+ // console.log("Brick passed player zone and removed");
408
+ continue;
409
+ }
410
+
411
+ // Check collision with players
412
+ const brickBox = new THREE.Box3().setFromObject(brick);
413
+ for (const player of players) {
414
+ if (player.lives <= 0 || !player.mesh.visible || player.invincibleTimer > 0) continue;
415
+
416
+ const playerBox = new THREE.Box3().setFromObject(player.mesh);
417
+ if (brickBox.intersectsBox(playerBox)) {
418
+ player.lives--;
419
+ player.invincibleTimer = gameSettings.playerInvincibilityDuration;
420
+ player.mesh.material.color.setHex(0xff3333); // Flash red
421
+
422
+ // Destroy the brick that hit the player
423
+ scene.remove(brick);
424
+ if (brick.geometry) brick.geometry.dispose();
425
+ if (brick.material) brick.material.dispose();
426
+ advancingBricks.splice(i, 1);
427
+
428
+ updateUI();
429
+ if (player.lives <= 0) {
430
+ player.mesh.visible = false; // Hide player mesh when out of lives
431
+ }
432
+ checkOverallGameOver();
433
+ break; // Brick is gone, stop checking it against other players
434
+ }
435
+ }
436
+ }
437
+ }
438
+
439
+
440
+ function updateQotile(delta) {
441
+ if (qotile && qotile.hitTimer > 0) {
442
+ qotile.hitTimer -= delta;
443
+ if (qotile.hitTimer <= 0) {
444
+ qotile.mesh.material.emissive.setHex(0x330000);
445
+ }
446
+ }
447
+ }
448
+
449
+ function updateUI() {
450
+ const p1 = players && players[0] ? players[0] : { score: 0, lives: 0 };
451
+ const p2 = players && players[1] ? players[1] : { score: 0, lives: 0 };
452
+
453
+ document.getElementById('player1Info').textContent = `Player 1 (WASD, E): Score ${p1.score} | Lives ${p1.lives}`;
454
+ document.getElementById('player2Info').textContent = `Player 2 (IJKL, U): Score ${p2.score} | Lives ${p2.lives}`;
455
+
456
+ if (qotile) {
457
+ document.getElementById('qotileInfo').textContent = `Qotile Health: ${Math.max(0, qotile.health)}`;
458
+ } else {
459
+ document.getElementById('qotileInfo').textContent = `Qotile Health: N/A`;
460
+ }
461
+ }
462
+
463
+ function checkOverallGameOver() {
464
+ if (!gameActive) return;
465
+ const activePlayers = players.filter(p => p.lives > 0).length;
466
+ if (activePlayers === 0) {
467
+ endGame("All players are out of lives! Game Over.");
468
+ }
469
+ }
470
+
471
+ function endGame(message) {
472
+ if (!gameActive) return;
473
+ gameActive = false;
474
+ console.log("Game Over:", message);
475
+ document.getElementById('gameOverMessage').textContent = message;
476
+ document.getElementById('gameOverScreen').style.display = 'flex';
477
+
478
+ // Clear remaining projectiles (bricks are cleared by their own logic or restart)
479
+ playerProjectiles.forEach(p => {
480
+ if (p && scene.getObjectById(p.id)) scene.remove(p);
481
+ if (p && p.geometry) p.geometry.dispose();
482
+ if (p && p.material) p.material.dispose();
483
+ });
484
+ playerProjectiles = [];
485
+ }
486
+
487
+ function restartGame() {
488
+ // Clean up existing meshes from scene
489
+ players.forEach(p => {
490
+ if (p.mesh && scene.getObjectById(p.mesh.id)) scene.remove(p.mesh);
491
+ if (p.mesh && p.mesh.geometry) p.mesh.geometry.dispose();
492
+ if (p.mesh && p.mesh.material) p.mesh.material.dispose();
493
+ });
494
+ playerProjectiles.forEach(p => {
495
+ if (p && scene.getObjectById(p.id)) scene.remove(p);
496
+ if (p && p.geometry) p.geometry.dispose();
497
+ if (p && p.material) p.material.dispose();
498
+ });
499
+ advancingBricks.forEach(b => {
500
+ if (b && scene.getObjectById(b.id)) scene.remove(b);
501
+ if (b && b.geometry) b.geometry.dispose();
502
+ if (b && b.material) b.material.dispose();
503
+ });
504
+ if (qotile && qotile.mesh) {
505
+ if (scene.getObjectById(qotile.mesh.id)) scene.remove(qotile.mesh);
506
+ if (qotile.mesh.geometry) qotile.mesh.geometry.dispose();
507
+ if (qotile.mesh.material) qotile.mesh.material.dispose();
508
+ }
509
+
510
+ // Reset arrays
511
+ players = [];
512
+ playerProjectiles = [];
513
+ advancingBricks = [];
514
+ qotile = null;
515
+
516
+ // Re-initialize game elements
517
+ createPlayers(); // This will reset player scores, lives, positions, visibility
518
+ createInitialBrickLayers();
519
+ createQotile();
520
+
521
+ // Reset game state variables
522
+ gameActive = true;
523
+ newLayerTimer = 0;
524
+ players.forEach(p => { // Ensure players are visible and not invincible
525
+ p.mesh.visible = true;
526
+ p.invincibleTimer = 0;
527
+ p.mesh.material.color.setHex(p.originalColor);
528
+ });
529
+
530
+
531
+ document.getElementById('gameOverScreen').style.display = 'none';
532
+ updateUI();
533
+ }
534
+
535
+ function animate() {
536
+ requestAnimationFrame(animate);
537
+ const delta = clock.getDelta();
538
+
539
+ if (gameActive) {
540
+ players.forEach(player => {
541
+ if (player.mesh && player.lives > 0) {
542
+ handlePlayerMovement(player, delta);
543
+ handlePlayerShooting(player, delta);
544
+ }
545
+ // Invincibility update happens regardless of player input if timer is active
546
+ updatePlayerInvincibility(player, delta);
547
+ });
548
+
549
+ updateProjectiles(delta);
550
+ updateBricks(delta); // Handles brick movement and collision with players
551
+ updateQotile(delta);
552
+
553
+ // Spawn new brick layers
554
+ newLayerTimer += delta;
555
+ if (newLayerTimer >= gameSettings.newLayerSpawnInterval) {
556
+ spawnNewBrickLayer(gameSettings.brickSpawnStartZ);
557
+ newLayerTimer = 0;
558
+ }
559
+ }
560
+
561
+ updateUI();
562
+ renderer.render(scene, camera);
563
+ }
564
+
565
+ function onKeyDown(event) {
566
+ keysPressed[event.code] = true;
567
+ const gameKeys = ['KeyW', 'KeyA', 'KeyS', 'KeyD', 'KeyI', 'KeyJ', 'KeyK', 'KeyL', 'KeyE', 'KeyU', 'Space'];
568
+ if (gameKeys.includes(event.code)) {
569
+ event.preventDefault();
570
+ }
571
+ }
572
+ function onKeyUp(event) {
573
+ keysPressed[event.code] = false;
574
+ }
575
+ function onWindowResize() {
576
+ camera.aspect = window.innerWidth / window.innerHeight;
577
+ camera.updateProjectionMatrix();
578
+ renderer.setSize(window.innerWidth, window.innerHeight);
579
+ }
580
+
581
+ window.onload = function() {
582
+ init();
583
+ };
584
+ </script>
585
+ </body>
586
  </html>