// Game variables let scene, camera, renderer, controls; let player, walls = [], enemies = [], coins = [], emojis = [], powerups = []; let score = 0, timeLeft = 60, gameActive = false, gameOver = false; let playerSpeed = 5, invincible = false, doublePoints = false; let clock = new THREE.Clock(); let moveForward = false, moveBackward = false, moveLeft = false, moveRight = false; // Audio elements const bgMusic = document.getElementById('bg-music'); const coinSound = document.getElementById('coin-sound'); const enemySound = document.getElementById('enemy-sound'); const winSound = document.getElementById('win-sound'); const loseSound = document.getElementById('lose-sound'); // UI elements const scoreDisplay = document.getElementById('score-display'); const timerDisplay = document.getElementById('timer'); // Initialize the game function init() { // Create scene scene = new THREE.Scene(); scene.background = new THREE.Color(0x87CEEB); // Create camera camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.set(0, 1.6, 0); // Create renderer renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.shadowMap.enabled = true; document.body.appendChild(renderer.domElement); // Add pointer lock controls controls = new THREE.PointerLockControls(camera, renderer.domElement); // Create player createPlayer(); // Generate maze generateMaze(); // Add lights addLights(); // Add event listeners setupEventListeners(); // Start render loop animate(); // Setup restart button document.getElementById('restart-btn').addEventListener('click', restartGame); } // Create player function createPlayer() { // Create player with better 3D model const group = new THREE.Group(); // Body const body = new THREE.Mesh( new THREE.BoxGeometry(0.5, 1, 0.3), new THREE.MeshStandardMaterial({ color: 0x4ECDC4 }) ); body.position.y = 0.5; // Head const head = new THREE.Mesh( new THREE.SphereGeometry(0.3, 16, 16), new THREE.MeshStandardMaterial({ color: 0xFFD166 }) ); head.position.y = 1.15; // Eyes const eyeGeometry = new THREE.SphereGeometry(0.05, 8, 8); const leftEye = new THREE.Mesh(eyeGeometry, new THREE.MeshStandardMaterial({ color: 0x000000 })); const rightEye = new THREE.Mesh(eyeGeometry, new THREE.MeshStandardMaterial({ color: 0x000000 })); leftEye.position.set(0.1, 1.15, 0.25); rightEye.position.set(-0.1, 1.15, 0.25); group.add(body); group.add(head); group.add(leftEye); group.add(rightEye); player = group; player.castShadow = true; player.position.y = 0.85; scene.add(player); } // Generate maze function generateMaze() { const size = 30; const gridSize = 15; // Add skybox const skyGeometry = new THREE.BoxGeometry(100, 100, 100); const skyMaterial = new THREE.MeshBasicMaterial({ color: 0x87CEEB, side: THREE.BackSide }); const skybox = new THREE.Mesh(skyGeometry, skyMaterial); scene.add(skybox); const maze = []; // Create grid for (let i = 0; i < gridSize; i++) { maze[i] = []; for (let j = 0; j < gridSize; j++) { maze[i][j] = Math.random() > 0.7 ? 1 : 0; } } // Ensure exit path maze[0][0] = 0; maze[gridSize-1][gridSize-1] = 0; // Create walls for (let i = 0; i < gridSize; i++) { for (let j = 0; j < gridSize; j++) { if (maze[i][j] === 1) { const wall = new THREE.Mesh( new THREE.BoxGeometry(size/gridSize, 2, size/gridSize), new THREE.MeshStandardMaterial({ color: 0xFFD166 }) ); wall.position.x = (i - gridSize/2) * (size/gridSize); wall.position.z = (j - gridSize/2) * (size/gridSize); wall.position.y = 1; wall.castShadow = true; wall.receiveShadow = true; scene.add(wall); walls.push(wall); } } } // Create coins for (let i = 0; i < 30; i++) { createCoin(); } // Create enemies for (let i = 0; i < 5; i++) { createEnemy(); } // Create powerups for (let i = 0; i < 3; i++) { createPowerup(); } // Create exit const exit = new THREE.Mesh( new THREE.BoxGeometry(size/gridSize, 2, size/gridSize), new THREE.MeshStandardMaterial({ color: 0x06D6A0, transparent: true, opacity: 0.5 }) ); exit.position.x = (gridSize/2 - 0.5) * (size/gridSize); exit.position.z = (gridSize/2 - 0.5) * (size/gridSize); exit.position.y = 1; scene.add(exit); exit.isExit = true; walls.push(exit); } function createCoin() { const coin = new THREE.Mesh( new THREE.CylinderGeometry(0.3, 0.3, 0.1, 32), new THREE.MeshStandardMaterial({ color: 0xFFD700, metalness: 0.9, roughness: 0.1 }) ); coin.castShadow = true; coin.rotation.x = Math.PI / 2; coin.position.set( (Math.random() - 0.5) * 18, 0.5, (Math.random() - 0.5) * 18 ); coin.castShadow = true; coin.isCoin = true; scene.add(coin); coins.push(coin); } function createEnemy() { const enemy = new THREE.Mesh( new THREE.ConeGeometry(0.4, 1.2, 4), new THREE.MeshStandardMaterial({ color: 0xFF6B6B, emissive: 0x880000, emissiveIntensity: 0.2 }) ); enemy.position.set( (Math.random() - 0.5) * 18, 0.5, (Math.random() - 0.5) * 18 ); enemy.castShadow = true; enemy.speed = 0.02 + Math.random() * 0.03; enemy.direction = new THREE.Vector3(Math.random() - 0.5, 0, Math.random() - 0.5).normalize(); scene.add(enemy); enemies.push(enemy); // Create emoji above enemy const emoji = document.createElement('div'); emoji.className = 'enemy-emote'; emoji.textContent = ['😈', '👹', '👻', '🤡', '👾'][Math.floor(Math.random() * 5)]; document.getElementById('ui').appendChild(emo); emojis.push({ element: emoji, enemy: enemy }); } function addLights() { // Ambient light const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); scene.add(ambientLight); // Directional light (sun) const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(10, 20, 10); directionalLight.castShadow = true; directionalLight.shadow.mapSize.width = 2048; directionalLight.shadow.mapSize.height = 2048; directionalLight.shadow.camera.near = 0.5; directionalLight.shadow.camera.far = 50; directionalLight.shadow.camera.left = -20; directionalLight.shadow.camera.right = 20; directionalLight.shadow.camera.top = 20; directionalLight.shadow.camera.bottom = -20; scene.add(directionalLight); // Hemisphere light for more natural outdoor lighting const hemisphereLight = new THREE.HemisphereLight(0x87CEEB, 0x006400, 0.3); scene.add(hemisphereLight); // Add some point lights for better 3D effect const pointLight1 = new THREE.PointLight(0xFFD166, 0.5, 10); pointLight1.position.set(5, 3, 5); scene.add(pointLight1); const pointLight2 = new THREE.PointLight(0x4ECDC4, 0.5, 10); pointLight2.position.set(-5, 3, -5); scene.add(pointLight2); } function setupEventListeners() { // Movement controls document.addEventListener('keydown', (event) => { switch (event.code) { case 'KeyW': case 'ArrowUp': moveForward = true; break; case 'KeyA': case 'ArrowLeft': moveLeft = true; break; case 'KeyS': case 'ArrowDown': moveBackward = true; break; case 'KeyD': case 'ArrowRight': moveRight = true; break; } }); document.addEventListener('keyup', (event) => { switch (event.code) { case 'KeyW': case 'ArrowUp': moveForward = false; break; case 'KeyA': case 'ArrowLeft': moveLeft = false; break; case 'KeyS': case 'ArrowDown': moveBackward = false; break; case 'KeyD': case 'ArrowRight': moveRight = false; break; } }); // Touch controls const upBtn = document.getElementById('up'); const leftBtn = document.getElementById('left'); const downBtn = document.getElementById('down'); const rightBtn = document.getElementById('right'); const lookBtn = document.getElementById('look'); // Touch start events upBtn.addEventListener('touchstart', (e) => { e.preventDefault(); moveForward = true; }); leftBtn.addEventListener('touchstart', (e) => { e.preventDefault(); moveLeft = true; }); downBtn.addEventListener('touchstart', (e) => { e.preventDefault(); moveBackward = true; }); rightBtn.addEventListener('touchstart', (e) => { e.preventDefault(); moveRight = true; }); lookBtn.addEventListener('touchstart', (e) => { e.preventDefault(); if (!controls.isLocked) { controls.lock(); } }); // Touch end events upBtn.addEventListener('touchend', (e) => { e.preventDefault(); moveForward = false; }); leftBtn.addEventListener('touchend', (e) => { e.preventDefault(); moveLeft = false; }); downBtn.addEventListener('touchend', (e) => { e.preventDefault(); moveBackward = false; }); rightBtn.addEventListener('touchend', (e) => { e.preventDefault(); moveRight = false; }); // Mouse events for desktop upBtn.addEventListener('mousedown', () => moveForward = true); leftBtn.addEventListener('mousedown', () => moveLeft = true); downBtn.addEventListener('mousedown', () => moveBackward = true); rightBtn.addEventListener('mousedown', () => moveRight = true); lookBtn.addEventListener('mousedown', () => { if (!controls.isLocked) { controls.lock(); } }); upBtn.addEventListener('mouseup', () => moveForward = false); leftBtn.addEventListener('mouseup', () => moveLeft = false); downBtn.addEventListener('mouseup', () => moveBackward = false); rightBtn.addEventListener('mouseup', () => moveRight = false); // Pointer lock controls const lockChangeAlert = () => { if (document.pointerLockElement === renderer.domElement) { controls.isLocked = true; } else { controls.isLocked = false; } }; document.addEventListener('pointerlockchange', lockChangeAlert, false); // Window resize window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); } function startGame() { // This function is now empty since we start the game automatically } function restartGame() { // Reset all game state scene = new THREE.Scene(); scene.background = new THREE.Color(0x87CEEB); // Clear all arrays walls = []; enemies = []; coins = []; powerups = []; emojis.forEach(emoji => emoji.element.remove()); emojis = []; // Reset variables score = 0; timeLeft = 60; gameActive = false; gameOver = false; playerSpeed = 5; invincible = false; doublePoints = false; // Update UI scoreDisplay.textContent = `Coins: ${score}`; timerDisplay.textContent = `Time: ${timeLeft}`; // Hide end screen document.getElementById('end-screen').style.display = 'none'; document.getElementById('start-screen').style.display = 'flex'; // Rebuild game elements createPlayer(); generateMaze(); addLights(); // Reattach camera controls.getObject().position.set(0, 1.6, 0); camera.position.set(0, 1.6, 0); camera.lookAt(0, 1.6, -1); } function endGame(win) { gameOver = true; gameActive = false; if (controls.isLocked) { controls.unlock(); } const endScreen = document.getElementById('end-screen'); const endMessage = document.getElementById('end-message'); endScreen.style.display = 'flex'; if (win) { endMessage.textContent = `You Escaped!\nFinal Score: ${score}`; endMessage.style.animation = 'rainbow 2s infinite'; winSound.play(); } else { endMessage.textContent = `Time's Up!\nFinal Score: ${score}`; endMessage.style.color = '#FF6B6B'; loseSound.play(); } } function createPowerup() { const types = ['speed', 'invincible', 'double']; const type = types[Math.floor(Math.random() * types.length)]; let color, shape; switch(type) { case 'speed': color = 0x00FF00; shape = new THREE.SphereGeometry(0.4); break; case 'invincible': color = 0x0000FF; shape = new THREE.TetrahedronGeometry(0.5); break; case 'double': color = 0xFF00FF; shape = new THREE.OctahedronGeometry(0.4); break; } const powerup = new THREE.Mesh( shape, new THREE.MeshStandardMaterial({ color: color, emissive: color, emissiveIntensity: 0.3 }) ); powerup.position.set( (Math.random() - 0.5) * 28, 0.5, (Math.random() - 0.5) * 28 ); powerup.castShadow = true; powerup.type = type; scene.add(powerup); powerups.push(powerup); } function checkCollisions() { // Check coin collisions for (let i = coins.length - 1; i >= 0; i--) { const coin = coins[i]; if (player.position.distanceTo(coin.position) < 1) { scene.remove(coin); coins.splice(i, 1); score += doublePoints ? 20 : 10; scoreDisplay.textContent = `Coins: ${score}`; coinSound.play(); createCoin(); } } // Check powerup collisions for (let i = powerups.length - 1; i >= 0; i--) { const powerup = powerups[i]; if (player.position.distanceTo(powerup.position) < 1) { scene.remove(powerup); powerups.splice(i, 1); switch(powerup.type) { case 'speed': playerSpeed = 8; setTimeout(() => playerSpeed = 5, 10000); break; case 'invincible': invincible = true; setTimeout(() => invincible = false, 15000); break; case 'double': doublePoints = true; setTimeout(() => doublePoints = false, 20000); break; } coinSound.play(); } } // Check wall collisions const playerBox = new THREE.Box3().setFromObject(player); for (const wall of walls) { const wallBox = new THREE.Box3().setFromObject(wall); if (playerBox.intersectsBox(wallBox)) { if (wall.isExit) { endGame(true); } else { // Simple collision response const direction = new THREE.Vector3().subVectors(player.position, wall.position).normalize(); player.position.add(direction.multiplyScalar(0.1)); } } } // Check enemy collisions for (let i = 0; i < enemies.length; i++) { const enemy = enemies[i]; if (player.position.distanceTo(enemy.position) < 1 && !invincible) { score = Math.max(0, score - 5); scoreDisplay.textContent = `Coins: ${score}`; enemySound.play(); // Push player away const direction = new THREE.Vector3().subVectors(player.position, enemy.position).normalize(); player.position.add(direction.multiplyScalar(1.5)); } } } function updateEnemies() { for (let i = 0; i < enemies.length; i++) { const enemy = enemies[i]; // Simple AI - change direction randomly if (Math.random() < 0.01) { enemy.direction = new THREE.Vector3(Math.random() - 0.5, 0, Math.random() - 0.5).normalize(); } // Move enemy enemy.position.add(enemy.direction.clone().multiplyScalar(enemy.speed)); // Boundary check if (enemy.position.x > 9 || enemy.position.x < -9 || enemy.position.z > 9 || enemy.position.z < -9) { enemy.direction.negate(); } // Wall collision const enemyBox = new THREE.Box3().setFromObject(enemy); for (const wall of walls) { const wallBox = new THREE.Box3().setFromObject(wall); if (enemyBox.intersectsBox(wallBox) && !wall.isExit) { enemy.direction.negate(); break; } } // Update emoji position if (emojis[i]) { const emoji = emojis[i].element; const screenPos = enemy.position.clone().project(camera); emoji.style.left = `${(screenPos.x * 0.5 + 0.5) * window.innerWidth}px`; emoji.style.top = `${(-(screenPos.y * 0.5) + 0.5) * window.innerHeight - 50}px`; // Show emoji when enemy is close const distance = player.position.distanceTo(enemy.position); emoji.style.opacity = distance < 5 ? '1' : '0'; } } } function animate() { requestAnimationFrame(animate); const delta = clock.getDelta(); if (gameActive && controls.isLocked) { // Player movement const speed = playerSpeed * delta; if (moveForward) { controls.moveForward(speed); } if (moveBackward) { controls.moveForward(-speed); } if (moveLeft) { controls.moveRight(-speed); } if (moveRight) { controls.moveRight(speed); } // Update player position player.position.copy(controls.getObject().position); player.position.y = 0.85; // Check collisions checkCollisions(); // Update enemies updateEnemies(); } renderer.render(scene, camera); } // Start the game init();