about summary refs log tree commit diff stats
path: root/html
diff options
context:
space:
mode:
authorelioat <elioat@tilde.institute>2024-10-20 17:39:48 -0400
committerelioat <elioat@tilde.institute>2024-10-20 17:39:48 -0400
commite89532c25f94ebe2860f3912d562556694df57e3 (patch)
tree3ea2d91a0d8f1e5b434582fd149c881564b85bf8 /html
parentf27fbf895a1c8b4b8d590e7d50e11be3282e9267 (diff)
downloadtour-e89532c25f94ebe2860f3912d562556694df57e3.tar.gz
*
Diffstat (limited to 'html')
-rw-r--r--html/broughlike/index.html504
1 files changed, 504 insertions, 0 deletions
diff --git a/html/broughlike/index.html b/html/broughlike/index.html
new file mode 100644
index 0000000..fa0894b
--- /dev/null
+++ b/html/broughlike/index.html
@@ -0,0 +1,504 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Broughlike</title>
+    <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>
+    <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_ENEMY_HEALTH = 7;
+        const MIN_ENEMY_HEALTH = 2;
+        const WALLS_MIN = 5;
+        const WALLS_MAX = 20;
+        const ITEMS_MIN = 0;
+        const ITEMS_MAX = 2;
+        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,
+            bonusDamageTurns: 0,
+            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() * 3); // Between 0 and 2 enemies, FIXME consider making this a tunable constant
+            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)
+                ) 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;
+                    player.bonusDamageTurns = Math.floor(Math.random() * 6) + 5; // Between 5 and 10 turns
+                    console.log("Collected diamond! +3 damage for " + player.bonusDamageTurns + " turns.");
+                } 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 to miss
+            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--;
+            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();
+            generateWalls();
+            generateEnemies();
+            generateItems();
+            render();
+        }
+
+        function checkPlayerAtExit() {
+            if (player.x === exit.x && player.y === exit.y) {
+                player.score += 1;
+                console.log("Next level! Score: " + player.score);
+                combatDots = {};
+                generateExit();
+                generateWalls();
+                generateEnemies();
+                generateItems();
+                render();
+            }
+        }
+
+        function render() {
+            drawGrid();
+            drawWalls();
+            drawExit();
+            drawItems();
+            drawEnemies();
+            drawPlayer();
+            drawCombatDots();
+        }
+
+        const directionMap = {
+            ArrowUp: [0, -1],
+            ArrowDown: [0, 1],
+            ArrowLeft: [-1, 0],
+            ArrowRight: [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 work
+
+        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();
+        generateWalls();
+        generateEnemies();
+        generateItems();
+        render();
+    </script>
+</body>
+</html>
\ No newline at end of file