diff options
author | elioat <elioat@tilde.institute> | 2024-10-23 08:06:49 -0400 |
---|---|---|
committer | elioat <elioat@tilde.institute> | 2024-10-23 08:06:49 -0400 |
commit | 9398fff65f10b01fb0cad35f9ad71ac46452d7b5 (patch) | |
tree | 342fcd14b4a8cc3e2ba625c5d19eb2969ac24312 /html/broughlike | |
parent | d5a010727cfa0399fece6734b4dfc86fee73f24e (diff) | |
download | tour-9398fff65f10b01fb0cad35f9ad71ac46452d7b5.tar.gz |
*
Diffstat (limited to 'html/broughlike')
-rw-r--r-- | html/broughlike/debug.html | 693 |
1 files changed, 693 insertions, 0 deletions
diff --git a/html/broughlike/debug.html b/html/broughlike/debug.html new file mode 100644 index 0000000..923f0dd --- /dev/null +++ b/html/broughlike/debug.html @@ -0,0 +1,693 @@ +<!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 |