awacke1 commited on
Commit
47aab95
·
verified ·
1 Parent(s): c5d5414

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +253 -568
index.html CHANGED
@@ -5,32 +5,16 @@
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>3D City Scene</title>
7
  <style>
8
- body {
9
- margin: 0;
10
- overflow: hidden;
11
- font-family: Arial, sans-serif;
12
- }
13
- canvas {
14
- display: block;
15
- }
16
  .ui-container {
17
- position: absolute;
18
- top: 10px;
19
- left: 10px;
20
- color: white;
21
- background-color: rgba(0, 0, 0, 0.5);
22
- padding: 10px;
23
- border-radius: 5px;
24
  user-select: none;
25
  }
26
  .controls {
27
- position: absolute;
28
- bottom: 10px;
29
- left: 10px;
30
- color: white;
31
- background-color: rgba(0, 0, 0, 0.5);
32
- padding: 10px;
33
- border-radius: 5px;
34
  }
35
  </style>
36
  </head>
@@ -41,375 +25,175 @@
41
  <div id="time">Time: 60</div>
42
  </div>
43
  <div class="controls">
44
- <p>Controls: W to move backward, S to move forward, A/D to move left/right, Mouse to look, Space to jump</p>
45
  <p>Collect the floating cubes to score points!</p>
46
  </div>
47
 
48
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
49
- <script>
50
- // Polyfill for tqdm since it's not defined
51
- const tqdm = {
52
- tqdm: function(desc, total) {
53
- return {
54
- update: function() {},
55
- // Other methods as needed
56
- };
57
- }
58
- };
59
- </script>
60
  <script>
61
  // Game variables
62
- let score = 0;
63
- let timeRemaining = 60;
64
- let gameActive = true;
65
 
66
- // Set up scene
67
  const scene = new THREE.Scene();
68
- scene.background = new THREE.Color(0x87CEEB); // Sky blue background
69
-
70
- // Set up camera
71
  const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
72
  camera.position.set(0, 5, 15);
73
-
74
- // Set up renderer
75
  const renderer = new THREE.WebGLRenderer({ antialias: true });
76
  renderer.setSize(window.innerWidth, window.innerHeight);
77
  renderer.shadowMap.enabled = true;
78
  document.body.appendChild(renderer.domElement);
79
 
80
- // Add lights
81
- const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
82
  scene.add(ambientLight);
83
 
84
- const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
85
- directionalLight.position.set(50, 50, 50);
86
- directionalLight.castShadow = true;
87
- directionalLight.shadow.mapSize.width = 2048;
88
- directionalLight.shadow.mapSize.height = 2048;
89
- directionalLight.shadow.camera.near = 1;
90
- directionalLight.shadow.camera.far = 500;
91
- directionalLight.shadow.camera.left = -100;
92
- directionalLight.shadow.camera.right = 100;
93
- directionalLight.shadow.camera.top = 100;
94
- directionalLight.shadow.camera.bottom = -100;
95
- scene.add(directionalLight);
96
 
97
- // Create ground
 
 
 
 
 
 
 
98
  const groundGeometry = new THREE.PlaneGeometry(200, 200);
99
  const groundMaterial = new THREE.MeshStandardMaterial({
100
- color: 0x1a5e1a, // Green
101
  roughness: 0.8,
102
- metalness: 0.2
 
 
103
  });
104
  const ground = new THREE.Mesh(groundGeometry, groundMaterial);
105
  ground.rotation.x = -Math.PI / 2;
106
  ground.receiveShadow = true;
107
  scene.add(ground);
108
 
109
- // Buildings array
110
- const buildings = [];
111
- const collectibles = [];
112
-
113
- // Building color palette
114
- const buildingColors = [
115
- 0x888888, 0x666666, 0x999999, 0xaaaaaa, 0x555555,
116
- 0x334455, 0x445566, 0x223344, 0x556677, 0x667788,
117
- 0x993333, 0x884422, 0x553333, 0x772222, 0x664433
118
- ];
119
 
120
- // L-system grammar rules for buildings
121
  const buildingRules = [
122
- // Colonial style - symmetrical with central features
123
- {
124
- name: "Colonial",
125
- axiom: "A",
126
- rules: {
127
- "A": "B[+F][-F]",
128
- "B": "F[-C][+C]F",
129
- "C": "D[-E][+E]",
130
- "D": "F[+F][-F]F",
131
- "E": "F[-F][+F]"
132
- },
133
- iterations: 2,
134
- baseHeight: 10,
135
- baseWidth: 6,
136
- baseDepth: 6,
137
- angle: Math.PI/6,
138
- probability: 0.2
139
- },
140
- // Victorian style - complex with many decorative elements
141
- {
142
- name: "Victorian",
143
- axiom: "A",
144
- rules: {
145
- "A": "B[+C][-C][/D][\\D]",
146
- "B": "F[+F][-F][/F][\\F]",
147
- "C": "F[++F][--F]",
148
- "D": "F[+\F][-/F]"
149
- },
150
- iterations: 3,
151
- baseHeight: 15,
152
- baseWidth: 5,
153
- baseDepth: 5,
154
- angle: Math.PI/5,
155
- probability: 0.15
156
- },
157
- // Modern style - clean lines, boxy but with variations
158
- {
159
- name: "Modern",
160
- axiom: "A",
161
- rules: {
162
- "A": "B[+B][-B]",
163
- "B": "F[/C][\\C]",
164
- "C": "F"
165
- },
166
- iterations: 2,
167
- baseHeight: 20,
168
- baseWidth: 8,
169
- baseDepth: 8,
170
- angle: Math.PI/2,
171
- probability: 0.25
172
- },
173
- // Skyscraper - tall vertical structures
174
- {
175
- name: "Skyscraper",
176
- axiom: "A",
177
- rules: {
178
- "A": "FB[+C][-C]",
179
- "B": "FB",
180
- "C": "F[+F][-F]"
181
- },
182
- iterations: 4,
183
- baseHeight: 30,
184
- baseWidth: 10,
185
- baseDepth: 10,
186
- angle: Math.PI/8,
187
- probability: 0.15
188
- },
189
- // Simple box building - for variety and filling space
190
- {
191
- name: "Simple",
192
- axiom: "F",
193
- rules: {
194
- "F": "F[+F][-F]"
195
- },
196
- iterations: 1,
197
- baseHeight: 8,
198
- baseWidth: 6,
199
- baseDepth: 6,
200
- angle: Math.PI/4,
201
- probability: 0.25
202
- }
203
  ];
204
-
205
- // L-system interpreter
206
  function interpretLSystem(rule, position, rotation) {
207
- // Start with the axiom
208
  let currentString = rule.axiom;
209
-
210
- // Apply rules for the specified number of iterations
211
  for (let i = 0; i < rule.iterations; i++) {
212
  let newString = "";
213
-
214
- // Apply rules to each character
215
  for (let j = 0; j < currentString.length; j++) {
216
- const char = currentString[j];
217
- newString += rule.rules[char] || char;
218
  }
219
-
220
  currentString = newString;
221
  }
222
 
223
- // Now interpret the L-system string to create building parts
224
  let buildingGroup = new THREE.Group();
225
  buildingGroup.position.copy(position);
226
-
227
- // Stack to keep track of transformations
228
  const stack = [];
229
  let currentPosition = new THREE.Vector3(0, 0, 0);
230
  let currentRotation = rotation || new THREE.Euler();
231
  let scale = new THREE.Vector3(1, 1, 1);
232
-
233
- // Select a material for this building
234
  const color = buildingColors[Math.floor(Math.random() * buildingColors.length)];
235
- const material = new THREE.MeshStandardMaterial({
236
- color: color,
237
- roughness: 0.7,
238
- metalness: 0.2
239
- });
240
 
241
- // Interpret each character in the final string
242
  for (let i = 0; i < currentString.length; i++) {
243
  const char = currentString[i];
244
-
245
  switch (char) {
246
- case 'F': // Forward and create a building part
247
- // Randomize dimensions with constraints based on rule
248
  const width = rule.baseWidth * (0.5 + Math.random() * 0.5) * scale.x;
249
  const height = rule.baseHeight * (0.5 + Math.random() * 0.5) * scale.y;
250
  const depth = rule.baseDepth * (0.5 + Math.random() * 0.5) * scale.z;
251
-
252
- // Create geometry
253
  const geometry = new THREE.BoxGeometry(width, height, depth);
254
  const buildingPart = new THREE.Mesh(geometry, material);
255
-
256
- // Position and add to group
257
  buildingPart.position.copy(currentPosition);
 
258
  buildingPart.rotation.copy(currentRotation);
259
  buildingPart.castShadow = true;
260
  buildingPart.receiveShadow = true;
261
-
262
- // Add windows if part is large enough
263
  if (height > 5 && width > 2 && depth > 2) {
264
  addWindowsToBuilding(buildingPart, width, height, depth);
265
  }
266
-
267
  buildingGroup.add(buildingPart);
268
-
269
- // Move forward in the direction of current rotation
270
- const direction = new THREE.Vector3(0, height/2, 0);
271
  direction.applyEuler(currentRotation);
272
  currentPosition.add(direction);
273
  break;
274
-
275
- case '+': // Rotate right around Y axis
276
- currentRotation.y += rule.angle;
277
- break;
278
-
279
- case '-': // Rotate left around Y axis
280
- currentRotation.y -= rule.angle;
281
- break;
282
-
283
- case '/': // Rotate around X axis
284
- currentRotation.x += rule.angle;
285
- break;
286
-
287
- case '\\': // Rotate around X axis (opposite)
288
- currentRotation.x -= rule.angle;
289
- break;
290
-
291
- case '^': // Rotate around Z axis
292
- currentRotation.z += rule.angle;
293
- break;
294
-
295
- case '&': // Rotate around Z axis (opposite)
296
- currentRotation.z -= rule.angle;
297
- break;
298
-
299
- case '[': // Push state
300
- stack.push({
301
- position: currentPosition.clone(),
302
- rotation: currentRotation.clone(),
303
- scale: scale.clone()
304
- });
305
- break;
306
-
307
- case ']': // Pop state
308
- if (stack.length > 0) {
309
- const state = stack.pop();
310
- currentPosition = state.position;
311
- currentRotation = state.rotation;
312
- scale = state.scale;
313
- }
314
- break;
315
-
316
- case '>': // Scale up
317
- scale.multiplyScalar(1.2);
318
- break;
319
-
320
- case '<': // Scale down
321
- scale.multiplyScalar(0.8);
322
- break;
323
  }
324
  }
325
-
326
  return buildingGroup;
327
  }
328
 
329
- // Create a city
330
  function createCity() {
331
- // Create grid of buildings
332
- const citySize = 5; // Size of the city grid
333
- const spacing = 15; // Spacing between building centers
334
-
335
  for (let x = -citySize; x <= citySize; x++) {
336
  for (let z = -citySize; z <= citySize; z++) {
337
- // Skip sometimes to create spaces
338
  if (Math.random() < 0.2) continue;
339
-
340
- // Position with slight randomization
341
- const position = new THREE.Vector3(
342
- x * spacing + (Math.random() * 2 - 1), // Add slight randomness
343
- 0, // Will be adjusted by the L-system
344
- z * spacing + (Math.random() * 2 - 1)
345
- );
346
-
347
- // Select a building style based on probability
348
- let selectedRule = null;
349
- let random = Math.random();
350
- let cumulativeProbability = 0;
351
-
352
  for (const rule of buildingRules) {
353
  cumulativeProbability += rule.probability;
354
- if (random <= cumulativeProbability) {
355
- selectedRule = rule;
356
- break;
357
- }
358
- }
359
-
360
- if (!selectedRule) {
361
- selectedRule = buildingRules[0]; // Default to first rule if somehow none selected
362
  }
363
-
364
- // Create building using L-system
365
  const building = interpretLSystem(selectedRule, position, new THREE.Euler());
366
  scene.add(building);
367
  buildings.push(building);
368
  }
369
  }
370
-
371
- // Create streets
372
- const roadWidth = 8;
373
- const roadColor = 0x333333;
374
-
375
- // X-axis roads
376
  for (let x = -citySize; x <= citySize; x++) {
377
  const roadGeometry = new THREE.PlaneGeometry(roadWidth, citySize * 2 * spacing + roadWidth);
378
- const roadMaterial = new THREE.MeshStandardMaterial({
379
- color: roadColor,
380
- roughness: 0.9,
381
- metalness: 0.1
382
- });
383
  const road = new THREE.Mesh(roadGeometry, roadMaterial);
384
  road.rotation.x = -Math.PI / 2;
385
- road.position.set(x * spacing, 0.01, 0); // Slightly above ground to prevent z-fighting
386
  scene.add(road);
387
-
388
- // Add road markings
389
  const markingGeometry = new THREE.PlaneGeometry(0.5, citySize * 2 * spacing + roadWidth);
390
- const markingMaterial = new THREE.MeshStandardMaterial({ color: 0xffffff });
391
  const marking = new THREE.Mesh(markingGeometry, markingMaterial);
392
  marking.rotation.x = -Math.PI / 2;
393
  marking.position.set(x * spacing, 0.02, 0);
394
  scene.add(marking);
395
  }
396
-
397
- // Z-axis roads
398
  for (let z = -citySize; z <= citySize; z++) {
399
  const roadGeometry = new THREE.PlaneGeometry(citySize * 2 * spacing + roadWidth, roadWidth);
400
- const roadMaterial = new THREE.MeshStandardMaterial({
401
- color: roadColor,
402
- roughness: 0.9,
403
- metalness: 0.1
404
- });
405
  const road = new THREE.Mesh(roadGeometry, roadMaterial);
406
  road.rotation.x = -Math.PI / 2;
407
  road.position.set(0, 0.01, z * spacing);
408
  scene.add(road);
409
-
410
- // Add road markings
411
  const markingGeometry = new THREE.PlaneGeometry(citySize * 2 * spacing + roadWidth, 0.5);
412
- const markingMaterial = new THREE.MeshStandardMaterial({ color: 0xffffff });
413
  const marking = new THREE.Mesh(markingGeometry, markingMaterial);
414
  marking.rotation.x = -Math.PI / 2;
415
  marking.position.set(0, 0.02, z * spacing);
@@ -417,248 +201,110 @@
417
  }
418
  }
419
 
420
- // Add windows to a building
421
  function addWindowsToBuilding(building, width, height, depth) {
422
- const windowSize = 0.5;
423
- const windowSpacing = 1.5;
424
  const windowGeometry = new THREE.PlaneGeometry(windowSize, windowSize);
425
- const windowMaterial = new THREE.MeshStandardMaterial({
426
- color: 0xffffcc,
427
- emissive: 0xffffcc,
428
- emissiveIntensity: 0.5,
429
- transparent: true,
430
- opacity: 0.8
431
  });
432
-
433
- // Calculate how many levels of windows to add based on building height
434
  const numLevels = Math.floor((height - 2) / windowSpacing);
435
 
436
- // Front and back windows
437
- const frontZ = depth / 2 + 0.01;
438
- const backZ = -depth / 2 - 0.01;
439
-
440
  for (let level = 0; level < numLevels; level++) {
441
- // Calculate y position for this level, starting from near the bottom
442
- const y = 1 + level * windowSpacing;
443
-
444
- for (let x = -width / 2 + windowSpacing; x < width / 2 - windowSpacing / 2; x += windowSpacing) {
445
- // Only add some windows randomly
446
  if (Math.random() < 0.3) continue;
447
-
448
- // Front window
449
  const frontWindow = new THREE.Mesh(windowGeometry, windowMaterial);
450
- frontWindow.position.set(x, y, frontZ);
451
- frontWindow.rotation.y = Math.PI;
452
  building.add(frontWindow);
453
-
454
- // Back window
455
  const backWindow = new THREE.Mesh(windowGeometry, windowMaterial);
456
- backWindow.position.set(x, y, backZ);
 
457
  building.add(backWindow);
458
  }
459
  }
460
 
461
- // Side windows
462
- const rightX = width / 2 + 0.01;
463
- const leftX = -width / 2 - 0.01;
464
-
465
  for (let level = 0; level < numLevels; level++) {
466
- // Calculate y position for this level, starting from near the bottom
467
- const y = 1 + level * windowSpacing;
468
-
469
- for (let z = -depth / 2 + windowSpacing; z < depth / 2 - windowSpacing / 2; z += windowSpacing) {
470
- // Only add some windows randomly
471
  if (Math.random() < 0.3) continue;
472
-
473
- // Right window
474
  const rightWindow = new THREE.Mesh(windowGeometry, windowMaterial);
475
- rightWindow.position.set(rightX, y, z);
476
- rightWindow.rotation.y = Math.PI / 2;
477
  building.add(rightWindow);
478
-
479
- // Left window
480
  const leftWindow = new THREE.Mesh(windowGeometry, windowMaterial);
481
- leftWindow.position.set(leftX, y, z);
482
- leftWindow.rotation.y = -Math.PI / 2;
483
  building.add(leftWindow);
484
  }
485
  }
486
  }
487
 
488
- // Create collectible items
489
  function createCollectibles() {
490
- const citySize = 5;
491
- const spacing = 15;
492
-
493
  for (let i = 0; i < 20; i++) {
 
 
 
 
494
  const x = (Math.random() * 2 - 1) * citySize * spacing;
495
  const z = (Math.random() * 2 - 1) * citySize * spacing;
496
  const y = 1 + Math.random() * 20;
497
-
498
- const collectibleGeometry = new THREE.BoxGeometry(1, 1, 1);
499
- const collectibleMaterial = new THREE.MeshStandardMaterial({
500
- color: 0xffff00,
501
- emissive: 0xffff00,
502
- emissiveIntensity: 0.5,
503
- transparent: true,
504
- opacity: 0.8
505
- });
506
-
507
- const collectible = new THREE.Mesh(collectibleGeometry, collectibleMaterial);
508
  collectible.position.set(x, y, z);
509
- collectible.userData.id = i;
510
- collectible.userData.rotationSpeed = 0.01 + Math.random() * 0.02;
511
- collectible.userData.floatSpeed = 0.5 + Math.random() * 0.5;
512
- collectible.userData.floatRange = 0.5 + Math.random() * 0.5;
513
- collectible.userData.initialY = y;
514
-
515
  scene.add(collectible);
516
  collectibles.push(collectible);
517
  }
518
  }
519
 
520
- // Create skybox with clouds
521
  function createSkybox() {
522
- const skyGeometry = new THREE.SphereGeometry(400, 32, 32);
523
- const skyMaterial = new THREE.MeshBasicMaterial({
524
- color: 0x87CEEB,
525
- side: THREE.BackSide
526
- });
527
- const sky = new THREE.Mesh(skyGeometry, skyMaterial);
528
  scene.add(sky);
529
-
530
- // Add clouds
531
  for (let i = 0; i < 50; i++) {
532
- const radius = 350;
533
- const phi = Math.random() * Math.PI;
534
- const theta = Math.random() * Math.PI * 2;
535
-
536
- const x = radius * Math.sin(phi) * Math.cos(theta);
537
- const y = radius * Math.cos(phi) + 50; // Keep clouds higher in the sky
538
- const z = radius * Math.sin(phi) * Math.sin(theta);
539
-
540
- const cloudSize = 10 + Math.random() * 20;
541
- const cloudGeometry = new THREE.SphereGeometry(cloudSize, 8, 8);
542
- const cloudMaterial = new THREE.MeshStandardMaterial({
543
- color: 0xffffff,
544
- roughness: 1,
545
- metalness: 0,
546
- transparent: true,
547
- opacity: 0.8
548
- });
549
-
550
- const cloud = new THREE.Mesh(cloudGeometry, cloudMaterial);
551
- cloud.position.set(x, y, z);
552
  cloud.userData.rotationSpeed = 0.0001 + Math.random() * 0.0002;
553
-
554
  scene.add(cloud);
555
  }
556
  }
557
 
558
- // Player object
559
- const playerHeight = 2;
560
- const playerRadius = 0.5;
561
- // Use cylinder instead of capsule for compatibility with r128
562
- const playerGeometry = new THREE.CylinderGeometry(playerRadius, playerRadius, playerHeight, 8);
563
- const playerMaterial = new THREE.MeshStandardMaterial({ color: 0x0000ff });
564
- const player = new THREE.Mesh(playerGeometry, playerMaterial);
565
- player.position.set(0, playerHeight / 2, 0);
566
  player.castShadow = true;
567
  scene.add(player);
568
 
569
- // Player physics
570
  const playerVelocity = new THREE.Vector3();
571
  const playerDirection = new THREE.Vector3();
572
  let isJumping = false;
573
- const GRAVITY = 0.2;
574
- const JUMP_FORCE = 0.7;
575
- const MOVE_SPEED = 0.2;
576
-
577
- // Player collision detection
578
- function checkPlayerCollisions() {
579
- // Collectible collisions
580
- for (let i = collectibles.length - 1; i >= 0; i--) {
581
- const collectible = collectibles[i];
582
- const distance = player.position.distanceTo(collectible.position);
583
-
584
- if (distance < playerRadius + 1) {
585
- scene.remove(collectible);
586
- collectibles.splice(i, 1);
587
- score += 10;
588
- document.getElementById('score').textContent = `Score: ${score}`;
589
- }
590
- }
591
-
592
- // Building collisions - updated for composite buildings
593
- for (const building of buildings) {
594
- // For each building group, we need to check collision with each child
595
- building.traverse((child) => {
596
- if (child.isMesh) {
597
- const buildingBox = new THREE.Box3().setFromObject(child);
598
- const playerPos = player.position.clone();
599
-
600
- // Check if player is inside building bounds but add some margin for the radius
601
- if (playerPos.x + playerRadius > buildingBox.min.x &&
602
- playerPos.x - playerRadius < buildingBox.max.x &&
603
- playerPos.z + playerRadius > buildingBox.min.z &&
604
- playerPos.z - playerRadius < buildingBox.max.z) {
605
-
606
- // Find the closest edge to push the player away from
607
- const edgeDistances = [
608
- {edge: 'left', dist: Math.abs(playerPos.x - buildingBox.min.x)},
609
- {edge: 'right', dist: Math.abs(playerPos.x - buildingBox.max.x)},
610
- {edge: 'front', dist: Math.abs(playerPos.z - buildingBox.min.z)},
611
- {edge: 'back', dist: Math.abs(playerPos.z - buildingBox.max.z)}
612
- ];
613
-
614
- edgeDistances.sort((a, b) => a.dist - b.dist);
615
- const closestEdge = edgeDistances[0].edge;
616
-
617
- // Push player away from the closest edge
618
- switch (closestEdge) {
619
- case 'left':
620
- player.position.x = buildingBox.min.x - playerRadius;
621
- break;
622
- case 'right':
623
- player.position.x = buildingBox.max.x + playerRadius;
624
- break;
625
- case 'front':
626
- player.position.z = buildingBox.min.z - playerRadius;
627
- break;
628
- case 'back':
629
- player.position.z = buildingBox.max.z + playerRadius;
630
- break;
631
- }
632
- }
633
- }
634
- });
635
- }
636
-
637
- // Floor collision and jumping physics
638
- if (player.position.y <= playerHeight / 2) {
639
- player.position.y = playerHeight / 2;
640
- playerVelocity.y = 0;
641
- isJumping = false;
642
- }
643
- }
644
-
645
- // Controls
646
- const keys = {
647
- w: false,
648
- a: false,
649
- s: false,
650
- d: false,
651
- space: false
652
- };
653
 
654
- // Mouse controls for looking around
655
- const mousePosition = {
656
- x: 0,
657
- y: 0
658
- };
659
-
660
- let cameraRotation = 0;
661
- let cameraPitch = 0;
662
 
663
  document.addEventListener('keydown', (event) => {
664
  switch (event.key.toLowerCase()) {
@@ -667,6 +313,7 @@
667
  case 's': keys.s = true; break;
668
  case 'd': keys.d = true; break;
669
  case ' ': keys.space = true; break;
 
670
  }
671
  });
672
 
@@ -677,72 +324,157 @@
677
  case 's': keys.s = false; break;
678
  case 'd': keys.d = false; break;
679
  case ' ': keys.space = false; break;
 
680
  }
681
  });
682
 
683
  document.addEventListener('mousemove', (event) => {
684
- // Only capture mouse if pointer is locked
685
  if (document.pointerLockElement === renderer.domElement) {
686
  cameraRotation -= event.movementX * 0.002;
687
  cameraPitch -= event.movementY * 0.002;
688
-
689
- // Limit pitch to prevent camera flipping
690
  cameraPitch = Math.max(-Math.PI / 2 + 0.1, Math.min(Math.PI / 2 - 0.1, cameraPitch));
691
  }
692
  });
693
 
694
- // Lock pointer when clicking on the game
695
  renderer.domElement.addEventListener('click', () => {
696
- if (!document.pointerLockElement) {
697
- renderer.domElement.requestPointerLock();
698
- }
699
  });
700
 
701
- // Update player position based on input
702
- function updatePlayer() {
703
- // Apply gravity
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
704
  playerVelocity.y -= GRAVITY;
705
-
706
- // Handle jumping
707
  if (keys.space && !isJumping) {
708
  playerVelocity.y = JUMP_FORCE;
709
  isJumping = true;
710
  }
711
-
712
- // Get movement direction based on camera rotation
713
- // Switching W and S keys (W now moves backward, S moves forward)
714
  playerDirection.z = Number(keys.s) - Number(keys.w);
715
  playerDirection.x = Number(keys.d) - Number(keys.a);
716
  playerDirection.normalize();
717
-
718
- // Rotate direction based on camera rotation
719
  playerDirection.applyAxisAngle(new THREE.Vector3(0, 1, 0), cameraRotation);
720
-
721
- // Apply movement
722
- player.position.x += playerDirection.x * MOVE_SPEED;
723
- player.position.z += playerDirection.z * MOVE_SPEED;
724
  player.position.y += playerVelocity.y;
725
-
726
- // Update camera position to follow player
727
- camera.position.x = player.position.x;
728
- camera.position.z = player.position.z;
729
- camera.position.y = player.position.y + 1.5; // Eye level
730
-
731
- // Update camera rotation
732
- camera.rotation.order = 'YXZ'; // Important for proper rotation
733
  camera.rotation.y = cameraRotation;
734
  camera.rotation.x = cameraPitch;
735
-
736
- // Collision detection
737
  checkPlayerCollisions();
738
  }
739
 
740
- // Timer function
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
741
  function updateTimer() {
742
  if (gameActive && timeRemaining > 0) {
743
  timeRemaining--;
744
  document.getElementById('time').textContent = `Time: ${timeRemaining}`;
745
-
746
  if (timeRemaining === 0) {
747
  gameActive = false;
748
  endGame();
@@ -750,72 +482,29 @@
750
  }
751
  }
752
 
753
- // End game
754
  function endGame() {
755
  const endScreen = document.createElement('div');
756
- endScreen.style.position = 'absolute';
757
- endScreen.style.top = '50%';
758
- endScreen.style.left = '50%';
759
- endScreen.style.transform = 'translate(-50%, -50%)';
760
- endScreen.style.background = 'rgba(0, 0, 0, 0.8)';
761
- endScreen.style.color = 'white';
762
- endScreen.style.padding = '20px';
763
- endScreen.style.borderRadius = '10px';
764
- endScreen.style.textAlign = 'center';
765
  endScreen.innerHTML = `
766
  <h2>Game Over!</h2>
767
  <p>Your final score: ${score}</p>
768
- <button id="restart-btn" style="padding: 10px 20px; background: #4CAF50; color: white; border: none; border-radius: 5px; cursor: pointer; margin-top: 10px;">Play Again</button>
769
  `;
770
  document.body.appendChild(endScreen);
771
-
772
  document.getElementById('restart-btn').addEventListener('click', () => {
773
  document.body.removeChild(endScreen);
774
- score = 0;
775
- timeRemaining = 60;
776
- gameActive = true;
777
  document.getElementById('score').textContent = `Score: ${score}`;
778
  document.getElementById('time').textContent = `Time: ${timeRemaining}`;
779
-
780
- // Reset player position
781
- player.position.set(0, playerHeight / 2, 0);
782
  playerVelocity.set(0, 0, 0);
783
-
784
- // Reset camera
785
- cameraRotation = 0;
786
- cameraPitch = 0;
787
-
788
- // Reset collectibles
789
- for (const collectible of collectibles) {
790
- scene.remove(collectible);
791
- }
792
  collectibles.length = 0;
793
  createCollectibles();
794
  });
795
  }
796
 
797
- // Animation and game loop
798
- function animate() {
799
- requestAnimationFrame(animate);
800
-
801
- if (gameActive) {
802
- updatePlayer();
803
-
804
- // Animate collectibles
805
- for (const collectible of collectibles) {
806
- collectible.rotation.x += collectible.userData.rotationSpeed;
807
- collectible.rotation.y += collectible.userData.rotationSpeed * 1.5;
808
-
809
- // Float up and down
810
- const floatOffset = Math.sin(Date.now() * 0.001 * collectible.userData.floatSpeed) * collectible.userData.floatRange;
811
- collectible.position.y = collectible.userData.initialY + floatOffset;
812
- }
813
- }
814
-
815
- renderer.render(scene, camera);
816
- }
817
-
818
- // Handle window resize
819
  window.addEventListener('resize', () => {
820
  camera.aspect = window.innerWidth / window.innerHeight;
821
  camera.updateProjectionMatrix();
@@ -826,11 +515,7 @@
826
  createCity();
827
  createCollectibles();
828
  createSkybox();
829
-
830
- // Start game timer
831
  setInterval(updateTimer, 1000);
832
-
833
- // Start animation loop
834
  animate();
835
  </script>
836
  </body>
 
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>3D City Scene</title>
7
  <style>
8
+ body { margin: 0; overflow: hidden; font-family: Arial, sans-serif; }
9
+ canvas { display: block; }
 
 
 
 
 
 
10
  .ui-container {
11
+ position: absolute; top: 10px; left: 10px; color: white;
12
+ background-color: rgba(0, 0, 0, 0.5); padding: 10px; border-radius: 5px;
 
 
 
 
 
13
  user-select: none;
14
  }
15
  .controls {
16
+ position: absolute; bottom: 10px; left: 10px; color: white;
17
+ background-color: rgba(0, 0, 0, 0.5); padding: 10px; border-radius: 5px;
 
 
 
 
 
18
  }
19
  </style>
20
  </head>
 
25
  <div id="time">Time: 60</div>
26
  </div>
27
  <div class="controls">
28
+ <p>Controls: W/A/S/D to move, Mouse to look, Space to jump, Shift for speed boost</p>
29
  <p>Collect the floating cubes to score points!</p>
30
  </div>
31
 
32
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
 
 
 
 
 
 
 
 
 
 
 
33
  <script>
34
  // Game variables
35
+ let score = 0, timeRemaining = 60, gameActive = true;
 
 
36
 
37
+ // Scene setup
38
  const scene = new THREE.Scene();
 
 
 
39
  const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
40
  camera.position.set(0, 5, 15);
 
 
41
  const renderer = new THREE.WebGLRenderer({ antialias: true });
42
  renderer.setSize(window.innerWidth, window.innerHeight);
43
  renderer.shadowMap.enabled = true;
44
  document.body.appendChild(renderer.domElement);
45
 
46
+ // Lighting
47
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.2);
48
  scene.add(ambientLight);
49
 
50
+ // Sun and Moon
51
+ const sunLight = new THREE.DirectionalLight(0xffddaa, 0.8);
52
+ sunLight.castShadow = true;
53
+ sunLight.shadow.mapSize.width = 2048;
54
+ sunLight.shadow.mapSize.height = 2048;
55
+ sunLight.shadow.camera.near = 1;
56
+ sunLight.shadow.camera.far = 500;
57
+ sunLight.shadow.camera.left = -100;
58
+ sunLight.shadow.camera.right = 100;
59
+ sunLight.shadow.camera.top = 100;
60
+ sunLight.shadow.camera.bottom = -100;
61
+ scene.add(sunLight);
62
 
63
+ const moonLight = new THREE.DirectionalLight(0xaabbff, 0.4);
64
+ moonLight.castShadow = true;
65
+ moonLight.shadow.mapSize.width = 2048;
66
+ moonLight.shadow.mapSize.height = 2048;
67
+ scene.add(moonLight);
68
+
69
+ // Ground with bump mapping
70
+ const textureLoader = new THREE.TextureLoader();
71
  const groundGeometry = new THREE.PlaneGeometry(200, 200);
72
  const groundMaterial = new THREE.MeshStandardMaterial({
73
+ color: 0x1a5e1a,
74
  roughness: 0.8,
75
+ metalness: 0.2,
76
+ bumpMap: textureLoader.load('https://threejs.org/examples/textures/terrain/grasslight-big-nm.jpg'),
77
+ bumpScale: 0.1
78
  });
79
  const ground = new THREE.Mesh(groundGeometry, groundMaterial);
80
  ground.rotation.x = -Math.PI / 2;
81
  ground.receiveShadow = true;
82
  scene.add(ground);
83
 
84
+ // Buildings and collectibles arrays
85
+ const buildings = [], collectibles = [];
86
+ const buildingColors = [0x888888, 0x666666, 0x999999, 0xaaaaaa, 0x555555, 0x334455, 0x445566, 0x223344, 0x556677, 0x667788, 0x993333, 0x884422, 0x553333, 0x772222, 0x664433];
 
 
 
 
 
 
 
87
 
88
+ // Building rules (unchanged)
89
  const buildingRules = [
90
+ {name: "Colonial", axiom: "A", rules: {"A": "B[+F][-F]", "B": "F[-C][+C]F", "C": "D[-E][+E]", "D": "F[+F][-F]F", "E": "F[-F][+F]"}, iterations: 2, baseHeight: 10, baseWidth: 6, baseDepth: 6, angle: Math.PI/6, probability: 0.2},
91
+ {name: "Victorian", axiom: "A", rules: {"A": "B[+C][-C][/D][\\D]", "B": "F[+F][-F][/F][\\F]", "C": "F[++F][--F]", "D": "F[+\F][-/F]"}, iterations: 3, baseHeight: 15, baseWidth: 5, baseDepth: 5, angle: Math.PI/5, probability: 0.15},
92
+ {name: "Modern", axiom: "A", rules: {"A": "B[+B][-B]", "B": "F[/C][\\C]", "C": "F"}, iterations: 2, baseHeight: 20, baseWidth: 8, baseDepth: 8, angle: Math.PI/2, probability: 0.25},
93
+ {name: "Skyscraper", axiom: "A", rules: {"A": "FB[+C][-C]", "B": "FB", "C": "F[+F][-F]"}, iterations: 4, baseHeight: 30, baseWidth: 10, baseDepth: 10, angle: Math.PI/8, probability: 0.15},
94
+ {name: "Simple", axiom: "F", rules: {"F": "F[+F][-F]"}, iterations: 1, baseHeight: 8, baseWidth: 6, baseDepth: 6, angle: Math.PI/4, probability: 0.25}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  ];
96
+
97
+ // L-system interpreter (modified for better window positioning)
98
  function interpretLSystem(rule, position, rotation) {
 
99
  let currentString = rule.axiom;
 
 
100
  for (let i = 0; i < rule.iterations; i++) {
101
  let newString = "";
 
 
102
  for (let j = 0; j < currentString.length; j++) {
103
+ newString += rule.rules[currentString[j]] || currentString[j];
 
104
  }
 
105
  currentString = newString;
106
  }
107
 
 
108
  let buildingGroup = new THREE.Group();
109
  buildingGroup.position.copy(position);
 
 
110
  const stack = [];
111
  let currentPosition = new THREE.Vector3(0, 0, 0);
112
  let currentRotation = rotation || new THREE.Euler();
113
  let scale = new THREE.Vector3(1, 1, 1);
 
 
114
  const color = buildingColors[Math.floor(Math.random() * buildingColors.length)];
115
+ const material = new THREE.MeshStandardMaterial({color: color, roughness: 0.7, metalness: 0.2});
 
 
 
 
116
 
 
117
  for (let i = 0; i < currentString.length; i++) {
118
  const char = currentString[i];
 
119
  switch (char) {
120
+ case 'F':
 
121
  const width = rule.baseWidth * (0.5 + Math.random() * 0.5) * scale.x;
122
  const height = rule.baseHeight * (0.5 + Math.random() * 0.5) * scale.y;
123
  const depth = rule.baseDepth * (0.5 + Math.random() * 0.5) * scale.z;
 
 
124
  const geometry = new THREE.BoxGeometry(width, height, depth);
125
  const buildingPart = new THREE.Mesh(geometry, material);
 
 
126
  buildingPart.position.copy(currentPosition);
127
+ buildingPart.position.y += height / 2; // Center properly
128
  buildingPart.rotation.copy(currentRotation);
129
  buildingPart.castShadow = true;
130
  buildingPart.receiveShadow = true;
 
 
131
  if (height > 5 && width > 2 && depth > 2) {
132
  addWindowsToBuilding(buildingPart, width, height, depth);
133
  }
 
134
  buildingGroup.add(buildingPart);
135
+ const direction = new THREE.Vector3(0, height, 0);
 
 
136
  direction.applyEuler(currentRotation);
137
  currentPosition.add(direction);
138
  break;
139
+ case '+': currentRotation.y += rule.angle; break;
140
+ case '-': currentRotation.y -= rule.angle; break;
141
+ case '/': currentRotation.x += rule.angle; break;
142
+ case '\\': currentRotation.x -= rule.angle; break;
143
+ case '^': currentRotation.z += rule.angle; break;
144
+ case '&': currentRotation.z -= rule.angle; break;
145
+ case '[': stack.push({position: currentPosition.clone(), rotation: currentRotation.clone(), scale: scale.clone()}); break;
146
+ case ']': if (stack.length > 0) { const state = stack.pop(); currentPosition = state.position; currentRotation = state.rotation; scale = state.scale; } break;
147
+ case '>': scale.multiplyScalar(1.2); break;
148
+ case '<': scale.multiplyScalar(0.8); break;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  }
150
  }
 
151
  return buildingGroup;
152
  }
153
 
154
+ // Create city (unchanged)
155
  function createCity() {
156
+ const citySize = 5, spacing = 15;
 
 
 
157
  for (let x = -citySize; x <= citySize; x++) {
158
  for (let z = -citySize; z <= citySize; z++) {
 
159
  if (Math.random() < 0.2) continue;
160
+ const position = new THREE.Vector3(x * spacing + (Math.random() * 2 - 1), 0, z * spacing + (Math.random() * 2 - 1));
161
+ let selectedRule, random = Math.random(), cumulativeProbability = 0;
 
 
 
 
 
 
 
 
 
 
 
162
  for (const rule of buildingRules) {
163
  cumulativeProbability += rule.probability;
164
+ if (random <= cumulativeProbability) { selectedRule = rule; break; }
 
 
 
 
 
 
 
165
  }
166
+ if (!selectedRule) selectedRule = buildingRules[0];
 
167
  const building = interpretLSystem(selectedRule, position, new THREE.Euler());
168
  scene.add(building);
169
  buildings.push(building);
170
  }
171
  }
172
+ // Roads (unchanged)
173
+ const roadWidth = 8, roadColor = 0x333333;
 
 
 
 
174
  for (let x = -citySize; x <= citySize; x++) {
175
  const roadGeometry = new THREE.PlaneGeometry(roadWidth, citySize * 2 * spacing + roadWidth);
176
+ const roadMaterial = new THREE.MeshStandardMaterial({color: roadColor, roughness: 0.9, metalness: 0.1});
 
 
 
 
177
  const road = new THREE.Mesh(roadGeometry, roadMaterial);
178
  road.rotation.x = -Math.PI / 2;
179
+ road.position.set(x * spacing, 0.01, 0);
180
  scene.add(road);
 
 
181
  const markingGeometry = new THREE.PlaneGeometry(0.5, citySize * 2 * spacing + roadWidth);
182
+ const markingMaterial = new THREE.MeshStandardMaterial({color: 0xffffff});
183
  const marking = new THREE.Mesh(markingGeometry, markingMaterial);
184
  marking.rotation.x = -Math.PI / 2;
185
  marking.position.set(x * spacing, 0.02, 0);
186
  scene.add(marking);
187
  }
 
 
188
  for (let z = -citySize; z <= citySize; z++) {
189
  const roadGeometry = new THREE.PlaneGeometry(citySize * 2 * spacing + roadWidth, roadWidth);
190
+ const roadMaterial = new THREE.MeshStandardMaterial({color: roadColor, roughness: 0.9, metalness: 0.1});
 
 
 
 
191
  const road = new THREE.Mesh(roadGeometry, roadMaterial);
192
  road.rotation.x = -Math.PI / 2;
193
  road.position.set(0, 0.01, z * spacing);
194
  scene.add(road);
 
 
195
  const markingGeometry = new THREE.PlaneGeometry(citySize * 2 * spacing + roadWidth, 0.5);
196
+ const markingMaterial = new THREE.MeshStandardMaterial({color: 0xffffff});
197
  const marking = new THREE.Mesh(markingGeometry, markingMaterial);
198
  marking.rotation.x = -Math.PI / 2;
199
  marking.position.set(0, 0.02, z * spacing);
 
201
  }
202
  }
203
 
204
+ // Add windows to building (fixed positioning)
205
  function addWindowsToBuilding(building, width, height, depth) {
206
+ const windowSize = 0.5, windowSpacing = 1.5;
 
207
  const windowGeometry = new THREE.PlaneGeometry(windowSize, windowSize);
208
+ const windowMaterial = new THREE.MeshStandardMaterial({
209
+ color: 0xffffcc, emissive: 0xffffcc, emissiveIntensity: 0.5, transparent: true, opacity: 0.8
 
 
 
 
210
  });
 
 
211
  const numLevels = Math.floor((height - 2) / windowSpacing);
212
 
213
+ // Front and back
 
 
 
214
  for (let level = 0; level < numLevels; level++) {
215
+ const y = -height/2 + 1 + level * windowSpacing;
216
+ for (let x = -width/2 + windowSpacing; x < width/2 - windowSpacing/2; x += windowSpacing) {
 
 
 
217
  if (Math.random() < 0.3) continue;
 
 
218
  const frontWindow = new THREE.Mesh(windowGeometry, windowMaterial);
219
+ frontWindow.position.set(x, y, depth/2 + 0.01);
 
220
  building.add(frontWindow);
 
 
221
  const backWindow = new THREE.Mesh(windowGeometry, windowMaterial);
222
+ backWindow.position.set(x, y, -depth/2 - 0.01);
223
+ backWindow.rotation.y = Math.PI;
224
  building.add(backWindow);
225
  }
226
  }
227
 
228
+ // Sides
 
 
 
229
  for (let level = 0; level < numLevels; level++) {
230
+ const y = -height/2 + 1 + level * windowSpacing;
231
+ for (let z = -depth/2 + windowSpacing; z < depth/2 - windowSpacing/2; z += windowSpacing) {
 
 
 
232
  if (Math.random() < 0.3) continue;
 
 
233
  const rightWindow = new THREE.Mesh(windowGeometry, windowMaterial);
234
+ rightWindow.position.set(width/2 + 0.01, y, z);
235
+ rightWindow.rotation.y = -Math.PI/2;
236
  building.add(rightWindow);
 
 
237
  const leftWindow = new THREE.Mesh(windowGeometry, windowMaterial);
238
+ leftWindow.position.set(-width/2 - 0.01, y, z);
239
+ leftWindow.rotation.y = Math.PI/2;
240
  building.add(leftWindow);
241
  }
242
  }
243
  }
244
 
245
+ // Collectibles, skybox, player setup (unchanged)
246
  function createCollectibles() {
247
+ const citySize = 5, spacing = 15;
 
 
248
  for (let i = 0; i < 20; i++) {
249
+ const collectible = new THREE.Mesh(
250
+ new THREE.BoxGeometry(1, 1, 1),
251
+ new THREE.MeshStandardMaterial({color: 0xffff00, emissive: 0xffff00, emissiveIntensity: 0.5, transparent: true, opacity: 0.8})
252
+ );
253
  const x = (Math.random() * 2 - 1) * citySize * spacing;
254
  const z = (Math.random() * 2 - 1) * citySize * spacing;
255
  const y = 1 + Math.random() * 20;
 
 
 
 
 
 
 
 
 
 
 
256
  collectible.position.set(x, y, z);
257
+ collectible.userData = {
258
+ id: i, rotationSpeed: 0.01 + Math.random() * 0.02,
259
+ floatSpeed: 0.5 + Math.random() * 0.5, floatRange: 0.5 + Math.random() * 0.5,
260
+ initialY: y
261
+ };
 
262
  scene.add(collectible);
263
  collectibles.push(collectible);
264
  }
265
  }
266
 
 
267
  function createSkybox() {
268
+ const sky = new THREE.Mesh(
269
+ new THREE.SphereGeometry(400, 32, 32),
270
+ new THREE.MeshBasicMaterial({color: 0x87CEEB, side: THREE.BackSide})
271
+ );
 
 
272
  scene.add(sky);
 
 
273
  for (let i = 0; i < 50; i++) {
274
+ const radius = 350, phi = Math.random() * Math.PI, theta = Math.random() * Math.PI * 2;
275
+ const cloud = new THREE.Mesh(
276
+ new THREE.SphereGeometry(10 + Math.random() * 20, 8, 8),
277
+ new THREE.MeshStandardMaterial({color: 0xffffff, roughness: 1, metalness: 0, transparent: true, opacity: 0.8})
278
+ );
279
+ cloud.position.set(
280
+ radius * Math.sin(phi) * Math.cos(theta),
281
+ radius * Math.cos(phi) + 50,
282
+ radius * Math.sin(phi) * Math.sin(theta)
283
+ );
 
 
 
 
 
 
 
 
 
 
284
  cloud.userData.rotationSpeed = 0.0001 + Math.random() * 0.0002;
 
285
  scene.add(cloud);
286
  }
287
  }
288
 
289
+ const player = new THREE.Mesh(
290
+ new THREE.CylinderGeometry(0.5, 0.5, 2, 8),
291
+ new THREE.MeshStandardMaterial({color: 0x0000ff})
292
+ );
293
+ player.position.set(0, 1, 0);
 
 
 
294
  player.castShadow = true;
295
  scene.add(player);
296
 
297
+ // Player physics and controls
298
  const playerVelocity = new THREE.Vector3();
299
  const playerDirection = new THREE.Vector3();
300
  let isJumping = false;
301
+ const GRAVITY = 0.2, JUMP_FORCE = 0.7, BASE_SPEED = 0.2;
302
+ let moveSpeed = BASE_SPEED;
303
+ let speedBoostActive = false, speedBoostCooldown = false;
304
+ let speedBoostTimer = 0, cooldownTimer = 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
 
306
+ const keys = {w: false, a: false, s: false, d: false, space: false, shift: false};
307
+ let cameraRotation = 0, cameraPitch = 0;
 
 
 
 
 
 
308
 
309
  document.addEventListener('keydown', (event) => {
310
  switch (event.key.toLowerCase()) {
 
313
  case 's': keys.s = true; break;
314
  case 'd': keys.d = true; break;
315
  case ' ': keys.space = true; break;
316
+ case 'shift': keys.shift = true; break;
317
  }
318
  });
319
 
 
324
  case 's': keys.s = false; break;
325
  case 'd': keys.d = false; break;
326
  case ' ': keys.space = false; break;
327
+ case 'shift': keys.shift = false; break;
328
  }
329
  });
330
 
331
  document.addEventListener('mousemove', (event) => {
 
332
  if (document.pointerLockElement === renderer.domElement) {
333
  cameraRotation -= event.movementX * 0.002;
334
  cameraPitch -= event.movementY * 0.002;
 
 
335
  cameraPitch = Math.max(-Math.PI / 2 + 0.1, Math.min(Math.PI / 2 - 0.1, cameraPitch));
336
  }
337
  });
338
 
 
339
  renderer.domElement.addEventListener('click', () => {
340
+ if (!document.pointerLockElement) renderer.domElement.requestPointerLock();
 
 
341
  });
342
 
343
+ function updatePlayer(delta) {
344
+ // Speed boost handling
345
+ if (keys.shift && !speedBoostActive && !speedBoostCooldown) {
346
+ speedBoostActive = true;
347
+ speedBoostTimer = 10;
348
+ moveSpeed = BASE_SPEED * 2;
349
+ }
350
+ if (speedBoostActive) {
351
+ speedBoostTimer -= delta;
352
+ if (speedBoostTimer <= 0) {
353
+ speedBoostActive = false;
354
+ speedBoostCooldown = true;
355
+ cooldownTimer = 10;
356
+ moveSpeed = BASE_SPEED;
357
+ }
358
+ }
359
+ if (speedBoostCooldown) {
360
+ cooldownTimer -= delta;
361
+ if (cooldownTimer <= 0) speedBoostCooldown = false;
362
+ }
363
+
364
  playerVelocity.y -= GRAVITY;
 
 
365
  if (keys.space && !isJumping) {
366
  playerVelocity.y = JUMP_FORCE;
367
  isJumping = true;
368
  }
 
 
 
369
  playerDirection.z = Number(keys.s) - Number(keys.w);
370
  playerDirection.x = Number(keys.d) - Number(keys.a);
371
  playerDirection.normalize();
 
 
372
  playerDirection.applyAxisAngle(new THREE.Vector3(0, 1, 0), cameraRotation);
373
+ player.position.x += playerDirection.x * moveSpeed;
374
+ player.position.z += playerDirection.z * moveSpeed;
 
 
375
  player.position.y += playerVelocity.y;
376
+ camera.position.set(player.position.x, player.position.y + 1.5, player.position.z);
377
+ camera.rotation.order = 'YXZ';
 
 
 
 
 
 
378
  camera.rotation.y = cameraRotation;
379
  camera.rotation.x = cameraPitch;
 
 
380
  checkPlayerCollisions();
381
  }
382
 
383
+ function checkPlayerCollisions() {
384
+ for (let i = collectibles.length - 1; i >= 0; i--) {
385
+ const collectible = collectibles[i];
386
+ if (player.position.distanceTo(collectible.position) < 1) {
387
+ scene.remove(collectible);
388
+ collectibles.splice(i, 1);
389
+ score += 10;
390
+ document.getElementById('score').textContent = `Score: ${score}`;
391
+ }
392
+ }
393
+ for (const building of buildings) {
394
+ building.traverse((child) => {
395
+ if (child.isMesh) {
396
+ const buildingBox = new THREE.Box3().setFromObject(child);
397
+ const playerPos = player.position.clone();
398
+ if (playerPos.x + 0.5 > buildingBox.min.x && playerPos.x - 0.5 < buildingBox.max.x &&
399
+ playerPos.z + 0.5 > buildingBox.min.z && playerPos.z - 0.5 < buildingBox.max.z) {
400
+ const edgeDistances = [
401
+ {edge: 'left', dist: Math.abs(playerPos.x - buildingBox.min.x)},
402
+ {edge: 'right', dist: Math.abs(playerPos.x - buildingBox.max.x)},
403
+ {edge: 'front', dist: Math.abs(playerPos.z - buildingBox.min.z)},
404
+ {edge: 'back', dist: Math.abs(playerPos.z - buildingBox.max.z)}
405
+ ].sort((a, b) => a.dist - b.dist);
406
+ switch (edgeDistances[0].edge) {
407
+ case 'left': player.position.x = buildingBox.min.x - 0.5; break;
408
+ case 'right': player.position.x = buildingBox.max.x + 0.5; break;
409
+ case 'front': player.position.z = buildingBox.min.z - 0.5; break;
410
+ case 'back': player.position.z = buildingBox.max.z + 0.5; break;
411
+ }
412
+ }
413
+ }
414
+ });
415
+ }
416
+ if (player.position.y <= 1) {
417
+ player.position.y = 1;
418
+ playerVelocity.y = 0;
419
+ isJumping = false;
420
+ }
421
+ }
422
+
423
+ // Sun and Moon cycle
424
+ let cycleTime = 0;
425
+ function updateLighting(delta) {
426
+ cycleTime += delta;
427
+ const cycleDuration = 120; // 2 minutes in seconds
428
+ const angle = (cycleTime / cycleDuration) * Math.PI * 2;
429
+
430
+ // Sun position and intensity
431
+ sunLight.position.set(
432
+ Math.cos(angle) * 100,
433
+ Math.sin(angle) * 100,
434
+ Math.sin(angle) * 50
435
+ );
436
+ sunLight.intensity = Math.max(0, Math.sin(angle)) * 0.8;
437
+
438
+ // Moon position and intensity
439
+ moonLight.position.set(
440
+ Math.cos(angle + Math.PI) * 100,
441
+ Math.sin(angle + Math.PI) * 100,
442
+ Math.sin(angle + Math.PI) * 50
443
+ );
444
+ moonLight.intensity = Math.max(0, Math.sin(angle + Math.PI)) * 0.4;
445
+
446
+ // Sky color transition
447
+ const dayColor = new THREE.Color(0x87CEEB);
448
+ const nightColor = new THREE.Color(0x001133);
449
+ scene.background = dayColor.clone().lerp(nightColor, Math.max(0, -Math.sin(angle)));
450
+ }
451
+
452
+ // Game loop
453
+ let lastTime = performance.now();
454
+ function animate() {
455
+ requestAnimationFrame(animate);
456
+ const currentTime = performance.now();
457
+ const delta = (currentTime - lastTime) / 1000;
458
+ lastTime = currentTime;
459
+
460
+ if (gameActive) {
461
+ updatePlayer(delta);
462
+ updateLighting(delta);
463
+ for (const collectible of collectibles) {
464
+ collectible.rotation.x += collectible.userData.rotationSpeed;
465
+ collectible.rotation.y += collectible.userData.rotationSpeed * 1.5;
466
+ collectible.position.y = collectible.userData.initialY +
467
+ Math.sin(Date.now() * 0.001 * collectible.userData.floatSpeed) * collectible.userData.floatRange;
468
+ }
469
+ }
470
+ renderer.render(scene, camera);
471
+ }
472
+
473
+ // Timer and end game (unchanged)
474
  function updateTimer() {
475
  if (gameActive && timeRemaining > 0) {
476
  timeRemaining--;
477
  document.getElementById('time').textContent = `Time: ${timeRemaining}`;
 
478
  if (timeRemaining === 0) {
479
  gameActive = false;
480
  endGame();
 
482
  }
483
  }
484
 
 
485
  function endGame() {
486
  const endScreen = document.createElement('div');
487
+ endScreen.style.cssText = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:rgba(0,0,0,0.8);color:white;padding:20px;border-radius:10px;text-align:center;';
 
 
 
 
 
 
 
 
488
  endScreen.innerHTML = `
489
  <h2>Game Over!</h2>
490
  <p>Your final score: ${score}</p>
491
+ <button id="restart-btn" style="padding:10px 20px;background:#4CAF50;color:white;border:none;border-radius:5px;cursor:pointer;margin-top:10px;">Play Again</button>
492
  `;
493
  document.body.appendChild(endScreen);
 
494
  document.getElementById('restart-btn').addEventListener('click', () => {
495
  document.body.removeChild(endScreen);
496
+ score = 0; timeRemaining = 60; gameActive = true;
 
 
497
  document.getElementById('score').textContent = `Score: ${score}`;
498
  document.getElementById('time').textContent = `Time: ${timeRemaining}`;
499
+ player.position.set(0, 1, 0);
 
 
500
  playerVelocity.set(0, 0, 0);
501
+ cameraRotation = 0; cameraPitch = 0;
502
+ for (const collectible of collectibles) scene.remove(collectible);
 
 
 
 
 
 
 
503
  collectibles.length = 0;
504
  createCollectibles();
505
  });
506
  }
507
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
508
  window.addEventListener('resize', () => {
509
  camera.aspect = window.innerWidth / window.innerHeight;
510
  camera.updateProjectionMatrix();
 
515
  createCity();
516
  createCollectibles();
517
  createSkybox();
 
 
518
  setInterval(updateTimer, 1000);
 
 
519
  animate();
520
  </script>
521
  </body>