Spaces:
Sleeping
Sleeping
Update templates/index.html
#2
by
zxciop - opened
- templates/index.html +94 -15
templates/index.html
CHANGED
|
@@ -25,7 +25,7 @@
|
|
| 25 |
.game-ui {
|
| 26 |
position: absolute;
|
| 27 |
padding: 10px;
|
| 28 |
-
background-color: rgba(0,
|
| 29 |
border: 1px solid #0f0;
|
| 30 |
border-radius: 5px;
|
| 31 |
font-size: 1.2em;
|
|
@@ -52,7 +52,7 @@
|
|
| 52 |
flex-direction: column;
|
| 53 |
justify-content: center;
|
| 54 |
align-items: center;
|
| 55 |
-
background-color: rgba(0, 10,
|
| 56 |
z-index: 10;
|
| 57 |
}
|
| 58 |
#startScreen, #gameOverScreen {
|
|
@@ -91,9 +91,9 @@
|
|
| 91 |
pointer-events: auto;
|
| 92 |
}
|
| 93 |
.button:hover {
|
| 94 |
-
background-color: rgba(0, 120, 0, 0.
|
| 95 |
transform: translateY(-5px);
|
| 96 |
-
box-shadow: 0px 2px 10px #2eff00;
|
| 97 |
}
|
| 98 |
.controls {
|
| 99 |
margin-top: 20px;
|
|
@@ -121,12 +121,12 @@
|
|
| 121 |
display: none; /* Hidden by default, shown on mobile */
|
| 122 |
}
|
| 123 |
.touchBtn {
|
| 124 |
-
width:
|
| 125 |
-
height:
|
| 126 |
background-color: rgba(0, 120, 0, 0.7);
|
| 127 |
-
border:
|
| 128 |
border-radius: 50%;
|
| 129 |
-
margin:
|
| 130 |
display: inline-flex;
|
| 131 |
justify-content: center;
|
| 132 |
align-items: center;
|
|
@@ -233,6 +233,7 @@
|
|
| 233 |
'HARD': { speedMultiplier: 0.7, obstacleMultiplier: 1.5 }
|
| 234 |
},
|
| 235 |
MAX_OBSTACLE_COUNT: 10, // Maximum number of obstacles
|
|
|
|
| 236 |
FOOD_TYPES: [
|
| 237 |
{ type: 'regular', color: 0x00ff00, points: 1, speedEffect: 0 },
|
| 238 |
{ type: 'special', color: 0x00ffff, points: 5, speedEffect: -10 },
|
|
@@ -256,7 +257,7 @@
|
|
| 256 |
const material = new THREE.MeshBasicMaterial({
|
| 257 |
color: color || 0x00ff00,
|
| 258 |
transparent: true,
|
| 259 |
-
opacity: 0.
|
| 260 |
});
|
| 261 |
const particle = new THREE.Mesh(this.geometry, material);
|
| 262 |
particle.position.copy(position);
|
|
@@ -446,6 +447,7 @@
|
|
| 446 |
this.snake = [];
|
| 447 |
this.food = null;
|
| 448 |
this.obstacles = [];
|
|
|
|
| 449 |
this.direction = new THREE.Vector3(CONFIG.CELL_SIZE, 0, 0);
|
| 450 |
this.nextDirection = new THREE.Vector3(CONFIG.CELL_SIZE, 0, 0);
|
| 451 |
this.score = 0;
|
|
@@ -457,6 +459,7 @@
|
|
| 457 |
this.gameLoopId = null;
|
| 458 |
this.bounds = Math.floor(CONFIG.GRID_SIZE / 2) * CONFIG.CELL_SIZE;
|
| 459 |
this.obstacleCount = CONFIG.MAX_OBSTACLE_COUNT;
|
|
|
|
| 460 |
this.comboCount = 0;
|
| 461 |
this.lastFoodTime = 0;
|
| 462 |
this.currentDifficulty = 'NORMAL';
|
|
@@ -473,14 +476,18 @@
|
|
| 473 |
obstacle: new THREE.MeshStandardMaterial({ // Changed to Standard for lighting
|
| 474 |
color: 0xff0000, emissive: 0x550000, roughness: 0.3, metalness: 1, wireframe: false
|
| 475 |
}),
|
|
|
|
|
|
|
|
|
|
| 476 |
// Food materials defined per type in chooseFoodType
|
| 477 |
};
|
| 478 |
this.geometries = {
|
| 479 |
segment: new THREE.BoxGeometry(CONFIG.CELL_SIZE, CONFIG.CELL_SIZE, CONFIG.CELL_SIZE),
|
| 480 |
foodBox: new THREE.BoxGeometry(CONFIG.CELL_SIZE * 0.8, CONFIG.CELL_SIZE * 0.8, CONFIG.CELL_SIZE * 0.8),
|
| 481 |
-
foodSphere: new THREE.SphereGeometry(CONFIG.CELL_SIZE * 0.5,
|
| 482 |
foodTetrahedron: new THREE.TetrahedronGeometry(CONFIG.CELL_SIZE * 0.6, 0),
|
| 483 |
-
obstacle: new THREE.BoxGeometry(CONFIG.CELL_SIZE * 0.9, CONFIG.CELL_SIZE * 1.5, CONFIG.CELL_SIZE * 0.9) // Taller obstacles
|
|
|
|
| 484 |
};
|
| 485 |
|
| 486 |
this.segmentPool = new ObjectPool(() => {
|
|
@@ -495,6 +502,12 @@
|
|
| 495 |
return obstacle;
|
| 496 |
}, CONFIG.MAX_OBSTACLE_COUNT * 1.5);
|
| 497 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 498 |
this.init(); // Call init before matrixRain if matrixRain depends on game elements
|
| 499 |
this.matrixRain = new MatrixRain(); // Initialize after main game setup if it interacts
|
| 500 |
this.setupEventListeners();
|
|
@@ -524,9 +537,9 @@
|
|
| 524 |
// Post-processing for bloom
|
| 525 |
const renderPass = new RenderPass(this.scene, this.camera);
|
| 526 |
const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.2, 0.8, 0.75);
|
| 527 |
-
bloomPass.threshold = 0.
|
| 528 |
-
bloomPass.strength = 1.
|
| 529 |
-
bloomPass.radius = 0.
|
| 530 |
|
| 531 |
this.composer = new EffectComposer(this.renderer);
|
| 532 |
this.composer.addPass(renderPass);
|
|
@@ -586,8 +599,12 @@
|
|
| 586 |
let collisionWithObstacle = this.obstacles.some(obstacle =>
|
| 587 |
obstacle.position.distanceToSquared(foodPos) < (CONFIG.CELL_SIZE * 0.9)**2
|
| 588 |
);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 589 |
|
| 590 |
-
validPosition = !collisionWithSnake && !collisionWithObstacle;
|
| 591 |
attempts++;
|
| 592 |
}
|
| 593 |
|
|
@@ -652,6 +669,54 @@
|
|
| 652 |
}
|
| 653 |
}
|
| 654 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 655 |
clearGameObjects() {
|
| 656 |
this.snake.forEach(segment => {
|
| 657 |
this.scene.remove(segment);
|
|
@@ -671,6 +736,12 @@
|
|
| 671 |
});
|
| 672 |
this.obstacles = [];
|
| 673 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 674 |
this.particleSystem.clear();
|
| 675 |
}
|
| 676 |
|
|
@@ -712,6 +783,13 @@
|
|
| 712 |
}
|
| 713 |
}
|
| 714 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 715 |
const newHead = this.segmentPool.get();
|
| 716 |
newHead.position.copy(newHeadPos);
|
| 717 |
newHead.material = this.materials.snakeHead; // Head material
|
|
@@ -778,6 +856,7 @@
|
|
| 778 |
}
|
| 779 |
}
|
| 780 |
|
|
|
|
| 781 |
chooseFoodType() {
|
| 782 |
const rand = Math.random();
|
| 783 |
let foodTypeData;
|
|
|
|
| 25 |
.game-ui {
|
| 26 |
position: absolute;
|
| 27 |
padding: 10px;
|
| 28 |
+
background-color: rgba(0, 0, 20, 0.9);
|
| 29 |
border: 1px solid #0f0;
|
| 30 |
border-radius: 5px;
|
| 31 |
font-size: 1.2em;
|
|
|
|
| 52 |
flex-direction: column;
|
| 53 |
justify-content: center;
|
| 54 |
align-items: center;
|
| 55 |
+
background-color: rgba(0, 10, 50, 0.2);
|
| 56 |
z-index: 10;
|
| 57 |
}
|
| 58 |
#startScreen, #gameOverScreen {
|
|
|
|
| 91 |
pointer-events: auto;
|
| 92 |
}
|
| 93 |
.button:hover {
|
| 94 |
+
background-color: rgba(0, 120, 0, 0.7);
|
| 95 |
transform: translateY(-5px);
|
| 96 |
+
box-shadow: 0px 2px 10px #2eff00, 0 7px 14px rgba(0, 255, 0.1, 0.9), 3px 3px 6px rgba(0, 255, 0.1, 0.3);
|
| 97 |
}
|
| 98 |
.controls {
|
| 99 |
margin-top: 20px;
|
|
|
|
| 121 |
display: none; /* Hidden by default, shown on mobile */
|
| 122 |
}
|
| 123 |
.touchBtn {
|
| 124 |
+
width: 80px;
|
| 125 |
+
height: 80px;
|
| 126 |
background-color: rgba(0, 120, 0, 0.7);
|
| 127 |
+
border: 10px solid #0f0;
|
| 128 |
border-radius: 50%;
|
| 129 |
+
margin: 15px;
|
| 130 |
display: inline-flex;
|
| 131 |
justify-content: center;
|
| 132 |
align-items: center;
|
|
|
|
| 233 |
'HARD': { speedMultiplier: 0.7, obstacleMultiplier: 1.5 }
|
| 234 |
},
|
| 235 |
MAX_OBSTACLE_COUNT: 10, // Maximum number of obstacles
|
| 236 |
+
MAX_OBSTACLEB_COUNT: 3, // Maximum number of obstacles
|
| 237 |
FOOD_TYPES: [
|
| 238 |
{ type: 'regular', color: 0x00ff00, points: 1, speedEffect: 0 },
|
| 239 |
{ type: 'special', color: 0x00ffff, points: 5, speedEffect: -10 },
|
|
|
|
| 257 |
const material = new THREE.MeshBasicMaterial({
|
| 258 |
color: color || 0x00ff00,
|
| 259 |
transparent: true,
|
| 260 |
+
opacity: 0.7
|
| 261 |
});
|
| 262 |
const particle = new THREE.Mesh(this.geometry, material);
|
| 263 |
particle.position.copy(position);
|
|
|
|
| 447 |
this.snake = [];
|
| 448 |
this.food = null;
|
| 449 |
this.obstacles = [];
|
| 450 |
+
this.obstaclesB = [];
|
| 451 |
this.direction = new THREE.Vector3(CONFIG.CELL_SIZE, 0, 0);
|
| 452 |
this.nextDirection = new THREE.Vector3(CONFIG.CELL_SIZE, 0, 0);
|
| 453 |
this.score = 0;
|
|
|
|
| 459 |
this.gameLoopId = null;
|
| 460 |
this.bounds = Math.floor(CONFIG.GRID_SIZE / 2) * CONFIG.CELL_SIZE;
|
| 461 |
this.obstacleCount = CONFIG.MAX_OBSTACLE_COUNT;
|
| 462 |
+
this.obstacleBCount = CONFIG.MAX_OBSTACLEB_COUNT;
|
| 463 |
this.comboCount = 0;
|
| 464 |
this.lastFoodTime = 0;
|
| 465 |
this.currentDifficulty = 'NORMAL';
|
|
|
|
| 476 |
obstacle: new THREE.MeshStandardMaterial({ // Changed to Standard for lighting
|
| 477 |
color: 0xff0000, emissive: 0x550000, roughness: 0.3, metalness: 1, wireframe: false
|
| 478 |
}),
|
| 479 |
+
obstacleB: new THREE.MeshStandardMaterial({ // Changed to Standard for lighting
|
| 480 |
+
color: 0xff0069, emissive: 0x550000, roughness: 0.9, metalness: 0.1, wireframe: false
|
| 481 |
+
}),
|
| 482 |
// Food materials defined per type in chooseFoodType
|
| 483 |
};
|
| 484 |
this.geometries = {
|
| 485 |
segment: new THREE.BoxGeometry(CONFIG.CELL_SIZE, CONFIG.CELL_SIZE, CONFIG.CELL_SIZE),
|
| 486 |
foodBox: new THREE.BoxGeometry(CONFIG.CELL_SIZE * 0.8, CONFIG.CELL_SIZE * 0.8, CONFIG.CELL_SIZE * 0.8),
|
| 487 |
+
foodSphere: new THREE.SphereGeometry(CONFIG.CELL_SIZE * 0.5, 6, 12),
|
| 488 |
foodTetrahedron: new THREE.TetrahedronGeometry(CONFIG.CELL_SIZE * 0.6, 0),
|
| 489 |
+
obstacle: new THREE.BoxGeometry(CONFIG.CELL_SIZE * 0.9, CONFIG.CELL_SIZE * 1.5, CONFIG.CELL_SIZE * 0.9), // Taller obstacles
|
| 490 |
+
obstacleB: new THREE.BoxGeometry(CONFIG.CELL_SIZE * 0.8, CONFIG.CELL_SIZE * 1.25, CONFIG.CELL_SIZE * 0.7) // Taller obstacles
|
| 491 |
};
|
| 492 |
|
| 493 |
this.segmentPool = new ObjectPool(() => {
|
|
|
|
| 502 |
return obstacle;
|
| 503 |
}, CONFIG.MAX_OBSTACLE_COUNT * 1.5);
|
| 504 |
|
| 505 |
+
this.obstacleBPool = new ObjectPool(() => {
|
| 506 |
+
const obstacleB = new THREE.Mesh(this.geometries.obstacleB, this.materials.obstacleB.clone());
|
| 507 |
+
obstacleB.castShadow = true; // If using shadows
|
| 508 |
+
return obstacleB;
|
| 509 |
+
}, CONFIG.MAX_OBSTACLEB_COUNT * 1.2);
|
| 510 |
+
|
| 511 |
this.init(); // Call init before matrixRain if matrixRain depends on game elements
|
| 512 |
this.matrixRain = new MatrixRain(); // Initialize after main game setup if it interacts
|
| 513 |
this.setupEventListeners();
|
|
|
|
| 537 |
// Post-processing for bloom
|
| 538 |
const renderPass = new RenderPass(this.scene, this.camera);
|
| 539 |
const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.2, 0.8, 0.75);
|
| 540 |
+
bloomPass.threshold = 0.2;
|
| 541 |
+
bloomPass.strength = 1.4; // Play with these
|
| 542 |
+
bloomPass.radius = 0.2;
|
| 543 |
|
| 544 |
this.composer = new EffectComposer(this.renderer);
|
| 545 |
this.composer.addPass(renderPass);
|
|
|
|
| 599 |
let collisionWithObstacle = this.obstacles.some(obstacle =>
|
| 600 |
obstacle.position.distanceToSquared(foodPos) < (CONFIG.CELL_SIZE * 0.9)**2
|
| 601 |
);
|
| 602 |
+
|
| 603 |
+
let collisionWithObstacleB = this.obstacles.some(obstacle =>
|
| 604 |
+
obstacleB.position.distanceToSquared(foodPos) < (CONFIG.CELL_SIZE * 0.9)**2
|
| 605 |
+
);
|
| 606 |
|
| 607 |
+
validPosition = !collisionWithSnake && !collisionWithObstacle && !collisionWithObstacleB;
|
| 608 |
attempts++;
|
| 609 |
}
|
| 610 |
|
|
|
|
| 669 |
}
|
| 670 |
}
|
| 671 |
|
| 672 |
+
createObstacleBs() {
|
| 673 |
+
this.obstacleBs.forEach(obstacleB => {
|
| 674 |
+
this.scene.remove(obstacleB);
|
| 675 |
+
this.obstacleBPool.release(obstacleB);
|
| 676 |
+
});
|
| 677 |
+
this.obstacleBs = [];
|
| 678 |
+
|
| 679 |
+
const numCells = CONFIG.GRID_SIZE;
|
| 680 |
+
|
| 681 |
+
for (let i = 0; i < this.obstacleBCount; i++) {
|
| 682 |
+
let obstaclePos;
|
| 683 |
+
let validPosition = false;
|
| 684 |
+
let attempts = 0;
|
| 685 |
+
const maxAttempts = 50;
|
| 686 |
+
|
| 687 |
+
while (!validPosition && attempts < maxAttempts) {
|
| 688 |
+
const xIndex = Math.floor(Math.random() * numCells) - Math.floor((numCells - 1) / 2);
|
| 689 |
+
const zIndex = Math.floor(Math.random() * numCells) - Math.floor((numCells - 1) / 2);
|
| 690 |
+
obstacleBPos = new THREE.Vector3(
|
| 691 |
+
xIndex * CONFIG.CELL_SIZE,
|
| 692 |
+
0, // Obstacles on the grid plane, but mesh is taller
|
| 693 |
+
zIndex * CONFIG.CELL_SIZE
|
| 694 |
+
);
|
| 695 |
+
|
| 696 |
+
let tooCloseToStart = obstacleBPos.lengthSq() < (CONFIG.CELL_SIZE * 4)**2; // Check squared length
|
| 697 |
+
|
| 698 |
+
let collisionWithSnake = this.snake.some(segment =>
|
| 699 |
+
segment.position.distanceToSquared(obstacleBPos) < (CONFIG.CELL_SIZE * 2)**2
|
| 700 |
+
);
|
| 701 |
+
|
| 702 |
+
let collisionWithOtherObstacleB = this.obstacleBs.some(existingObstacleB =>
|
| 703 |
+
existingObstacleB.position.distanceToSquared(obstacleBPos) < (CONFIG.CELL_SIZE * 1.5)**2 // Ensure spacing
|
| 704 |
+
);
|
| 705 |
+
|
| 706 |
+
validPosition = !tooCloseToStart && !collisionWithSnake && !collisionWithOtherObstacle;
|
| 707 |
+
attempts++;
|
| 708 |
+
}
|
| 709 |
+
|
| 710 |
+
if (validPosition) {
|
| 711 |
+
const obstacleB = this.obstacleBPool.get();
|
| 712 |
+
obstacleB.position.copy(obstacleBPos);
|
| 713 |
+
obstacle.position.y = (CONFIG.CELL_SIZE * 1.5 - CONFIG.CELL_SIZE) / 2 - CONFIG.CELL_SIZE / 2; // Adjust Y to sit on grid
|
| 714 |
+
this.obstacleBs.push(obstacleB);
|
| 715 |
+
this.scene.add(obstacleB);
|
| 716 |
+
}
|
| 717 |
+
}
|
| 718 |
+
}
|
| 719 |
+
|
| 720 |
clearGameObjects() {
|
| 721 |
this.snake.forEach(segment => {
|
| 722 |
this.scene.remove(segment);
|
|
|
|
| 736 |
});
|
| 737 |
this.obstacles = [];
|
| 738 |
|
| 739 |
+
this.obstacleBs.forEach(obstacleB => {
|
| 740 |
+
this.scene.remove(obstacleB);
|
| 741 |
+
this.obstacleBPool.release(obstacleB);
|
| 742 |
+
});
|
| 743 |
+
this.obstacleBs = [];
|
| 744 |
+
|
| 745 |
this.particleSystem.clear();
|
| 746 |
}
|
| 747 |
|
|
|
|
| 783 |
}
|
| 784 |
}
|
| 785 |
|
| 786 |
+
for (const obstacleB of this.obstacleBs) {
|
| 787 |
+
if (newHeadPos.distanceToSquared(obstacleB.position) < (CONFIG.CELL_SIZE * 0.75)**2) { // Check collision with obstacle
|
| 788 |
+
this.triggerGameOver();
|
| 789 |
+
return;
|
| 790 |
+
}
|
| 791 |
+
}
|
| 792 |
+
|
| 793 |
const newHead = this.segmentPool.get();
|
| 794 |
newHead.position.copy(newHeadPos);
|
| 795 |
newHead.material = this.materials.snakeHead; // Head material
|
|
|
|
| 856 |
}
|
| 857 |
}
|
| 858 |
|
| 859 |
+
|
| 860 |
chooseFoodType() {
|
| 861 |
const rand = Math.random();
|
| 862 |
let foodTypeData;
|