There must be a logical path through the maze to get the main character successfully from the starting point to the finish point. The water pot should change color with each move made by the main character so that we know if it will be a win or a lose when contact is made at the center point.
d32863f
verified
| document.addEventListener('DOMContentLoaded', () => { | |
| // Game constants | |
| const GRID_SIZE = 41; | |
| const CELL_COUNT = GRID_SIZE * GRID_SIZE; | |
| const ENEMY_COUNT = 5; | |
| const FOOD_COUNT = 5; | |
| const MAX_HEALTH = 5; | |
| const ENEMY_SCORE = 100; | |
| const FOOD_SCORE = 50; | |
| // Game state | |
| let playerPosition = (2 * GRID_SIZE) + 2; // 2 rows down, 2 columns in | |
| let health = MAX_HEALTH; | |
| let score = 0; | |
| let gameOver = false; | |
| let maze = []; | |
| let enemies = []; | |
| let foodPositions = []; | |
| let waterPosition = Math.floor((GRID_SIZE * GRID_SIZE) / 2); // Center | |
| let goalPosition = (GRID_SIZE * (GRID_SIZE - 2)) + (GRID_SIZE - 2); // 2 from bottom, 2 from right | |
| // DOM elements | |
| const mazeGrid = document.querySelector('.maze-grid'); | |
| const scoreValue = document.getElementById('scoreValue'); | |
| const healthBars = document.querySelectorAll('.health-bars div'); | |
| const newMazeBtn = document.getElementById('newMazeBtn'); | |
| const resetBtn = document.getElementById('resetBtn'); | |
| // Initialize game | |
| function initGame() { | |
| generateMaze(); | |
| placePlayer(); | |
| placeEnemies(); | |
| placeFood(); | |
| placeWater(); | |
| updateUI(); | |
| addEventListeners(); | |
| } | |
| // Generate maze with walls and paths ensuring a path exists | |
| function generateMaze() { | |
| mazeGrid.innerHTML = ''; | |
| maze = Array(CELL_COUNT).fill().map((_, i) => { | |
| const cell = document.createElement('div'); | |
| cell.className = 'cell'; | |
| cell.dataset.index = i; | |
| const row = Math.floor(i / GRID_SIZE); | |
| const col = i % GRID_SIZE; | |
| // Solid outer perimeter walls | |
| let isWall = (row === 0 || row === GRID_SIZE-1 || col === 0 || col === GRID_SIZE-1); | |
| // Create a guaranteed path from start to goal | |
| if (!isWall) { | |
| // Create a diagonal path from start to goal | |
| const isOnPath = | |
| (row === Math.floor(col * (GRID_SIZE-4)/(GRID_SIZE-4)) + 2) || | |
| (col === Math.floor(row * (GRID_SIZE-4)/(GRID_SIZE-4)) + 2); | |
| // Add some randomness to the path | |
| if (!isOnPath) { | |
| isWall = Math.random() < 0.3; | |
| } | |
| } | |
| // Ensure start, water and goal positions are accessible | |
| if (i === playerPosition || i === waterPosition || i === goalPosition) { | |
| isWall = false; | |
| } | |
| if (isWall) { | |
| cell.classList.add('wall'); | |
| } else { | |
| cell.classList.add('path'); | |
| } | |
| mazeGrid.appendChild(cell); | |
| return { | |
| element: cell, | |
| isWall: isWall | |
| }; | |
| }); | |
| } | |
| // Place player at start position | |
| function placePlayer() { | |
| playerPosition = (2 * GRID_SIZE) + 2; | |
| const cell = maze[playerPosition].element; | |
| cell.classList.add('player'); | |
| cell.innerHTML = '๐ฑ'; | |
| } | |
| // Place enemies randomly | |
| function placeEnemies() { | |
| enemies = []; | |
| while (enemies.length < ENEMY_COUNT) { | |
| const pos = Math.floor(Math.random() * CELL_COUNT); | |
| if (!maze[pos].isWall && | |
| pos !== playerPosition && | |
| pos !== waterPosition && | |
| pos !== goalPosition && | |
| !enemies.includes(pos)) { | |
| enemies.push(pos); | |
| const cell = maze[pos].element; | |
| cell.classList.add('enemy'); | |
| cell.innerHTML = '๐ธ'; | |
| } | |
| } | |
| } | |
| // Place food randomly | |
| function placeFood() { | |
| foodPositions = []; | |
| while (foodPositions.length < FOOD_COUNT) { | |
| const pos = Math.floor(Math.random() * CELL_COUNT); | |
| if (!maze[pos].isWall && | |
| pos !== playerPosition && | |
| pos !== waterPosition && | |
| !enemies.includes(pos) && | |
| !foodPositions.includes(pos)) { | |
| foodPositions.push(pos); | |
| const cell = maze[pos].element; | |
| cell.classList.add('food'); | |
| cell.innerHTML = '๐'; | |
| } | |
| } | |
| } | |
| // Place water at center with random outcome | |
| function placeWater() { | |
| const cell = maze[waterPosition].element; | |
| cell.classList.add('water'); | |
| cell.innerHTML = '๐ง'; | |
| updateWaterColor(); | |
| // Mark goal position | |
| const goalCell = maze[goalPosition].element; | |
| goalCell.classList.add('goal'); | |
| goalCell.innerHTML = '๐'; | |
| } | |
| // Update water color based on random outcome | |
| function updateWaterColor() { | |
| const waterCell = maze[waterPosition].element; | |
| const willWin = Math.random() > 0.5; | |
| waterCell.style.backgroundColor = willWin ? '#bbf7d0' : '#fecaca'; | |
| waterCell.style.border = willWin ? '1px solid #4ade80' : '1px solid #ef4444'; | |
| } | |
| // Update UI elements | |
| function updateUI() { | |
| scoreValue.textContent = score; | |
| // Update health display | |
| healthBars.forEach((bar, i) => { | |
| if (i < health) { | |
| bar.classList.add('bg-green-500'); | |
| bar.classList.remove('bg-gray-300'); | |
| } else { | |
| bar.classList.add('bg-gray-300'); | |
| bar.classList.remove('bg-green-500'); | |
| } | |
| }); | |
| } | |
| // Handle player movement | |
| function movePlayer(direction) { | |
| if (gameOver) return; | |
| let newPosition = playerPosition; | |
| const row = Math.floor(playerPosition / GRID_SIZE); | |
| const col = playerPosition % GRID_SIZE; | |
| switch (direction) { | |
| case 'up': | |
| if (row > 0) newPosition = playerPosition - GRID_SIZE; | |
| break; | |
| case 'down': | |
| if (row < GRID_SIZE - 1) newPosition = playerPosition + GRID_SIZE; | |
| break; | |
| case 'left': | |
| if (col > 0) newPosition = playerPosition - 1; | |
| break; | |
| case 'right': | |
| if (col < GRID_SIZE - 1) newPosition = playerPosition + 1; | |
| break; | |
| } | |
| // Check if new position is valid (not a wall) | |
| if (newPosition !== playerPosition && !maze[newPosition].isWall) { | |
| // Clear current player position | |
| const currentCell = maze[playerPosition].element; | |
| currentCell.classList.remove('player'); | |
| currentCell.innerHTML = ''; | |
| // Check what's at new position | |
| const newCell = maze[newPosition].element; | |
| // Check for enemy collision | |
| if (enemies.includes(newPosition)) { | |
| health--; | |
| score += ENEMY_SCORE; | |
| enemies = enemies.filter(pos => pos !== newPosition); | |
| newCell.classList.remove('enemy'); | |
| if (health <= 0) { | |
| endGame(false); | |
| return; | |
| } | |
| } | |
| // Check for food collection | |
| if (foodPositions.includes(newPosition)) { | |
| health = Math.min(health + 1, MAX_HEALTH); | |
| score += FOOD_SCORE; | |
| foodPositions = foodPositions.filter(pos => pos !== newPosition); | |
| newCell.classList.remove('food'); | |
| } | |
| // Check for water pot | |
| if (newPosition === waterPosition) { | |
| const isWin = Math.random() > 0.5; | |
| endGame(isWin); | |
| return; | |
| } | |
| // Check for win condition (2 from bottom, 2 from right) | |
| if (newPosition === goalPosition) { | |
| endGame(true); | |
| return; | |
| } | |
| // Move player | |
| playerPosition = newPosition; | |
| newCell.classList.add('player'); | |
| newCell.innerHTML = '๐ฑ'; | |
| // Move enemies | |
| moveEnemies(); | |
| // Update water color with each move | |
| updateWaterColor(); | |
| // Update UI | |
| updateUI(); | |
| } | |
| } | |
| // Move enemies towards player | |
| function moveEnemies() { | |
| enemies.forEach((enemyPos, index) => { | |
| const enemyCell = maze[enemyPos].element; | |
| enemyCell.classList.remove('enemy'); | |
| enemyCell.innerHTML = ''; | |
| const playerRow = Math.floor(playerPosition / GRID_SIZE); | |
| const playerCol = playerPosition % GRID_SIZE; | |
| const enemyRow = Math.floor(enemyPos / GRID_SIZE); | |
| const enemyCol = enemyPos % GRID_SIZE; | |
| // Simple AI: move towards player | |
| let newRow = enemyRow; | |
| let newCol = enemyCol; | |
| if (enemyRow < playerRow) newRow++; | |
| else if (enemyRow > playerRow) newRow--; | |
| if (enemyCol < playerCol) newCol++; | |
| else if (enemyCol > playerCol) newCol--; | |
| // Randomize direction sometimes | |
| if (Math.random() < 0.3) { | |
| if (Math.random() < 0.5) { | |
| newRow = enemyRow + (Math.random() < 0.5 ? 1 : -1); | |
| } else { | |
| newCol = enemyCol + (Math.random() < 0.5 ? 1 : -1); | |
| } | |
| } | |
| // Ensure new position is within bounds | |
| newRow = Math.max(0, Math.min(GRID_SIZE - 1, newRow)); | |
| newCol = Math.max(0, Math.min(GRID_SIZE - 1, newCol)); | |
| const newEnemyPos = newRow * GRID_SIZE + newCol; | |
| // Only move if not a wall and not another enemy | |
| if (!maze[newEnemyPos].isWall && !enemies.includes(newEnemyPos) && newEnemyPos !== playerPosition) { | |
| enemies[index] = newEnemyPos; | |
| } | |
| // Update enemy display | |
| const newEnemyCell = maze[enemies[index]].element; | |
| newEnemyCell.classList.add('enemy'); | |
| newEnemyCell.innerHTML = '๐ธ'; | |
| }); | |
| } | |
| // End game with win/lose | |
| function endGame(isWin) { | |
| gameOver = true; | |
| const gameOverDiv = document.createElement('div'); | |
| gameOverDiv.className = 'game-over'; | |
| gameOverDiv.innerHTML = ` | |
| <h2>${isWin ? 'You Won! ๐' : 'Game Over! ๐'}</h2> | |
| <p>Final Score: ${score}</p> | |
| <button onclick="location.reload()">Play Again</button> | |
| `; | |
| mazeGrid.appendChild(gameOverDiv); | |
| } | |
| // Add event listeners | |
| function addEventListeners() { | |
| // Keyboard controls | |
| document.addEventListener('keydown', (e) => { | |
| switch (e.key) { | |
| case 'ArrowUp': | |
| movePlayer('up'); | |
| break; | |
| case 'ArrowDown': | |
| movePlayer('down'); | |
| break; | |
| case 'ArrowLeft': | |
| movePlayer('left'); | |
| break; | |
| case 'ArrowRight': | |
| movePlayer('right'); | |
| break; | |
| } | |
| }); | |
| // New maze button | |
| newMazeBtn.addEventListener('click', () => { | |
| generateMaze(); | |
| placePlayer(); | |
| placeEnemies(); | |
| placeFood(); | |
| placeWater(); | |
| gameOver = false; | |
| score = 0; | |
| health = MAX_HEALTH; | |
| updateUI(); | |
| }); | |
| // Reset button | |
| resetBtn.addEventListener('click', () => { | |
| placePlayer(); | |
| placeEnemies(); | |
| placeFood(); | |
| gameOver = false; | |
| score = 0; | |
| health = MAX_HEALTH; | |
| updateUI(); | |
| }); | |
| } | |
| // Start the game | |
| initGame(); | |
| }); |