diff options
-rw-r--r-- | html/broughlike/debug.html | 693 | ||||
-rw-r--r-- | html/broughlike/index.html | 133 |
2 files changed, 92 insertions, 734 deletions
diff --git a/html/broughlike/debug.html b/html/broughlike/debug.html deleted file mode 100644 index 923f0dd..0000000 --- a/html/broughlike/debug.html +++ /dev/null @@ -1,693 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>Eli's Broughlike</title> - <meta description="A Broughlike, or something like Flatland."> - <style> - body { - background-color: #f0f0f0; - } - canvas { - width: 90vw; - height: 90vw; - max-width: 600px; - max-height: 600px; - border: 2px solid black; - display: block; - margin: 0 auto; - background-color: #f0f0f0; - } - </style> -</head> -<body> - <p><a href="about.html">About</a></p> - <canvas id="gameCanvas"></canvas> - <script> - - const COLORS = { - grid: '#2d2d2d', - walls: '#2d2d2d', - exit: 'teal', - diamond: 'gold', - pentagon: 'blueviolet', - player: 'rgba(0, 237, 209, ', - enemy: 'rgba(255, 155, 28, ', - combatDotPlayer: '#00edd1', - combatDotEnemy: '#ff731c' - }; - - const GRID_SIZE = 6; - const PLAYER_HEALTH = 10; - const PLAYER_MAX_HEALTH = 12; - const PLAYER_BASE_DAMAGE = 1; - const ENEMY_CHASE_DISTANCE = 4; - const MAX_ENEMIES_ON_LEVEL = 4; - const MAX_ENEMY_HEALTH = 7; - const MIN_ENEMY_HEALTH = 2; - const WALLS_MIN = 5; - const WALLS_MAX = 20; - const ITEMS_MIN = 0; - const ITEMS_MAX = 3; - const DOTS_PER_HIT = 5; - - const canvas = document.getElementById('gameCanvas'); - const ctx = canvas.getContext('2d'); - let tileSize = canvas.width / GRID_SIZE; - - const player = { - x: 0, - y: 0, - health: PLAYER_HEALTH, - score: 0, - damage: PLAYER_BASE_DAMAGE, - totalDamageDone: 0, - totalDamageTaken: 0, - cellsTraveled: 0 - }; - - const exit = { x: Math.floor(Math.random() * GRID_SIZE), y: Math.floor(Math.random() * GRID_SIZE) }; - let walls = []; - let enemies = []; - let items = []; - let combatDots = {}; - - function isValidMove(newX, newY) { - return ( - newX >= 0 && newX < GRID_SIZE && - newY >= 0 && newY < GRID_SIZE && - !walls.some(wall => wall.x === newX && wall.y === newY) - ); - } - - function generateExit() { - let distance = 0; - do { - exit.x = Math.floor(Math.random() * GRID_SIZE); - exit.y = Math.floor(Math.random() * GRID_SIZE); - distance = Math.abs(exit.x - player.x) + Math.abs(exit.y - player.y); - } while (distance < 4); - } - - function generateEnemies() { - enemies = []; - const numEnemies = Math.floor(Math.random() * (MAX_ENEMIES_ON_LEVEL + 1)); // Between 0 and the constant value. - for (let i = 0; i < numEnemies; i++) { - let enemyX, enemyY; - do { - enemyX = Math.floor(Math.random() * GRID_SIZE); - enemyY = Math.floor(Math.random() * GRID_SIZE); - } while ( - (enemyX === player.x && enemyY === player.y) || - (enemyX === exit.x && enemyY === exit.y) || - walls.some(wall => wall.x === enemyX && wall.y === enemyY) - ); - enemies.push({ - x: enemyX, - y: enemyY, - health: Math.floor(Math.random() * (MAX_ENEMY_HEALTH - MIN_ENEMY_HEALTH + 1)) + MIN_ENEMY_HEALTH - }); - } - } - - function generateWalls() { - walls = []; - let numWalls = Math.floor(Math.random() * (WALLS_MAX - WALLS_MIN + 1)) + WALLS_MIN; - while (walls.length < numWalls) { - const wallX = Math.floor(Math.random() * GRID_SIZE); - const wallY = Math.floor(Math.random() * GRID_SIZE); - - if ( - (wallX === player.x && wallY === player.y) || - (wallX === exit.x && wallY === exit.y) || - enemies.some(enemy => enemy.x === wallX && enemy.y === wallY) || - (wallX === 0 && wallY === 0) || // Don't block the player spawn - items.some(item => item.x === wallX && item.y === wallY) - ) continue; - - if (!walls.some(wall => wall.x === wallX && wall.y === wallY)) { - walls.push({ x: wallX, y: wallY }); - } - } - - if (!isPassable()) { - generateWalls(); - } - } - - function generateItems() { - items = []; - const numItems = Math.floor(Math.random() * (ITEMS_MAX - ITEMS_MIN + 1)) + ITEMS_MIN; - for (let i = 0; i < numItems; i++) { - let itemX, itemY; - do { - itemX = Math.floor(Math.random() * GRID_SIZE); - itemY = Math.floor(Math.random() * GRID_SIZE); - } while ( - (itemX === player.x && itemY === player.y) || - (itemX === exit.x && itemY === exit.y) || - walls.some(wall => wall.x === itemX && wall.y === itemY) || - enemies.some(enemy => enemy.x === itemX && enemy.y === itemY) - ); - const itemType = Math.random() < 0.5 ? 'diamond' : 'pentagon'; // 50% chance for each type - items.push({ x: itemX, y: itemY, type: itemType }); - } - } - - function isPassable() { - const visited = Array(GRID_SIZE).fill().map(() => Array(GRID_SIZE).fill(false)); - - function dfs(x, y) { - if (x < 0 || x >= GRID_SIZE || y < 0 || y >= GRID_SIZE) return false; - if (visited[x][y]) return false; - if (walls.some(wall => wall.x === x && wall.y === y)) return false; - visited[x][y] = true; - if (x === exit.x && y === exit.y) return true; - - return dfs(x + 1, y) || dfs(x - 1, y) || dfs(x, y + 1) || dfs(x, y - 1); - } - - return dfs(player.x, player.y); - } - - function drawGrid() { - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.strokeStyle = COLORS.grid; - for (let row = 0; row < GRID_SIZE; row++) { - for (let col = 0; col < GRID_SIZE; col++) { - ctx.strokeRect(col * tileSize, row * tileSize, tileSize, tileSize); - } - } - } - - function drawExit() { - const x = exit.x * tileSize + tileSize / 2; - const y = exit.y * tileSize + tileSize / 2; - ctx.beginPath(); - ctx.moveTo(x, y - tileSize / 3); - ctx.lineTo(x + tileSize / 3, y + tileSize / 3); - ctx.lineTo(x - tileSize / 3, y + tileSize / 3); - ctx.closePath(); - ctx.fillStyle = COLORS.exit; - ctx.fill(); - } - - function drawWalls() { - ctx.fillStyle = COLORS.walls; - walls.forEach(wall => { - ctx.fillRect(wall.x * tileSize, wall.y * tileSize, tileSize, tileSize); - }); - } - - function drawItems() { - items.forEach(item => { - const x = item.x * tileSize + tileSize / 2; - const y = item.y * tileSize + tileSize / 2; - ctx.fillStyle = item.type === 'diamond' ? COLORS.diamond : COLORS.pentagon; - ctx.beginPath(); - if (item.type === 'diamond') { - ctx.moveTo(x, y - tileSize / 4); - ctx.lineTo(x + tileSize / 4, y); - ctx.lineTo(x, y + tileSize / 4); - ctx.lineTo(x - tileSize / 4, y); - } else { - const sides = 5; - const radius = tileSize / 4; - for (let i = 0; i < sides; i++) { - const angle = (i * 2 * Math.PI) / sides - Math.PI / 2; - const pointX = x + radius * Math.cos(angle); - const pointY = y + radius * Math.sin(angle); - if (i === 0) ctx.moveTo(pointX, pointY); - else ctx.lineTo(pointX, pointY); - } - } - ctx.closePath(); - ctx.fill(); - }); - } - - function drawEnemies() { - enemies.forEach(enemy => { - const x = enemy.x * tileSize + tileSize / 2; - const y = enemy.y * tileSize + tileSize / 2; - const opacity = enemy.health / MAX_ENEMY_HEALTH; // Opacity based on health - ctx.beginPath(); - ctx.arc(x, y, tileSize / 3, 0, 2 * Math.PI); - ctx.fillStyle = `${COLORS.enemy}${opacity})`; - ctx.fill(); - ctx.lineWidth = 2; - ctx.strokeStyle = '#2d2d2d'; - ctx.stroke(); - }); - } - - function drawPlayer() { - const x = player.x * tileSize + tileSize / 2; - const y = player.y * tileSize + tileSize / 2; - const radius = tileSize / 3; - const playerOpacity = player.health / PLAYER_HEALTH; // Opacity based on health - ctx.beginPath(); - for (let i = 0; i < 6; i++) { - const angle = (Math.PI / 3) * i; - const hexX = x + radius * Math.cos(angle); - const hexY = y + radius * Math.sin(angle); - if (i === 0) { - ctx.moveTo(hexX, hexY); - } else { - ctx.lineTo(hexX, hexY); - } - } - ctx.closePath(); - ctx.fillStyle = `${COLORS.player}${playerOpacity})`; - ctx.fill(); - ctx.lineWidth = 2; - ctx.strokeStyle = '#2d2d2d'; - ctx.stroke(); - } - - function drawCombatDots() { - for (const key in combatDots) { - const [cellX, cellY] = key.split(',').map(Number); - const dots = combatDots[key]; - dots.forEach(dot => { - ctx.beginPath(); - ctx.arc(cellX * tileSize + dot.x, cellY * tileSize + dot.y, 2, 0, Math.PI * 2); - ctx.fillStyle = dot.color; - ctx.fill(); - ctx.closePath(); - }); - } - } - - function handleItemCollection() { - const collectedItem = items.find(item => item.x === player.x && item.y === player.y); - if (collectedItem) { - if (collectedItem.type === 'diamond') { - player.damage += 3; - console.log("Collected diamond! +3 damage on this level."); - } else if (collectedItem.type === 'pentagon') { - const healAmount = Math.floor(Math.random() * 2) + 1; - player.health = Math.min(player.health + healAmount, PLAYER_MAX_HEALTH); - console.log("Collected pentagon! Healed " + healAmount + " health."); - } - items = items.filter(item => item !== collectedItem); // Remove collected item - } - } - - // Function to add dots for damage in combat (including adjacent cells) - function addCombatDots(x, y, color) { - const cellsToFill = [ - [x, y], // Center - [x - 1, y], // Left - [x + 1, y], // Right - [x, y - 1], // Up - [x, y + 1], // Down - [x - 1, y - 1], // Top-left - [x + 1, y - 1], // Top-right - [x - 1, y + 1], // Bottom-left - [x + 1, y + 1] // Bottom-right - ]; - - cellsToFill.forEach(([cellX, cellY]) => { - if (cellX >= 0 && cellX < GRID_SIZE && cellY >= 0 && cellY < GRID_SIZE) { - const key = `${cellX},${cellY}`; - if (!combatDots[key]) { - combatDots[key] = []; - } - for (let i = 0; i < DOTS_PER_HIT; i++) { - combatDots[key].push({ - x: Math.random() * tileSize, - y: Math.random() * tileSize, - color: color - }); - } - } - }); - } - - function movePlayer(dx, dy) { - const newX = player.x + dx; - const newY = player.y + dy; - if (isValidMove(newX, newY)) { - const enemyInTargetCell = enemies.find(enemy => enemy.x === newX && enemy.y === newY); - if (!enemyInTargetCell) { - if (newX !== player.x || newY !== player.y) player.cellsTraveled++; - player.x = newX; - player.y = newY; - handleItemCollection(); // Check if the player collected an item - checkPlayerAtExit(); // Check if player reached the exit after moving - } else { - // Enemy in the target cell, stay in place and do combat - handleDamage(player, enemyInTargetCell); - } - } - moveEnemies(); - render(); - } - - // Chase logic (naive) - function moveEnemies() { - enemies.forEach(enemy => { - const distance = Math.abs(enemy.x - player.x) + Math.abs(enemy.y - player.y); - if (distance <= ENEMY_CHASE_DISTANCE) { - let dx = 0, dy = 0; - if (enemy.x < player.x && isValidMove(enemy.x + 1, enemy.y)) dx = 1; - else if (enemy.x > player.x && isValidMove(enemy.x - 1, enemy.y)) dx = -1; - else if (enemy.y < player.y && isValidMove(enemy.x, enemy.y + 1)) dy = 1; - else if (enemy.y > player.x && isValidMove(enemy.x, enemy.y - 1)) dy = -1; - - if (!enemies.some(e => e.x === enemy.x + dx && e.y === enemy.y)) { - enemy.x += dx; - enemy.y += dy; - } - } - }); - } - - function handleDamage(player, enemy) { - const enemyMisses = Math.random() < 0.2; // 1 in 5 chance the enemy misses you - const cellX = player.x; - const cellY = player.y; - - if (!enemyMisses) { - player.health--; - player.totalDamageTaken++; - addCombatDots(cellX, cellY, COLORS.combatDotPlayer); // Add dots for player damage - console.log("Enemy hit! Player health: " + player.health); - } else { - console.log("Enemy missed!"); - } - - enemy.health = enemy.health - player.damage; - player.totalDamageDone++; - addCombatDots(cellX, cellY, COLORS.combatDotEnemy); // Add dots for enemy damage - console.log("Player hit! Enemy health: " + enemy.health); - - if (enemy.health <= 0) { - enemies = enemies.filter(e => e !== enemy); - } - - if (player.health <= 0) { - alert(`Dead\n\nScore: ${player.score}\nDistance Traveled: ${player.cellsTraveled}\nTotal Damage Dealt: ${player.totalDamageDone}\nTotal Damage Received: ${player.totalDamageTaken}`); - resetGame(); - } - } - - function resetGame() { - player.health = PLAYER_HEALTH; - player.damage = PLAYER_BASE_DAMAGE; - player.bonusDamageTurns = 0; - player.totalDamageDone = 0; - player.totalDamageTaken = 0; - player.cellsTraveled = 0; - player.score = 0; - player.x = 0; - player.y = 0; - combatDots = {}; - generateExit(); - generateEnemies(); - generateItems(); - generateWalls(); - render(); - } - - function checkPlayerAtExit() { - if (player.x === exit.x && player.y === exit.y) { - player.score += 1; - player.damage = PLAYER_BASE_DAMAGE; - console.group("Level complete! " + player.score); - console.log("Score: " + player.score); - console.log("Current health: " + player.health); - console.log("Distance Traveled: " + player.cellsTraveled); - console.log("Total Damage Dealt: " + player.totalDamageDone); - console.log("Total Damage Received: " + player.totalDamageTaken); - console.groupEnd(); - combatDots = {}; - generateExit(); - generateEnemies(); - generateItems(); - generateWalls(); - render(); - } - } - - function render() { - drawGrid(); - drawExit(); - drawItems(); - drawEnemies(); - drawPlayer(); - drawWalls(); - drawCombatDots(); - } - - const directionMap = { - ArrowUp: [0, -1], - ArrowDown: [0, 1], - ArrowLeft: [-1, 0], - ArrowRight: [1, 0], - w: [0, -1], - s: [0, 1], - a: [-1, 0], - d: [1, 0], - h: [-1, 0], - j: [0, 1], - k: [0, -1], - l: [1, 0] - }; - - document.addEventListener('keydown', (e) => { - const direction = directionMap[e.key]; - if (direction) { - movePlayer(...direction); - checkPlayerAtExit(); - render(); - } - }); - - let touchStartX = 0; - let touchStartY = 0; - - canvas.addEventListener('touchstart', (e) => { - e.preventDefault(); // Prevent scrolling on touchstart - touchStartX = e.touches[0].clientX; - touchStartY = e.touches[0].clientY; - }); - - canvas.addEventListener('touchend', (e) => { - e.preventDefault(); // Prevent scrolling on touchend - const touchEndX = e.changedTouches[0].clientX; - const touchEndY = e.changedTouches[0].clientY; - const dx = touchEndX - touchStartX; - const dy = touchEndY - touchStartY; - - if (Math.abs(dx) > Math.abs(dy)) { - // Horizontal swipe - if (dx > 0) { - movePlayer(1, 0); // Swipe right - } else { - movePlayer(-1, 0); // Swipe left - } - } else { - // Vertical swipe - if (dy > 0) { - movePlayer(0, 1); // Swipe down - } else { - movePlayer(0, -1); // Swipe up - } - } - - render(); - }, { passive: false }); // TIL you can use passive set to false to help make preventDefault actually work? Feels like superstition - - const resizeCanvas = () => { - const rect = canvas.getBoundingClientRect(); - canvas.width = rect.width; - canvas.height = rect.height; - tileSize = canvas.width / GRID_SIZE; // Update tile size based on canvas dimensions - render(); - }; - - window.addEventListener('resize', resizeCanvas); - resizeCanvas(); - - // Initial level setup - // generateExit(); - // generateEnemies(); - // generateItems(); - // generateWalls(); - // render(); - - function generate100Levels() { - const container = document.createElement('div'); - container.style.display = 'grid'; - container.style.gridTemplateColumns = 'repeat(10, 1fr)'; - container.style.gridGap = '10px'; - document.body.appendChild(container); - - for (let i = 0; i < 100; i++) { - const levelCanvas = document.createElement('canvas'); - levelCanvas.width = canvas.width; // use the same size as the global canvas - levelCanvas.height = canvas.height; - container.appendChild(levelCanvas); - - // Generate and render the level using a new context for this canvas - generateAndRenderCustomLevel(levelCanvas); - } -} - -function generateAndRenderCustomLevel(customCanvas) { - const customCtx = customCanvas.getContext('2d'); - - // Clone the global generation functions into isolated functions using a local context - const customPlayer = Object.assign({}, player); // Create a fresh player object for each level - const customExit = generateCustomExit(customPlayer); - const customWalls = generateCustomWalls(customPlayer, customExit); - const customEnemies = generateCustomEnemies(customPlayer, customExit, customWalls); - const customItems = generateCustomItems(customPlayer, customExit, customWalls, customEnemies); - - // Now use the local variables to render the level on the custom canvas - renderCustomLevel(customCtx, customCanvas, customPlayer, customExit, customWalls, customEnemies, customItems); -} - -// Custom versions of the generation and rendering functions -function generateCustomExit(player) { - let exit = { x: 0, y: 0 }; - let distance = 0; - do { - exit.x = Math.floor(Math.random() * GRID_SIZE); - exit.y = Math.floor(Math.random() * GRID_SIZE); - distance = Math.abs(exit.x - player.x) + Math.abs(exit.y - player.y); - } while (distance < 4); - return exit; -} - -function generateCustomWalls(player, exit) { - let walls = []; - let numWalls = Math.floor(Math.random() * (WALLS_MAX - WALLS_MIN + 1)) + WALLS_MIN; - while (walls.length < numWalls) { - const wallX = Math.floor(Math.random() * GRID_SIZE); - const wallY = Math.floor(Math.random() * GRID_SIZE); - if ( - (wallX === player.x && wallY === player.y) || - (wallX === exit.x && wallY === exit.y) - ) continue; - if (!walls.some(wall => wall.x === wallX && wall.y === wallY)) { - walls.push({ x: wallX, y: wallY }); - } - } - return walls; -} - -function generateCustomEnemies(player, exit, walls) { - let enemies = []; - const numEnemies = Math.floor(Math.random() * (MAX_ENEMIES_ON_LEVEL + 1)); - for (let i = 0; i < numEnemies; i++) { - let enemyX, enemyY; - do { - enemyX = Math.floor(Math.random() * GRID_SIZE); - enemyY = Math.floor(Math.random() * GRID_SIZE); - } while ( - (enemyX === player.x && enemyY === player.y) || - (enemyX === exit.x && enemyY === exit.y) || - walls.some(wall => wall.x === enemyX && wall.y === enemyY) - ); - enemies.push({ - x: enemyX, - y: enemyY, - health: Math.floor(Math.random() * (MAX_ENEMY_HEALTH - MIN_ENEMY_HEALTH + 1)) + MIN_ENEMY_HEALTH - }); - } - return enemies; -} - -function generateCustomItems(player, exit, walls, enemies) { - let items = []; - const numItems = Math.floor(Math.random() * (ITEMS_MAX - ITEMS_MIN + 1)) + ITEMS_MIN; - for (let i = 0; i < numItems; i++) { - let itemX, itemY; - do { - itemX = Math.floor(Math.random() * GRID_SIZE); - itemY = Math.floor(Math.random() * GRID_SIZE); - } while ( - (itemX === player.x && itemY === player.y) || - (itemX === exit.x && itemY === exit.y) || - walls.some(wall => wall.x === itemX && wall.y === itemY) || - enemies.some(enemy => enemy.x === itemX && enemy.y === itemY) - ); - const itemType = Math.random() < 0.5 ? 'diamond' : 'pentagon'; - items.push({ x: itemX, y: itemY, type: itemType }); - } - return items; -} - -// This custom render function will render all the elements on the custom canvas -function renderCustomLevel(ctx, canvas, player, exit, walls, enemies, items) { - // Clear the canvas - ctx.clearRect(0, 0, canvas.width, canvas.height); - - const tileSize = canvas.width / GRID_SIZE; - - // Draw grid - ctx.strokeStyle = COLORS.grid; - for (let row = 0; row < GRID_SIZE; row++) { - for (let col = 0; col < GRID_SIZE; col++) { - ctx.strokeRect(col * tileSize, row * tileSize, tileSize, tileSize); - } - } - - // Draw exit - ctx.fillStyle = COLORS.exit; - ctx.fillRect(exit.x * tileSize, exit.y * tileSize, tileSize, tileSize); - - // Draw walls - ctx.fillStyle = COLORS.walls; - walls.forEach(wall => { - ctx.fillRect(wall.x * tileSize, wall.y * tileSize, tileSize, tileSize); - }); - - // Draw enemies - enemies.forEach(enemy => { - ctx.beginPath(); - ctx.arc(enemy.x * tileSize + tileSize / 2, enemy.y * tileSize + tileSize / 2, tileSize / 3, 0, 2 * Math.PI); - ctx.fillStyle = `${COLORS.enemy}${enemy.health / MAX_ENEMY_HEALTH})`; - ctx.fill(); - ctx.stroke(); - }); - - // Draw items - items.forEach(item => { - const x = item.x * tileSize + tileSize / 2; - const y = item.y * tileSize + tileSize / 2; - ctx.fillStyle = item.type === 'diamond' ? COLORS.diamond : COLORS.pentagon; - ctx.beginPath(); - if (item.type === 'diamond') { - ctx.moveTo(x, y - tileSize / 4); - ctx.lineTo(x + tileSize / 4, y); - ctx.lineTo(x, y + tileSize / 4); - ctx.lineTo(x - tileSize / 4, y); - } else { - const sides = 5; - const radius = tileSize / 4; - for (let i = 0; i < sides; i++) { - const angle = (i * 2 * Math.PI) / sides - Math.PI / 2; - const pointX = x + radius * Math.cos(angle); - const pointY = y + radius * Math.sin(angle); - if (i === 0) ctx.moveTo(pointX, pointY); - else ctx.lineTo(pointX, pointY); - } - } - ctx.closePath(); - ctx.fill(); - }); - - // Draw player - ctx.fillStyle = `${COLORS.player}${player.health / PLAYER_HEALTH})`; - ctx.fillRect(player.x * tileSize, player.y * tileSize, tileSize, tileSize); -} - - generate100Levels(); - </script> -</body> -</html> \ No newline at end of file diff --git a/html/broughlike/index.html b/html/broughlike/index.html index e643527..71cd9d7 100644 --- a/html/broughlike/index.html +++ b/html/broughlike/index.html @@ -39,18 +39,18 @@ }; const GRID_SIZE = 6; - const PLAYER_HEALTH = 10; - const PLAYER_MAX_HEALTH = 12; + const PLAYER_HEALTH = 12; + const PLAYER_MAX_HEALTH = 16; const PLAYER_BASE_DAMAGE = 1; const ENEMY_CHASE_DISTANCE = 4; - const MAX_ENEMIES_ON_LEVEL = 4; + const MAX_ENEMIES_ON_LEVEL = 3; const MAX_ENEMY_HEALTH = 7; const MIN_ENEMY_HEALTH = 2; const WALLS_MIN = 5; const WALLS_MAX = 20; - const ITEMS_MIN = 0; + const ITEMS_MIN = 1; const ITEMS_MAX = 3; - const DOTS_PER_HIT = 5; + const DOTS_PER_HIT = 7; const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); @@ -121,11 +121,10 @@ const wallY = Math.floor(Math.random() * GRID_SIZE); if ( - (wallX === player.x && wallY === player.y) || - (wallX === exit.x && wallY === exit.y) || - enemies.some(enemy => enemy.x === wallX && enemy.y === wallY) || - (wallX === 0 && wallY === 0) || // Don't block the player spawn - items.some(item => item.x === wallX && item.y === wallY) + (wallX === player.x && wallY === player.y) || // Check that a wall is not placed on the starting position + (wallX === exit.x && wallY === exit.y) || // Check that a wall is not placed on the exit + enemies.some(enemy => enemy.x === wallX && enemy.y === wallY) || // Check that a wall is not placed on any enemies + items.some(item => item.x === wallX && item.y === wallY) // Check that a wall is not placed on any items ) continue; if (!walls.some(wall => wall.x === wallX && wall.y === wallY)) { @@ -150,7 +149,8 @@ (itemX === player.x && itemY === player.y) || (itemX === exit.x && itemY === exit.y) || walls.some(wall => wall.x === itemX && wall.y === itemY) || - enemies.some(enemy => enemy.x === itemX && enemy.y === itemY) + enemies.some(enemy => enemy.x === itemX && enemy.y === itemY) || + items.some(item => item.x === itemX && item.y === itemY) ); const itemType = Math.random() < 0.5 ? 'diamond' : 'pentagon'; // 50% chance for each type items.push({ x: itemX, y: itemY, type: itemType }); @@ -258,6 +258,7 @@ drawCharacterBorder(x, y, radius, damageTaken); }); } + function drawPlayer() { const x = player.x * tileSize + tileSize / 2; @@ -313,14 +314,13 @@ } } - // Function to add dots for damage in combat (including adjacent cells) function addCombatDots(x, y, color) { const cellsToFill = [ - [x, y], // Center - [x - 1, y], // Left - [x + 1, y], // Right - [x, y - 1], // Up - [x, y + 1], // Down + [x, y], // Center + [x - 1, y], // Left + [x + 1, y], // Right + [x, y - 1], // Up + [x, y + 1], // Down [x - 1, y - 1], // Top-left [x + 1, y - 1], // Top-right [x - 1, y + 1], // Bottom-left @@ -347,44 +347,95 @@ function movePlayer(dx, dy) { const newX = player.x + dx; const newY = player.y + dy; - if (isValidMove(newX, newY)) { + if (isValidMove(newX, newY) && !enemies.some(enemy => enemy.x === newX && enemy.y === newY)) { + if (newX !== player.x || newY !== player.y) player.cellsTraveled++; + player.x = newX; + player.y = newY; + handleItemCollection(); // Did the player collect an item? + checkPlayerAtExit(); // Did the player get to the exit after moving? + } else { + // If an enemy is in the target cell, player stays put and does combat const enemyInTargetCell = enemies.find(enemy => enemy.x === newX && enemy.y === newY); - if (!enemyInTargetCell) { - if (newX !== player.x || newY !== player.y) player.cellsTraveled++; - player.x = newX; - player.y = newY; - handleItemCollection(); // Check if the player collected an item - checkPlayerAtExit(); // Check if player reached the exit after moving - } else { - // Enemy in the target cell, stay in place and do combat + if (enemyInTargetCell) { handleDamage(player, enemyInTargetCell); } } moveEnemies(); render(); } - - // Chase logic (naive) + function moveEnemies() { enemies.forEach(enemy => { - const distance = Math.abs(enemy.x - player.x) + Math.abs(enemy.y - player.y); - if (distance <= ENEMY_CHASE_DISTANCE) { - let dx = 0, dy = 0; - if (enemy.x < player.x && isValidMove(enemy.x + 1, enemy.y)) dx = 1; - else if (enemy.x > player.x && isValidMove(enemy.x - 1, enemy.y)) dx = -1; - else if (enemy.y < player.y && isValidMove(enemy.x, enemy.y + 1)) dy = 1; - else if (enemy.y > player.x && isValidMove(enemy.x, enemy.y - 1)) dy = -1; - - if (!enemies.some(e => e.x === enemy.x + dx && e.y === enemy.y)) { - enemy.x += dx; - enemy.y += dy; + const distanceToPlayer = Math.abs(enemy.x - player.x) + Math.abs(enemy.y - player.y); + const distanceToExit = Math.abs(enemy.x - exit.x) + Math.abs(enemy.y - exit.y); + + const target = distanceToPlayer <= ENEMY_CHASE_DISTANCE ? player : exit; + const path = findPath(enemy, target); + + if (path.length > 1) { + const nextStep = path[1]; + const enemyInNextStep = enemies.find(e => e.x === nextStep.x && e.y === nextStep.y); + + // Is the next step occupied by an enemy? + if (!enemyInNextStep && !(nextStep.x === player.x && nextStep.y === player.y)) { + // Move to the next place + enemy.x = nextStep.x; + enemy.y = nextStep.y; + } else if (nextStep.x === player.x && nextStep.y === player.y) { + // If the player is in the next step, stay put and do combat + handleDamage(player, enemy); } } }); } + function findPath(start, goal) { + const queue = [{ x: start.x, y: start.y, path: [] }]; + const visited = new Set(); + visited.add(`${start.x},${start.y}`); + + while (queue.length > 0) { + const { x, y, path } = queue.shift(); + const newPath = [...path, { x, y }]; + + if (x === goal.x && y === goal.y) { + return newPath; + } + + const directions = [ + { dx: 1, dy: 0 }, + { dx: -1, dy: 0 }, + { dx: 0, dy: 1 }, + { dx: 0, dy: -1 } + ]; + + directions.forEach(({ dx, dy }) => { + const newX = x + dx; + const newY = y + dy; + const key = `${newX},${newY}`; + + // Check if the new position is within the level and if it is passable + if ( + newX >= 0 && newX < GRID_SIZE && + newY >= 0 && newY < GRID_SIZE && + // Have we already been here? + !visited.has(key) && + // Is it a wall? + !walls.some(wall => wall.x === newX && wall.y === newY) && + // Is there an enemy already there? + !enemies.some(enemy => enemy.x === newX && enemy.y === newY) + ) { + queue.push({ x: newX, y: newY, path: newPath }); + visited.add(key); + } + }); + } + + return []; + } + function handleDamage(player, enemy) { - const enemyMisses = Math.random() < 0.2; // 1 in 5 chance the enemy misses you + const enemyMisses = Math.random() < 0.5; // 50% chance the enemy misses you const cellX = player.x; const cellY = player.y; @@ -457,10 +508,10 @@ function render() { drawGrid(); + drawPlayer(); drawExit(); drawItems(); drawEnemies(); - drawPlayer(); drawWalls(); drawCombatDots(); } |