|
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
const canvas = document.getElementById('maze-canvas'); |
|
|
const ctx = canvas.getContext('2d'); |
|
|
const timeDisplay = document.getElementById('time'); |
|
|
const movesDisplay = document.getElementById('moves'); |
|
|
const difficultySelect = document.getElementById('difficulty'); |
|
|
const startBtn = document.getElementById('start-btn'); |
|
|
const upBtn = document.getElementById('up-btn'); |
|
|
const downBtn = document.getElementById('down-btn'); |
|
|
const leftBtn = document.getElementById('left-btn'); |
|
|
const rightBtn = document.getElementById('right-btn'); |
|
|
|
|
|
|
|
|
function resizeCanvas() { |
|
|
const container = document.getElementById('maze-container'); |
|
|
const size = Math.min(container.offsetWidth, container.offsetHeight); |
|
|
canvas.width = size; |
|
|
canvas.height = size; |
|
|
} |
|
|
|
|
|
let maze = []; |
|
|
let cellSize = 0; |
|
|
let orientationEnabled = false; |
|
|
let ufo = { x: 0, y: 0 }; |
|
|
let exit = { x: 0, y: 0 }; |
|
|
let moves = 0; |
|
|
let time = 0; |
|
|
let timer = null; |
|
|
let gameRunning = false; |
|
|
let mazeSize = 25; |
|
|
|
|
|
function handleOrientation(event) { |
|
|
if (!gameRunning) return; |
|
|
|
|
|
const tiltThreshold = 15; |
|
|
const beta = event.beta; |
|
|
const gamma = event.gamma; |
|
|
|
|
|
if (beta > tiltThreshold) { |
|
|
moveUfo(0, 1); |
|
|
} else if (beta < -tiltThreshold) { |
|
|
moveUfo(0, -1); |
|
|
} |
|
|
|
|
|
if (gamma > tiltThreshold) { |
|
|
moveUfo(1, 0); |
|
|
} else if (gamma < -tiltThreshold) { |
|
|
moveUfo(-1, 0); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function initGame() { |
|
|
resizeCanvas(); |
|
|
window.addEventListener('resize', resizeCanvas); |
|
|
|
|
|
|
|
|
if (window.DeviceOrientationEvent) { |
|
|
window.addEventListener('deviceorientation', handleOrientation); |
|
|
} |
|
|
|
|
|
switch(difficultySelect.value) { |
|
|
case 'easy': mazeSize = 15; break; |
|
|
case 'medium': mazeSize = 25; break; |
|
|
case 'hard': mazeSize = 35; break; |
|
|
} |
|
|
|
|
|
|
|
|
generateMaze(); |
|
|
drawMaze(); |
|
|
|
|
|
|
|
|
document.addEventListener('keydown', handleKeyPress); |
|
|
upBtn.addEventListener('click', () => moveUfo(0, -1)); |
|
|
downBtn.addEventListener('click', () => moveUfo(0, 1)); |
|
|
leftBtn.addEventListener('click', () => moveUfo(-1, 0)); |
|
|
rightBtn.addEventListener('click', () => moveUfo(1, 0)); |
|
|
|
|
|
|
|
|
startBtn.addEventListener('click', startGame); |
|
|
} |
|
|
|
|
|
|
|
|
function generateMaze() { |
|
|
|
|
|
maze = Array(mazeSize).fill().map(() => Array(mazeSize).fill(1)); |
|
|
|
|
|
|
|
|
ufo = { x: 0, y: 0 }; |
|
|
maze[ufo.y][ufo.x] = 0; |
|
|
|
|
|
|
|
|
exit = { x: mazeSize - 1, y: mazeSize - 1 }; |
|
|
|
|
|
|
|
|
const stack = [{ x: ufo.x, y: ufo.y }]; |
|
|
const directions = [ |
|
|
{ dx: 1, dy: 0 }, |
|
|
{ dx: -1, dy: 0 }, |
|
|
{ dx: 0, dy: 1 }, |
|
|
{ dx: 0, dy: -1 }, |
|
|
{ dx: 1, dy: 1 }, |
|
|
{ dx: -1, dy: -1 }, |
|
|
{ dx: 1, dy: -1 }, |
|
|
{ dx: -1, dy: 1 } |
|
|
]; |
|
|
while (stack.length > 0) { |
|
|
const current = stack[stack.length - 1]; |
|
|
const neighbors = []; |
|
|
|
|
|
|
|
|
for (const dir of directions) { |
|
|
|
|
|
const step = (Math.abs(dir.dx) + Math.abs(dir.dy) === 2) ? 3 : 2; |
|
|
const nx = current.x + dir.dx * step; |
|
|
const ny = current.y + dir.dy * step; |
|
|
if (nx >= 0 && nx < mazeSize && ny >= 0 && ny < mazeSize && maze[ny][nx] === 1) { |
|
|
neighbors.push({ x: nx, y: ny, dir }); |
|
|
} |
|
|
} |
|
|
|
|
|
if (neighbors.length > 0) { |
|
|
const next = neighbors[Math.floor(Math.random() * neighbors.length)]; |
|
|
|
|
|
for (let i = 1; i <= (Math.abs(next.dir.dx) + Math.abs(next.dir.dy) === 2 ? 3 : 2); i++) { |
|
|
const pathX = current.x + next.dir.dx * i; |
|
|
const pathY = current.y + next.dir.dy * i; |
|
|
if (pathX >= 0 && pathX < mazeSize && pathY >= 0 && pathY < mazeSize) { |
|
|
maze[pathY][pathX] = 0; |
|
|
} |
|
|
} |
|
|
stack.push({ x: next.x, y: next.y }); |
|
|
} else { |
|
|
stack.pop(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
maze[exit.y][exit.x] = 0; |
|
|
} |
|
|
|
|
|
|
|
|
function drawMaze() { |
|
|
cellSize = canvas.width / mazeSize; |
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height); |
|
|
|
|
|
|
|
|
ctx.fillStyle = '#374151'; |
|
|
ctx.shadowColor = 'rgba(0, 0, 0, 0.3)'; |
|
|
ctx.shadowBlur = 5; |
|
|
ctx.shadowOffsetX = 2; |
|
|
ctx.shadowOffsetY = 2; |
|
|
for (let y = 0; y < mazeSize; y++) { |
|
|
for (let x = 0; x < mazeSize; x++) { |
|
|
if (maze[y][x] === 1) { |
|
|
ctx.fillRect(x * cellSize, y * cellSize, cellSize, cellSize); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
ctx.fillStyle = '#10B981'; |
|
|
ctx.shadowColor = 'rgba(16, 185, 129, 0.5)'; |
|
|
ctx.shadowBlur = 10; |
|
|
ctx.fillRect(exit.x * cellSize, exit.y * cellSize, cellSize, cellSize); |
|
|
|
|
|
|
|
|
drawUfo(); |
|
|
} |
|
|
|
|
|
|
|
|
function drawUfo() { |
|
|
const centerX = ufo.x * cellSize + cellSize / 2; |
|
|
const centerY = ufo.y * cellSize + cellSize / 2; |
|
|
const radius = cellSize * 0.3; |
|
|
|
|
|
ctx.beginPath(); |
|
|
ctx.ellipse(centerX, centerY, radius, radius * 0.6, 0, 0, Math.PI * 2); |
|
|
ctx.fillStyle = '#7C3AED'; |
|
|
ctx.shadowColor = 'rgba(124, 58, 237, 0.5)'; |
|
|
ctx.shadowBlur = 10; |
|
|
ctx.fill(); |
|
|
|
|
|
|
|
|
ctx.beginPath(); |
|
|
ctx.ellipse(centerX, centerY - radius * 0.2, radius * 0.7, radius * 0.4, 0, 0, Math.PI * 2); |
|
|
ctx.fillStyle = 'rgba(167, 139, 250, 0.7)'; |
|
|
ctx.fill(); |
|
|
|
|
|
|
|
|
ctx.beginPath(); |
|
|
for (let i = 0; i < 5; i++) { |
|
|
const lightX = centerX - radius * 0.6 + i * radius * 0.3; |
|
|
ctx.arc(lightX, centerY + radius * 0.3, radius * 0.1, 0, Math.PI * 2); |
|
|
} |
|
|
ctx.fillStyle = '#F59E0B'; |
|
|
ctx.shadowColor = 'rgba(245, 158, 11, 0.5)'; |
|
|
ctx.shadowBlur = 5; |
|
|
ctx.fill(); |
|
|
} |
|
|
|
|
|
|
|
|
function handleKeyPress(e) { |
|
|
if (!gameRunning) return; |
|
|
|
|
|
switch(e.key) { |
|
|
case 'ArrowUp': moveUfo(0, -1); break; |
|
|
case 'ArrowDown': moveUfo(0, 1); break; |
|
|
case 'ArrowLeft': moveUfo(-1, 0); break; |
|
|
case 'ArrowRight': moveUfo(1, 0); break; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function moveUfo(dx, dy) { |
|
|
if (!gameRunning || (orientationEnabled && dx === 0 && dy === 0)) return; |
|
|
const newX = ufo.x + dx; |
|
|
const newY = ufo.y + dy; |
|
|
|
|
|
|
|
|
if (newX >= 0 && newX < mazeSize && newY >= 0 && newY < mazeSize && maze[newY][newX] === 0) { |
|
|
ufo.x = newX; |
|
|
ufo.y = newY; |
|
|
moves++; |
|
|
movesDisplay.textContent = moves; |
|
|
drawMaze(); |
|
|
|
|
|
|
|
|
if (ufo.x === exit.x && ufo.y === exit.y) { |
|
|
endGame(true); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
document.getElementById('tilt-control').addEventListener('change', function() { |
|
|
orientationEnabled = this.checked; |
|
|
if (orientationEnabled && !gameRunning) { |
|
|
alert('Tilt controls enabled! Tilt your device to move the UFO.'); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
function startGame() { |
|
|
if (gameRunning) return; |
|
|
|
|
|
moves = 0; |
|
|
time = 0; |
|
|
movesDisplay.textContent = moves; |
|
|
timeDisplay.textContent = '00:00'; |
|
|
|
|
|
|
|
|
generateMaze(); |
|
|
drawMaze(); |
|
|
|
|
|
|
|
|
gameRunning = true; |
|
|
timer = setInterval(() => { |
|
|
time++; |
|
|
const minutes = Math.floor(time / 60).toString().padStart(2, '0'); |
|
|
const seconds = (time % 60).toString().padStart(2, '0'); |
|
|
timeDisplay.textContent = `${minutes}:${seconds}`; |
|
|
}, 1000); |
|
|
|
|
|
startBtn.textContent = 'Restart Game'; |
|
|
} |
|
|
|
|
|
|
|
|
function endGame(win) { |
|
|
gameRunning = false; |
|
|
clearInterval(timer); |
|
|
|
|
|
if (win) { |
|
|
setTimeout(() => { |
|
|
alert(`Congratulations! You escaped the maze in ${time} seconds with ${moves} moves!`); |
|
|
}, 100); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
initGame(); |
|
|
}); |