akhaliq HF staff commited on
Commit
4552af7
·
verified ·
1 Parent(s): 4d71127

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +228 -257
app.py CHANGED
@@ -8,302 +8,273 @@ with demo:
8
  <head>
9
  <meta charset="UTF-8">
10
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
11
- <title>Autonomous Maze Game Demo</title>
12
  <style>
13
  body { margin: 0; overflow: hidden; }
14
- #status {
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  position: absolute;
16
  top: 10px;
17
  left: 10px;
 
18
  color: white;
19
  background: rgba(0, 0, 0, 0.7);
20
- padding: 5px;
21
  font-family: Arial, sans-serif;
22
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  </style>
24
  </head>
25
  <body>
26
- <div id="container"></div>
27
- <div id="status">Status: In progress</div>
28
-
29
- <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
30
- <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/loaders/GLTFLoader.js"></script>
31
- <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.js"></script>
32
-
 
 
 
 
 
 
33
  <script>
34
- // Scene Setup
35
  const scene = new THREE.Scene();
36
  const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
37
  const renderer = new THREE.WebGLRenderer();
38
  renderer.setSize(window.innerWidth, window.innerHeight);
39
- document.getElementById('container').appendChild(renderer.domElement);
40
-
41
- // Lighting
42
- const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444);
43
- hemiLight.position.set(0, 20, 0);
44
- scene.add(hemiLight);
45
- const dirLight = new THREE.DirectionalLight(0xffffff);
46
- dirLight.position.set(0, 20, 10);
47
- scene.add(dirLight);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
- // Floor Texture
50
- function createFloorTexture() {
51
- const canvas = document.createElement('canvas');
52
- canvas.width = 256;
53
- canvas.height = 256;
54
- const context = canvas.getContext('2d');
55
- context.fillStyle = '#8B4513'; // Brown
56
- context.fillRect(0, 0, 256, 256);
57
- context.strokeStyle = '#FFFFFF';
58
- context.lineWidth = 2;
59
- for (let i = 0; i <= 16; i++) {
60
- const pos = i * 16;
61
- context.beginPath();
62
- context.moveTo(pos, 0);
63
- context.lineTo(pos, 256);
64
- context.stroke();
65
- context.beginPath();
66
- context.moveTo(0, pos);
67
- context.lineTo(256, pos);
68
- context.stroke();
69
- }
70
- const texture = new THREE.CanvasTexture(canvas);
71
- texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
72
- texture.repeat.set(16, 16);
73
- return texture;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  }
75
 
76
- // Wall Texture
77
- function createWallTexture() {
78
- const canvas = document.createElement('canvas');
79
- canvas.width = 64;
80
- canvas.height = 64;
81
- const context = canvas.getContext('2d');
82
- context.fillStyle = '#808080'; // Gray
83
- context.fillRect(0, 0, 64, 64);
84
- context.fillStyle = '#A0A0A0';
85
- for (let i = 0; i < 100; i++) {
86
- const x = Math.random() * 64;
87
- const y = Math.random() * 64;
88
- context.fillRect(x, y, 2, 2);
89
- }
90
- return new THREE.CanvasTexture(canvas);
 
 
 
 
 
 
 
 
 
 
91
  }
92
 
93
- // Floor
94
- const floorTexture = createFloorTexture();
95
- const floorGeometry = new THREE.PlaneGeometry(64, 64);
96
- const floorMaterial = new THREE.MeshBasicMaterial({ map: floorTexture });
97
- const floor = new THREE.Mesh(floorGeometry, floorMaterial);
98
- floor.rotation.x = -Math.PI / 2;
99
- scene.add(floor);
100
 
101
- // Maze Generation
102
- const gridSize = 16;
103
- const cellSize = 4;
104
- const maze = [];
105
- for (let i = 0; i < gridSize; i++) {
106
- maze[i] = [];
107
- for (let j = 0; j < gridSize; j++) {
108
- if (i === 0 || i === gridSize - 1 || j === 0 || j === gridSize - 1) {
109
- maze[i][j] = 1; // Walls on boundaries
110
- } else if (i >= 6 && i <= 9 && j >= 6 && j <= 9) {
111
- maze[i][j] = 0; // Central open area
112
- } else {
113
- maze[i][j] = Math.random() < 0.15 ? 1 : 0; // 15% chance of wall
114
- }
115
  }
116
- }
117
-
118
- // Walls
119
- const wallTexture = createWallTexture();
120
- const wallGeometry = new THREE.BoxGeometry(cellSize, cellSize, cellSize);
121
- const wallMaterial = new THREE.MeshBasicMaterial({ map: wallTexture });
122
- for (let i = 0; i < gridSize; i++) {
123
- for (let j = 0; j < gridSize; j++) {
124
- if (maze[i][j] === 1) {
125
- const wall = new THREE.Mesh(wallGeometry, wallMaterial);
126
- wall.position.set((j - 7.5) * cellSize, cellSize / 2, (i - 7.5) * cellSize);
127
- scene.add(wall);
128
- }
129
  }
130
- }
131
-
132
- // Flag
133
- const flagGeometry = new THREE.BoxGeometry(1, 2, 1);
134
- const flagMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 }); // Yellow
135
- const flag = new THREE.Mesh(flagGeometry, flagMaterial);
136
- flag.position.set(2, 1, 2); // Center of maze (8,8) translated to world coords
137
- scene.add(flag);
138
-
139
- // A* Pathfinding
140
- function aStar(maze, start, goal) {
141
- const openSet = [start];
142
- const cameFrom = {};
143
- const gScore = { [start]: 0 };
144
- const fScore = { [start]: heuristic(start, goal) };
145
-
146
- while (openSet.length > 0) {
147
- let current = openSet.reduce((a, b) => fScore[a] < fScore[b] ? a : b);
148
- if (current === goal) return reconstructPath(cameFrom, current);
149
-
150
- openSet.splice(openSet.indexOf(current), 1);
151
- for (let neighbor of getNeighbors(maze, current)) {
152
- let tentative_gScore = gScore[current] + 1;
153
- if (!(neighbor in gScore) || tentative_gScore < gScore[neighbor]) {
154
- cameFrom[neighbor] = current;
155
- gScore[neighbor] = tentative_gScore;
156
- fScore[neighbor] = gScore[neighbor] + heuristic(neighbor, goal);
157
- if (!openSet.includes(neighbor)) openSet.push(neighbor);
158
- }
159
- }
160
  }
161
- return [];
162
- }
163
 
164
- function heuristic(a, b) {
165
- const [aRow, aCol] = a.split(',').map(Number);
166
- const [bRow, bCol] = b.split(',').map(Number);
167
- return Math.abs(aRow - bRow) + Math.abs(aCol - bCol);
168
  }
169
 
170
- function getNeighbors(maze, node) {
171
- const [row, col] = node.split(',').map(Number);
172
- const neighbors = [];
173
- if (row > 0 && maze[row - 1][col] === 0) neighbors.push(`${row - 1},${col}`);
174
- if (row < 15 && maze[row + 1][col] === 0) neighbors.push(`${row + 1},${col}`);
175
- if (col > 0 && maze[row][col - 1] === 0) neighbors.push(`${row},${col - 1}`);
176
- if (col < 15 && maze[row][col + 1] === 0) neighbors.push(`${row},${col + 1}`);
177
- return neighbors;
178
- }
179
-
180
- function reconstructPath(cameFrom, current) {
181
- const path = [current];
182
- while (current in cameFrom) {
183
- current = cameFrom[current];
184
- path.unshift(current);
185
- }
186
- return path;
187
- }
188
-
189
- // Soldiers
190
- const loader = new THREE.GLTFLoader();
191
- let soldiers = [];
192
- let winner = null;
193
- const moveSpeed = 5;
194
-
195
- loader.load(
196
- 'https://threejs.org/examples/models/gltf/Soldier.glb',
197
- (gltf) => {
198
- const animations = gltf.animations;
199
-
200
- for (let team = 0; team < 2; team++) {
201
- for (let i = 0; i < 3; i++) {
202
- const soldierClone = gltf.scene.clone();
203
- const mixer = new THREE.AnimationMixer(soldierClone);
204
- soldierClone.scale.set(2, 2, 2);
205
-
206
- const soldierObj = {
207
- mesh: soldierClone,
208
- mixer: mixer,
209
- team: team,
210
- path: [],
211
- idleAction: mixer.clipAction(THREE.AnimationClip.findByName(animations, 'Idle')),
212
- runAction: mixer.clipAction(THREE.AnimationClip.findByName(animations, 'Run')),
213
- activeAction: null
214
- };
215
-
216
- soldierObj.activeAction = soldierObj.idleAction;
217
- soldierObj.activeAction.play();
218
-
219
- // Color soldiers
220
- soldierClone.traverse((child) => {
221
- if (child.isMesh) {
222
- child.material = child.material.clone();
223
- child.material.color.set(team === 0 ? 0x0000ff : 0xff0000);
224
- }
225
- });
226
-
227
- // Random starting position
228
- let gridX, gridZ;
229
- do {
230
- gridZ = Math.floor(Math.random() * 16);
231
- gridX = team === 0 ? Math.floor(Math.random() * 8) : Math.floor(Math.random() * 8) + 8;
232
- } while (maze[gridZ][gridX] === 1);
233
- const worldX = (gridX - 7.5) * cellSize;
234
- const worldZ = (gridZ - 7.5) * cellSize;
235
- soldierClone.position.set(worldX, 0, worldZ);
236
- scene.add(soldierClone);
237
-
238
- // Path to flag
239
- const start = `${gridZ},${gridX}`;
240
- const goal = "8,8";
241
- const path = aStar(maze, start, goal);
242
- soldierObj.path = path.map(node => {
243
- const [row, col] = node.split(',').map(Number);
244
- return new THREE.Vector3((col - 7.5) * cellSize, 0, (row - 7.5) * cellSize);
245
- });
246
-
247
- soldiers.push(soldierObj);
248
- }
249
- }
250
- },
251
- undefined,
252
- (error) => console.error('Error loading soldier:', error)
253
- );
254
-
255
- // Camera and Controls
256
- camera.position.set(0, 20, 30);
257
- const controls = new THREE.OrbitControls(camera, renderer.domElement);
258
- controls.target.set(0, 0, 0);
259
- controls.update();
260
-
261
- // Animation Loop
262
- const clock = new THREE.Clock();
263
  function animate() {
264
  requestAnimationFrame(animate);
265
- const delta = clock.getDelta();
266
-
267
- if (!winner) {
268
- for (let soldier of soldiers) {
269
- if (soldier.mesh.position.distanceTo(flag.position) < 1) {
270
- winner = soldier.team;
271
- document.getElementById('status').textContent = `Status: Team ${winner} wins!`;
272
- break;
273
- } else if (soldier.path.length > 0) {
274
- const target = soldier.path[0];
275
- const direction = target.clone().sub(soldier.mesh.position).normalize();
276
- const step = direction.multiplyScalar(moveSpeed * delta);
277
- soldier.mesh.position.add(step);
278
-
279
- const angle = Math.atan2(direction.x, direction.z);
280
- soldier.mesh.rotation.y = angle + Math.PI;
281
-
282
- if (soldier.mesh.position.distanceTo(target) < 0.5) {
283
- soldier.path.shift();
284
- }
285
-
286
- if (soldier.activeAction !== soldier.runAction) {
287
- soldier.activeAction.fadeOut(0.2);
288
- soldier.runAction.reset().fadeIn(0.2).play();
289
- soldier.activeAction = soldier.runAction;
290
- }
291
- } else {
292
- if (soldier.activeAction !== soldier.idleAction) {
293
- soldier.activeAction.fadeOut(0.2);
294
- soldier.idleAction.reset().fadeIn(0.2).play();
295
- soldier.activeAction = soldier.idleAction;
296
  }
297
  }
298
- soldier.mixer.update(delta);
299
- }
300
  }
301
 
 
 
 
 
 
302
  renderer.render(scene, camera);
303
  }
 
304
  animate();
305
 
306
- // Responsive Design
307
  window.addEventListener('resize', () => {
308
  camera.aspect = window.innerWidth / window.innerHeight;
309
  camera.updateProjectionMatrix();
 
8
  <head>
9
  <meta charset="UTF-8">
10
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
11
+ <title>Hugging Face Game with Sound</title>
12
  <style>
13
  body { margin: 0; overflow: hidden; }
14
+ canvas { display: block; }
15
+ #win-message {
16
+ position: absolute;
17
+ top: 50%;
18
+ left: 50%;
19
+ transform: translate(-50%, -50%);
20
+ font-size: 48px;
21
+ color: white;
22
+ background: rgba(0, 0, 0, 0.7);
23
+ padding: 20px;
24
+ display: none;
25
+ font-family: Arial, sans-serif;
26
+ }
27
+ #gpu-counter {
28
  position: absolute;
29
  top: 10px;
30
  left: 10px;
31
+ font-size: 24px;
32
  color: white;
33
  background: rgba(0, 0, 0, 0.7);
34
+ padding: 10px;
35
  font-family: Arial, sans-serif;
36
  }
37
+ #controls {
38
+ position: absolute;
39
+ top: 50%;
40
+ left: 50%;
41
+ transform: translate(-50%, -50%);
42
+ font-size: 20px;
43
+ color: white;
44
+ background: rgba(0, 0, 0, 0.8);
45
+ padding: 20px;
46
+ font-family: Arial, sans-serif;
47
+ text-align: center;
48
+ transition: opacity 1s;
49
+ }
50
  </style>
51
  </head>
52
  <body>
53
+ <div id="win-message">You Win!</div>
54
+ <div id="gpu-counter">GPUs Collected: 0 / 10</div>
55
+ <div id="controls">
56
+ <p>Controls:</p>
57
+ <p>W / Up Arrow: Forward</p>
58
+ <p>S / Down Arrow: Backward</p>
59
+ <p>A / Left Arrow: Left</p>
60
+ <p>D / Right Arrow: Right</p>
61
+ <p>Spacebar: Up</p>
62
+ <p>Shift: Down</p>
63
+ <p>Collect all GPUs to win!</p>
64
+ </div>
65
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
66
  <script>
67
+ // Scene setup
68
  const scene = new THREE.Scene();
69
  const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
70
  const renderer = new THREE.WebGLRenderer();
71
  renderer.setSize(window.innerWidth, window.innerHeight);
72
+ document.body.appendChild(renderer.domElement);
73
+
74
+ // Morning lighting
75
+ const directionalLight = new THREE.DirectionalLight(0xffe0b2, 1);
76
+ directionalLight.position.set(1, 1, 0.5).normalize();
77
+ scene.add(directionalLight);
78
+ const ambientLight = new THREE.AmbientLight(0x87ceeb, 0.5);
79
+ scene.add(ambientLight);
80
+
81
+ // Morning background
82
+ scene.background = new THREE.Color(0x87ceeb);
83
+ scene.fog = new THREE.Fog(0x87ceeb, 10, 100);
84
+
85
+ // Ground
86
+ const groundGeometry = new THREE.PlaneGeometry(100, 100);
87
+ const groundMaterial = new THREE.MeshLambertMaterial({ color: 0x8c8c8c });
88
+ const ground = new THREE.Mesh(groundGeometry, groundMaterial);
89
+ ground.rotation.x = -Math.PI / 2;
90
+ scene.add(ground);
91
+
92
+ // Realistic Buildings
93
+ const buildings = [];
94
+ const numBuildings = 50;
95
+ for (let i = 0; i < numBuildings; i++) {
96
+ const width = Math.random() * 5 + 2;
97
+ const height = Math.random() * 20 + 10;
98
+ const depth = Math.random() * 5 + 2;
99
+ const buildingGeometry = new THREE.BoxGeometry(width, height, depth);
100
+ const buildingMaterial = new THREE.MeshLambertMaterial({ color: 0xb0b0b0 });
101
+ const building = new THREE.Mesh(buildingGeometry, buildingMaterial);
102
+ building.position.x = Math.random() * 80 - 40;
103
+ building.position.y = height / 2;
104
+ building.position.z = Math.random() * 80 - 40;
105
+ scene.add(building);
106
+ buildings.push(building);
107
+ }
108
 
109
+ // Hugging Face Emoji
110
+ const emojiCanvas = document.createElement('canvas');
111
+ emojiCanvas.width = 256;
112
+ emojiCanvas.height = 256;
113
+ const emojiCtx = emojiCanvas.getContext('2d');
114
+ emojiCtx.font = '128px Arial';
115
+ emojiCtx.textAlign = 'center';
116
+ emojiCtx.textBaseline = 'middle';
117
+ emojiCtx.fillText('🤗', 128, 128);
118
+ const emojiTexture = new THREE.CanvasTexture(emojiCanvas);
119
+ const faceGeometry = new THREE.SphereGeometry(1, 32, 32);
120
+ const faceMaterial = new THREE.MeshLambertMaterial({ map: emojiTexture });
121
+ const face = new THREE.Mesh(faceGeometry, faceMaterial);
122
+ face.position.set(0, 5, 0);
123
+ scene.add(face);
124
+ face.velocity = new THREE.Vector3(0, 0, 0);
125
+ face.maxSpeed = 25;
126
+ face.acceleration = new THREE.Vector3(0, 0, 0);
127
+ face.friction = 0.95;
128
+
129
+ // "GPU" Texts
130
+ const canvas = document.createElement('canvas');
131
+ canvas.width = 128;
132
+ canvas.height = 128;
133
+ const ctx = canvas.getContext('2d');
134
+ ctx.font = '48px Arial';
135
+ ctx.fillStyle = 'white';
136
+ ctx.textAlign = 'center';
137
+ ctx.textBaseline = 'middle';
138
+ ctx.fillText('GPU', 64, 64);
139
+ const texture = new THREE.CanvasTexture(canvas);
140
+ const gpuSprites = [];
141
+ const totalGPUs = 10;
142
+ let collectedGPUs = 0;
143
+ for (let i = 0; i < totalGPUs; i++) {
144
+ const spriteMaterial = new THREE.SpriteMaterial({ map: texture });
145
+ const sprite = new THREE.Sprite(spriteMaterial);
146
+ sprite.scale.set(2, 2, 1);
147
+ sprite.position.set(Math.random() * 80 - 40, Math.random() * 20 + 10, Math.random() * 80 - 40);
148
+ scene.add(sprite);
149
+ gpuSprites.push(sprite);
150
  }
151
 
152
+ // Sound setup (Web Audio API)
153
+ const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
154
+
155
+ // Background music (simple looping oscillator)
156
+ const bgOscillator = audioCtx.createOscillator();
157
+ const bgGain = audioCtx.createGain();
158
+ bgOscillator.type = 'sine';
159
+ bgOscillator.frequency.setValueAtTime(220, audioCtx.currentTime); // Low hum
160
+ bgGain.gain.setValueAtTime(0.1, audioCtx.currentTime); // Quiet volume
161
+ bgOscillator.connect(bgGain);
162
+ bgGain.connect(audioCtx.destination);
163
+ bgOscillator.start();
164
+
165
+ // GPU collect sound
166
+ function playCollectSound() {
167
+ const oscillator = audioCtx.createOscillator();
168
+ const gain = audioCtx.createGain();
169
+ oscillator.type = 'square';
170
+ oscillator.frequency.setValueAtTime(880, audioCtx.currentTime); // High pitch
171
+ gain.gain.setValueAtTime(0.3, audioCtx.currentTime);
172
+ gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.2);
173
+ oscillator.connect(gain);
174
+ gain.connect(audioCtx.destination);
175
+ oscillator.start();
176
+ oscillator.stop(audioCtx.currentTime + 0.2);
177
  }
178
 
179
+ // Clock for smooth animation
180
+ const clock = new THREE.Clock();
 
 
 
 
 
181
 
182
+ // Win condition and counter
183
+ const winMessage = document.getElementById('win-message');
184
+ const gpuCounter = document.getElementById('gpu-counter');
185
+ let gameWon = false;
186
+
187
+ // Controls instructions
188
+ const controlsDiv = document.getElementById('controls');
189
+ let controlsVisible = true;
190
+ setTimeout(() => {
191
+ if (controlsVisible) {
192
+ controlsDiv.style.opacity = '0';
193
+ setTimeout(() => controlsDiv.style.display = 'none', 1000);
194
+ controlsVisible = false;
 
195
  }
196
+ }, 5000); // Hide after 5 seconds
197
+
198
+ // Keyboard controls
199
+ const keys = {};
200
+ window.addEventListener('keydown', (e) => {
201
+ keys[e.key] = true;
202
+ if (controlsVisible) {
203
+ controlsDiv.style.opacity = '0';
204
+ setTimeout(() => controlsDiv.style.display = 'none', 1000);
205
+ controlsVisible = false;
 
 
 
206
  }
207
+ });
208
+ window.addEventListener('keyup', (e) => { keys[e.key] = false; });
209
+
210
+ function handleControls(deltaTime) {
211
+ const accel = 50;
212
+ face.acceleration.set(0, 0, 0);
213
+
214
+ if (keys['w'] || keys['ArrowUp']) face.acceleration.z = -accel;
215
+ if (keys['s'] || keys['ArrowDown']) face.acceleration.z = accel;
216
+ if (keys['a'] || keys['ArrowLeft']) face.acceleration.x = -accel;
217
+ if (keys['d'] || keys['ArrowRight']) face.acceleration.x = accel;
218
+ if (keys[' ']) face.acceleration.y = accel;
219
+ if (keys['Shift']) face.acceleration.y = -accel;
220
+
221
+ // Apply acceleration and friction
222
+ face.velocity.add(face.acceleration.clone().multiplyScalar(deltaTime));
223
+ face.velocity.multiplyScalar(face.friction);
224
+ face.velocity.clampLength(0, face.maxSpeed);
225
+
226
+ // Update position
227
+ face.position.add(face.velocity.clone().multiplyScalar(deltaTime));
228
+
229
+ // Face direction of movement
230
+ if (face.velocity.length() > 0.1) {
231
+ face.lookAt(face.position.clone().add(face.velocity));
 
 
 
 
 
232
  }
 
 
233
 
234
+ // Keep within bounds
235
+ face.position.x = Math.max(-50, Math.min(50, face.position.x));
236
+ face.position.z = Math.max(-50, Math.min(50, face.position.z));
237
+ face.position.y = Math.max(1, Math.min(50, face.position.y));
238
  }
239
 
240
+ // Animation loop
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
  function animate() {
242
  requestAnimationFrame(animate);
243
+ const deltaTime = clock.getDelta();
244
+
245
+ if (!gameWon) {
246
+ // Handle player controls
247
+ handleControls(deltaTime);
248
+
249
+ // Check for "GPU" collisions
250
+ gpuSprites.forEach((gpu, index) => {
251
+ if (face.position.distanceTo(gpu.position) < 1) {
252
+ scene.remove(gpu);
253
+ gpuSprites.splice(index, 1);
254
+ collectedGPUs++;
255
+ gpuCounter.textContent = `GPUs Collected: ${collectedGPUs} / ${totalGPUs}`;
256
+ playCollectSound(); // Play sound on collection
257
+ if (collectedGPUs === totalGPUs) {
258
+ gameWon = true;
259
+ winMessage.style.display = 'block';
260
+ face.velocity.set(0, 0, 0);
261
+ bgOscillator.stop(); // Stop background music
 
 
 
 
 
 
 
 
 
 
 
 
262
  }
263
  }
264
+ });
 
265
  }
266
 
267
+ // Update camera
268
+ camera.position.set(face.position.x, face.position.y + 5, face.position.z - 10);
269
+ camera.lookAt(face.position);
270
+
271
+ // Render
272
  renderer.render(scene, camera);
273
  }
274
+
275
  animate();
276
 
277
+ // Handle window resize
278
  window.addEventListener('resize', () => {
279
  camera.aspect = window.innerWidth / window.innerHeight;
280
  camera.updateProjectionMatrix();