awacke1 commited on
Commit
11414f9
·
verified ·
1 Parent(s): a0198ca

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +515 -18
index.html CHANGED
@@ -1,19 +1,516 @@
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>Scottish Castle Tower Builder</title>
7
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/cannon.js/0.6.2/cannon.min.js"></script>
9
+ <script src="https://cdn.tailwindcss.com"></script>
10
+ <style>
11
+ body { margin: 0; overflow: hidden; font-family: 'Inter', sans-serif; background-color: #87CEEB; } /* Sky Blue */
12
+ #container { width: 100vw; height: 100vh; display: block; }
13
+ #ui-container {
14
+ position: absolute;
15
+ top: 20px;
16
+ left: 20px;
17
+ background-color: rgba(0,0,0,0.7);
18
+ color: white;
19
+ padding: 15px;
20
+ border-radius: 10px;
21
+ font-size: 16px;
22
+ z-index: 100;
23
+ max-width: 300px;
24
+ }
25
+ #ui-container h1 { font-size: 24px; margin-bottom: 10px; }
26
+ #ui-container p { margin-bottom: 5px; }
27
+ #next-piece-preview {
28
+ width: 80px;
29
+ height: 80px;
30
+ border: 1px solid #555;
31
+ margin-top: 10px;
32
+ background-color: rgba(255,255,255,0.1);
33
+ border-radius: 5px;
34
+ }
35
+ .button {
36
+ background-color: #4A90E2; /* Brighter Blue */
37
+ color: white;
38
+ padding: 10px 15px;
39
+ border: none;
40
+ border-radius: 5px;
41
+ cursor: pointer;
42
+ font-size: 16px;
43
+ margin-top: 10px;
44
+ transition: background-color 0.3s;
45
+ }
46
+ .button:hover {
47
+ background-color: #357ABD; /* Darker Blue on hover */
48
+ }
49
+ #game-over-message {
50
+ position: absolute;
51
+ top: 50%;
52
+ left: 50%;
53
+ transform: translate(-50%, -50%);
54
+ background-color: rgba(220, 50, 50, 0.9); /* Reddish */
55
+ color: white;
56
+ padding: 30px;
57
+ border-radius: 15px;
58
+ font-size: 28px;
59
+ text-align: center;
60
+ z-index: 200;
61
+ display: none; /* Hidden by default */
62
+ }
63
+ #game-over-message h2 { margin-bottom: 15px; }
64
+ </style>
65
+ </head>
66
+ <body>
67
+ <div id="container"></div>
68
+ <div id="ui-container">
69
+ <h1>Castle Builder</h1>
70
+ <p>Score: <span id="score">0</span></p>
71
+ <p>Controls:</p>
72
+ <ul class="list-disc list-inside ml-2 text-sm">
73
+ <li>Left/Right Arrows: Move piece</li>
74
+ <li>Up/Down Arrows: Rotate piece</li>
75
+ <li>Spacebar: Drop piece</li>
76
+ <li>R: Restart Game</li>
77
+ </ul>
78
+ <p class="mt-2">Next Piece:</p>
79
+ <div id="next-piece-preview-container">
80
+ <canvas id="next-piece-canvas" class="rounded-md border border-gray-600 mt-1"></canvas>
81
+ </div>
82
+ </div>
83
+
84
+ <div id="game-over-message">
85
+ <h2>Game Over!</h2>
86
+ <p>Final Score: <span id="final-score">0</span></p>
87
+ <button id="restart-button" class="button">Restart Game</button>
88
+ </div>
89
+
90
+ <script type="module">
91
+ let scene, camera, renderer, world;
92
+ let currentPiece, nextPieceMeshPreview;
93
+ let placedBlocks = []; // To store { mesh, body } of placed blocks
94
+ let score = 0;
95
+ let gameOver = false;
96
+ let gameStarted = false;
97
+
98
+ const pieceTypes = [
99
+ { name: 'Foundation Stone', type: 'box', size: { x: 4, y: 1.5, z: 3 }, color: 0x6c757d, mass: 10 }, // Dark grey
100
+ { name: 'Wall Segment', type: 'box', size: { x: 3.5, y: 2.5, z: 1 }, color: 0xadb5bd, mass: 5 }, // Medium grey
101
+ { name: 'Round Tower Section', type: 'cylinder', size: { rTop: 1.2, rBottom: 1.2, h: 2.5, segments: 16 }, color: 0xd1d1d1, mass: 4 }, // Light grey
102
+ { name: 'Square Tower Section', type: 'box', size: { x: 2, y: 3, z: 2 }, color: 0xced4da, mass: 4.5 }, // Lighter grey
103
+ { name: 'Bartizan Base', type: 'box', size: { x: 1, y: 1, z: 1 }, color: 0xadb5bd, mass: 1 }, // Small, like wall
104
+ { name: 'Cap House', type: 'box', size: { x: 1.8, y: 1.2, z: 1.8 }, color: 0x495057, mass: 2 }, // Dark slate
105
+ { name: 'Stepped Gable Block', type: 'box', size: { x: 2.5, y: 0.8, z: 0.8 }, color: 0x8a7967, mass: 1.5 } // Brownish stone
106
+ ];
107
+
108
+ let nextPieceType;
109
+
110
+ // For next piece preview
111
+ let previewScene, previewCamera, previewRenderer;
112
+
113
+ const groundMaterial = new CANNON.Material('groundMaterial');
114
+ const blockMaterial = new CANNON.Material('blockMaterial');
115
+ const contactMaterial = new CANNON.ContactMaterial(groundMaterial, blockMaterial, {
116
+ friction: 0.7,
117
+ restitution: 0.1,
118
+ });
119
+ const blockToBlockContactMaterial = new CANNON.ContactMaterial(blockMaterial, blockMaterial, {
120
+ friction: 0.6, // Higher friction between blocks
121
+ restitution: 0.05,
122
+ });
123
+
124
+
125
+ function init() {
126
+ // Main Scene
127
+ scene = new THREE.Scene();
128
+ scene.background = new THREE.Color(0x87CEEB); // Sky blue
129
+
130
+ camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
131
+ camera.position.set(0, 10, 20); // Adjusted for better view
132
+ camera.lookAt(0, 5, 0);
133
+
134
+ renderer = new THREE.WebGLRenderer({ antialias: true });
135
+ renderer.setSize(window.innerWidth, window.innerHeight);
136
+ renderer.shadowMap.enabled = true;
137
+ document.getElementById('container').appendChild(renderer.domElement);
138
+
139
+ // Lighting
140
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
141
+ scene.add(ambientLight);
142
+
143
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
144
+ directionalLight.position.set(10, 20, 15);
145
+ directionalLight.castShadow = true;
146
+ directionalLight.shadow.mapSize.width = 2048;
147
+ directionalLight.shadow.mapSize.height = 2048;
148
+ directionalLight.shadow.camera.near = 0.5;
149
+ directionalLight.shadow.camera.far = 50;
150
+ directionalLight.shadow.camera.left = -20;
151
+ directionalLight.shadow.camera.right = 20;
152
+ directionalLight.shadow.camera.top = 20;
153
+ directionalLight.shadow.camera.bottom = -20;
154
+ scene.add(directionalLight);
155
+
156
+ // Physics World
157
+ world = new CANNON.World();
158
+ world.gravity.set(0, -9.82, 0);
159
+ world.broadphase = new CANNON.NaiveBroadphase();
160
+ world.solver.iterations = 10;
161
+ world.addContactMaterial(contactMaterial);
162
+ world.addContactMaterial(blockToBlockContactMaterial);
163
+
164
+
165
+ // Ground
166
+ const groundGeometry = new THREE.PlaneGeometry(50, 50);
167
+ const groundMeshMaterial = new THREE.MeshStandardMaterial({ color: 0x556B2F, side: THREE.DoubleSide }); // Dark Olive Green
168
+ const groundMesh = new THREE.Mesh(groundGeometry, groundMeshMaterial);
169
+ groundMesh.rotation.x = -Math.PI / 2;
170
+ groundMesh.receiveShadow = true;
171
+ scene.add(groundMesh);
172
+
173
+ const groundBody = new CANNON.Body({
174
+ mass: 0, // static
175
+ shape: new CANNON.Plane(),
176
+ material: groundMaterial
177
+ });
178
+ groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2);
179
+ world.addBody(groundBody);
180
+
181
+ // Initial piece selection
182
+ selectNextPieceType();
183
+
184
+ // Initialize Next Piece Preview
185
+ initPreviewCanvas();
186
+ spawnNewPiece(); // Spawn the first piece
187
+ updateNextPiecePreview(); // Show the *next* one
188
+
189
+ document.addEventListener('keydown', onKeyDown);
190
+ window.addEventListener('resize', onWindowResize);
191
+
192
+ gameStarted = true;
193
+ gameOver = false;
194
+ document.getElementById('game-over-message').style.display = 'none';
195
+ updateScoreDisplay();
196
+
197
+ animate();
198
+ }
199
+
200
+ function initPreviewCanvas() {
201
+ const canvas = document.getElementById('next-piece-canvas');
202
+ const container = document.getElementById('next-piece-preview-container');
203
+ const size = Math.min(container.clientWidth, 80); // Ensure it fits
204
+ canvas.width = size;
205
+ canvas.height = size;
206
+
207
+ previewScene = new THREE.Scene();
208
+ previewScene.background = new THREE.Color(0x444444); // Darker background for preview
209
+ previewCamera = new THREE.PerspectiveCamera(50, 1, 0.1, 100);
210
+ previewCamera.position.set(0, 2, 4); // Adjusted for better preview
211
+ previewCamera.lookAt(0, 0, 0);
212
+
213
+ previewRenderer = new THREE.WebGLRenderer({ canvas: canvas, antialias: true });
214
+ previewRenderer.setSize(size, size);
215
+
216
+ const previewLight = new THREE.AmbientLight(0xffffff, 0.8);
217
+ previewScene.add(previewLight);
218
+ const previewDirectionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
219
+ previewDirectionalLight.position.set(2,3,2);
220
+ previewScene.add(previewDirectionalLight);
221
+ }
222
+
223
+ function selectNextPieceType() {
224
+ nextPieceType = pieceTypes[Math.floor(Math.random() * pieceTypes.length)];
225
+ }
226
+
227
+ function createCastlePiece(pieceInfo, isPreview = false) {
228
+ let geometry, bodyShape;
229
+ const material = new THREE.MeshStandardMaterial({
230
+ color: pieceInfo.color,
231
+ roughness: 0.7,
232
+ metalness: 0.1
233
+ });
234
+
235
+ switch (pieceInfo.type) {
236
+ case 'box':
237
+ geometry = new THREE.BoxGeometry(pieceInfo.size.x, pieceInfo.size.y, pieceInfo.size.z);
238
+ bodyShape = new CANNON.Box(new CANNON.Vec3(pieceInfo.size.x / 2, pieceInfo.size.y / 2, pieceInfo.size.z / 2));
239
+ break;
240
+ case 'cylinder':
241
+ geometry = new THREE.CylinderGeometry(pieceInfo.size.rTop, pieceInfo.size.rBottom, pieceInfo.size.h, pieceInfo.size.segments);
242
+ bodyShape = new CANNON.Cylinder(pieceInfo.size.rTop, pieceInfo.size.rBottom, pieceInfo.size.h, pieceInfo.size.segments);
243
+ break;
244
+ }
245
+
246
+ const mesh = new THREE.Mesh(geometry, material);
247
+ mesh.castShadow = true;
248
+ mesh.receiveShadow = true;
249
+
250
+ if (isPreview) return mesh; // For preview, only mesh is needed
251
+
252
+ const body = new CANNON.Body({
253
+ mass: pieceInfo.mass,
254
+ shape: bodyShape,
255
+ material: blockMaterial,
256
+ linearDamping: 0.1, // Helps stabilize faster
257
+ angularDamping: 0.1
258
+ });
259
+
260
+ return { mesh, body, pieceInfo };
261
+ }
262
+
263
+ function spawnNewPiece() {
264
+ if (gameOver) return;
265
+
266
+ currentPiece = createCastlePiece(nextPieceType); // Use the pre-selected nextPieceType
267
+ selectNextPieceType(); // Select the *next* one for the preview
268
+ updateNextPiecePreview();
269
+
270
+ // Position new piece above the highest block or ground
271
+ let highestY = 0.5; // Start slightly above ground
272
+ if (placedBlocks.length > 0) {
273
+ // Find the top surface of the highest block
274
+ let maxBodyY = -Infinity;
275
+ placedBlocks.forEach(block => {
276
+ if (block.pieceInfo.type === 'box') {
277
+ maxBodyY = Math.max(maxBodyY, block.body.position.y + block.pieceInfo.size.y / 2);
278
+ } else if (block.pieceInfo.type === 'cylinder') {
279
+ maxBodyY = Math.max(maxBodyY, block.body.position.y + block.pieceInfo.size.h / 2);
280
+ }
281
+ });
282
+ highestY = maxBodyY + 2; // Spawn above the highest point
283
+ } else {
284
+ highestY = 10; // Initial spawn height
285
+ }
286
+
287
+
288
+ currentPiece.mesh.position.set(0, highestY, 0);
289
+ currentPiece.body.position.copy(currentPiece.mesh.position);
290
+ currentPiece.body.quaternion.copy(currentPiece.mesh.quaternion);
291
+
292
+ // Initially, the body is kinematic or not added to world, so it doesn't fall
293
+ // We will add it to world when dropped. For now, just control mesh.
294
+ scene.add(currentPiece.mesh);
295
+ currentPiece.isDropped = false; // Custom flag
296
+ }
297
+
298
+ function updateNextPiecePreview() {
299
+ if (nextPieceMeshPreview) {
300
+ previewScene.remove(nextPieceMeshPreview);
301
+ nextPieceMeshPreview.geometry.dispose();
302
+ nextPieceMeshPreview.material.dispose();
303
+ }
304
+ nextPieceMeshPreview = createCastlePiece(nextPieceType, true); // Create mesh only
305
+ previewScene.add(nextPieceMeshPreview);
306
+ }
307
+
308
+
309
+ function dropPiece() {
310
+ if (!currentPiece || currentPiece.isDropped || gameOver) return;
311
+
312
+ currentPiece.isDropped = true;
313
+ // Set initial velocity or just let gravity take over
314
+ currentPiece.body.velocity.set(0, -1, 0); // Give it a slight downward push
315
+ currentPiece.body.angularVelocity.set(0,0,0); // Reset any spin from movement
316
+ currentPiece.body.position.copy(currentPiece.mesh.position);
317
+ currentPiece.body.quaternion.copy(currentPiece.mesh.quaternion);
318
+ world.addBody(currentPiece.body);
319
+ placedBlocks.push(currentPiece);
320
+
321
+ // Check for stability and game over after a short delay
322
+ setTimeout(() => {
323
+ if (!checkStabilityAndScore(currentPiece)) {
324
+ handleGameOver();
325
+ } else {
326
+ spawnNewPiece();
327
+ }
328
+ }, 1500); // Delay to allow piece to settle
329
+ }
330
+
331
+ function checkStabilityAndScore(droppedPiece) {
332
+ if (gameOver) return false;
333
+
334
+ // Check if the dropped piece fell off (too low)
335
+ if (droppedPiece.body.position.y < -5) { // Arbitrary low threshold
336
+ console.log("Piece fell off!");
337
+ return false;
338
+ }
339
+
340
+ // Check if it's moving too much (unstable)
341
+ const speedThreshold = 0.5;
342
+ if (droppedPiece.body.velocity.length() > speedThreshold && droppedPiece.body.position.y < 0) { // Check only if near ground
343
+ console.log("Piece unstable and fell!");
344
+ return false;
345
+ }
346
+
347
+ // Update score based on the highest point of any block
348
+ let maxOverallY = 0;
349
+ placedBlocks.forEach(block => {
350
+ let topY = block.body.position.y;
351
+ if (block.pieceInfo.type === 'box') {
352
+ topY += block.pieceInfo.size.y / 2;
353
+ } else if (block.pieceInfo.type === 'cylinder') {
354
+ topY += block.pieceInfo.size.h / 2;
355
+ }
356
+ maxOverallY = Math.max(maxOverallY, topY);
357
+ });
358
+
359
+ score = Math.floor(maxOverallY * 10); // Scale score
360
+ updateScoreDisplay();
361
+ return true;
362
+ }
363
+
364
+ function updateScoreDisplay() {
365
+ document.getElementById('score').innerText = score;
366
+ }
367
+
368
+ function handleGameOver() {
369
+ if (gameOver) return; // Prevent multiple calls
370
+ gameOver = true;
371
+ console.log("Game Over. Final Score:", score);
372
+ document.getElementById('final-score').innerText = score;
373
+ document.getElementById('game-over-message').style.display = 'flex';
374
+
375
+ // Optional: Stop current piece movement if any
376
+ if (currentPiece && !currentPiece.isDropped) {
377
+ scene.remove(currentPiece.mesh); // Remove the controlled piece if not dropped
378
+ }
379
+ }
380
+
381
+ function restartGame() {
382
+ gameOver = true; // Temporarily set to stop animate loop from interfering
383
+
384
+ // Clear existing blocks
385
+ placedBlocks.forEach(block => {
386
+ scene.remove(block.mesh);
387
+ if (block.mesh.geometry) block.mesh.geometry.dispose();
388
+ if (block.mesh.material) block.mesh.material.dispose();
389
+ if (world.bodies.includes(block.body)) {
390
+ world.removeBody(block.body);
391
+ }
392
+ });
393
+ placedBlocks = [];
394
+
395
+ if (currentPiece && currentPiece.mesh && scene.children.includes(currentPiece.mesh)) {
396
+ scene.remove(currentPiece.mesh);
397
+ if(currentPiece.mesh.geometry) currentPiece.mesh.geometry.dispose();
398
+ if(currentPiece.mesh.material) currentPiece.mesh.material.dispose();
399
+ if(currentPiece.body && world.bodies.includes(currentPiece.body)){
400
+ world.removeBody(currentPiece.body); // Corrected this line
401
+ }
402
+ }
403
+ currentPiece = null;
404
+
405
+ score = 0;
406
+ updateScoreDisplay();
407
+ document.getElementById('game-over-message').style.display = 'none';
408
+
409
+ // Reset camera if needed, though current setup might be fine
410
+ // camera.position.set(0, 10, 20);
411
+ // camera.lookAt(0, 5, 0);
412
+
413
+ gameOver = false; // Allow game to run again
414
+ gameStarted = false; // To re-trigger init aspects if needed, or just re-spawn
415
+
416
+ // Re-initialize critical parts or just spawn the first piece
417
+ selectNextPieceType();
418
+ spawnNewPiece();
419
+ updateNextPiecePreview();
420
+ gameStarted = true; // Set after spawning first piece
421
+ }
422
+
423
+
424
+ function onKeyDown(event) {
425
+ if (gameOver && event.key.toLowerCase() !== 'r') return;
426
+ if (!currentPiece || currentPiece.isDropped) return;
427
+
428
+ const moveSpeed = 0.5;
429
+ const rotateSpeed = Math.PI / 16; // 11.25 degrees
430
+
431
+ switch (event.key.toLowerCase()) {
432
+ case 'arrowleft':
433
+ currentPiece.mesh.position.x -= moveSpeed;
434
+ break;
435
+ case 'arrowright':
436
+ currentPiece.mesh.position.x += moveSpeed;
437
+ break;
438
+ case 'arrowup': // Rotate
439
+ currentPiece.mesh.rotateY(rotateSpeed);
440
+ break;
441
+ case 'arrowdown': // Rotate other way
442
+ currentPiece.mesh.rotateY(-rotateSpeed);
443
+ break;
444
+ case ' ': // Spacebar to drop
445
+ dropPiece();
446
+ break;
447
+ case 'r': // Restart
448
+ restartGame();
449
+ break;
450
+ }
451
+ // Keep body synced if not dropped yet (for visual placement)
452
+ if (currentPiece && !currentPiece.isDropped) {
453
+ currentPiece.body.position.copy(currentPiece.mesh.position);
454
+ currentPiece.body.quaternion.copy(currentPiece.mesh.quaternion);
455
+ }
456
+ }
457
+
458
+ function onWindowResize() {
459
+ camera.aspect = window.innerWidth / window.innerHeight;
460
+ camera.updateProjectionMatrix();
461
+ renderer.setSize(window.innerWidth, window.innerHeight);
462
+
463
+ // Resize preview canvas
464
+ const canvas = document.getElementById('next-piece-canvas');
465
+ const container = document.getElementById('next-piece-preview-container');
466
+ const size = Math.min(container.clientWidth, 80);
467
+ canvas.width = size;
468
+ canvas.height = size;
469
+ previewCamera.aspect = 1; // Square
470
+ previewCamera.updateProjectionMatrix();
471
+ previewRenderer.setSize(size, size);
472
+ updateNextPiecePreview(); // Re-render preview
473
+ }
474
+
475
+ function animate() {
476
+ if (gameOver && !gameStarted) { // Only truly stop if game over AND not in restart process
477
+ // Do nothing if game is fully over and not restarting
478
+ } else if (gameOver && gameStarted) {
479
+ // This case handles when game is over, but restart might be pending or just happened.
480
+ // We still want to render the game over screen and potentially the static scene.
481
+ renderer.render(scene, camera);
482
+ if (nextPieceMeshPreview && previewScene && previewCamera) {
483
+ if(nextPieceMeshPreview.parent) nextPieceMeshPreview.rotation.y += 0.02; // Gentle spin
484
+ previewRenderer.render(previewScene, previewCamera);
485
+ }
486
+ requestAnimationFrame(animate); // Keep rendering for game over screen
487
+ }
488
+ else { // Game is active
489
+ requestAnimationFrame(animate);
490
+ world.step(1 / 60); // Step the physics world
491
+
492
+ // Update visuals of all placed blocks
493
+ placedBlocks.forEach(block => {
494
+ block.mesh.position.copy(block.body.position);
495
+ block.mesh.quaternion.copy(block.body.quaternion);
496
+ });
497
+
498
+ // If current piece is dropped, its mesh is updated by the loop above.
499
+ // If not dropped, its mesh is controlled by player input.
500
+
501
+ renderer.render(scene, camera);
502
+
503
+ // Render next piece preview
504
+ if (nextPieceMeshPreview && previewScene && previewCamera) {
505
+ nextPieceMeshPreview.rotation.y += 0.02; // Gentle spin
506
+ previewRenderer.render(previewScene, previewCamera);
507
+ }
508
+ }
509
+ }
510
+
511
+ document.getElementById('restart-button').addEventListener('click', restartGame);
512
+ init();
513
+
514
+ </script>
515
+ </body>
516
  </html>