From 1d8f185880be10636e731bbc6b215601da0b169a Mon Sep 17 00:00:00 2001 From: elioat Date: Fri, 29 Dec 2023 15:12:41 -0500 Subject: * --- js/game-frame/game.js | 221 +++++++++++++++++++++++++++++++++++++---------- js/game-frame/index.html | 5 +- 2 files changed, 176 insertions(+), 50 deletions(-) diff --git a/js/game-frame/game.js b/js/game-frame/game.js index 2a3d3b6..eed3863 100644 --- a/js/game-frame/game.js +++ b/js/game-frame/game.js @@ -6,6 +6,30 @@ const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); canvas.width = 512; canvas.height = 512; +const startingX = 0; +const startingY = 0; + + + + + +// Misc. helpers +const camera = { + x: startingX, + y: startingY, + width: canvas.width, + height: canvas.height +}; + +const cons = ["b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "r", "s", "t", "v", "w", "z", "ch", "sh", "zh", "th"]; +const vow = ["a", "e", "i", "o", "u", "y", "ee", "ai", "ae", "au"]; +const rpick = t => t[Math.floor(Math.random() * t.length)]; +const syl = () => rpick(cons) + rpick(vow); +const word = () => { + const syllables = Array(Math.floor(Math.random() * 3) + 1).fill(null).map(() => syl()).join(''); + return Math.random() > 0.2 ? syllables + rpick(cons) : syllables; +}; +const speak = numberOfWords => Array(numberOfWords).fill(null).map(() => word()).join(' '); @@ -15,12 +39,12 @@ canvas.height = 512; // Create the player // This object tracks all information about the player chracter const player = { - x: 0, // X coordinate on the canvas - y: 0, // Y coordinate on the canvas - width: 8, // Width of the player - height: 8, // Height of the player - step: 8, // How many pixels the player moves per step - color: 'blue', // Color of the player + x: startingX, // X coordinate on the canvas + y: startingY, // Y coordinate on the canvas + width: 8, // Width of the player + height: 8, // Height of the player + step: 8, // How many pixels the player moves per step + color: 'blue', // Color of the player spriteUrl: "chickadee.svg" // Sprite of the player, if any }; @@ -47,18 +71,19 @@ playerSprite.src = player.spriteUrl; // Map const TILE_SIZE = 64; -// Create the map -// FIXME: This seems a bit clunky, and doesn't make it easy to support maps larger than our canvas. -// TODO: Consider either adding a camera that is restricted to the size of the canvas, or making it so that you can swap in and out different maps, effectively loading in a new area of the game const map = [ - [{ walkable: true, color: 'pink' }, { walkable: false, color: 'grey' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }], - [{ walkable: true, color: 'pink' }, { walkable: false, color: 'grey' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: false, color: 'grey' }, { walkable: true, color: 'pink' }], - [{ walkable: true, color: 'pink' }, { walkable: false, color: 'grey' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }], - [{ walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: false, color: 'grey' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }], - [{ walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: false, color: 'grey' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }], - [{ walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }], - [{ walkable: true, color: 'pink' }, { walkable: false, color: 'grey' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: false, color: 'grey' }, { walkable: true, color: 'pink' }], - [{ walkable: true, color: 'pink' }, { walkable: false, color: 'grey' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }] + [{ walkable: true, color: 'pink' }, { walkable: false, color: 'grey' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }], + [{ walkable: true, color: 'pink' }, { walkable: false, color: 'grey' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: false, color: 'grey' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }], + [{ walkable: true, color: 'pink' }, { walkable: false, color: 'grey' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }], + [{ walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: false, color: 'grey' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }], + [{ walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: false, color: 'grey' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }], + [{ walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }], + [{ walkable: true, color: 'pink' }, { walkable: false, color: 'grey' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: false, color: 'grey' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }], + [{ walkable: true, color: 'pink' }, { walkable: false, color: 'grey' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }], + [{ walkable: true, color: 'pink' }, { walkable: false, color: 'grey' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: false, color: 'grey' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }], + [{ walkable: true, color: 'pink' }, { walkable: false, color: 'grey' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }], + [{ walkable: true, color: 'pink' }, { walkable: false, color: 'grey' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: false, color: 'grey' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }], + [{ walkable: true, color: 'pink' }, { walkable: false, color: 'grey' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }, { walkable: true, color: 'pink' }] ]; @@ -83,17 +108,66 @@ let items = [ new Item('big key', 216, 216, 'key', 'cyan'), ]; +// Populate the world with really very weird objects +// FIXME: update this so that items aren't always placed within the 0x0 space of a tile +const appendRandomItems = () => { + const getRandomColor = () => '#' + Math.floor(Math.random() * 16777215).toString(16); + const getRandomName = () => speak(Math.floor(Math.random() * 3) + 1); + const getRandomCoordinates = () => { + let x, y; + do { + x = Math.floor(Math.random() * (map[0].length - 2)) + 1; + y = Math.floor(Math.random() * (map.length - 2)) + 1; + } while (!map[y][x].walkable); + return { x, y }; + }; + const createRandomItem = () => { + const { x, y } = getRandomCoordinates(); + const name = getRandomName(); + const color = getRandomColor(); + return new Item(name, x * TILE_SIZE, y * TILE_SIZE, 'mystery', color); + }; + const numberOfItems = Math.floor(Math.random() * 39); + const randomItems = Array.from({ length: numberOfItems }, createRandomItem); + items.push(...randomItems); +}; + + + + + + +// Define the Sign class +function Sign(x, y, width, height, message) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + this.message = message; +} + +// Create some signs +let signs = [ + new Sign(34, 38, player.width, player.height, speak(3).toUpperCase()), + new Sign(28, 64, player.width, player.height, 'Welcome to the game!'), + new Sign(128, 128, player.width, player.height, 'You cannot pass through walls!'), + new Sign(24, 212, player.width, player.height, 'You can collect items!'), + new Sign(428, 712, player.width, player.height, 'This sign has very long text that will wrap around to the next line!'), + new Sign(28, 712, player.width, player.height, 'This sign is very far away.'), +]; -// Funtion to display the player's stats, useful for debugging +// Display the player's stats, useful for debugging function displayStats() { document.getElementById("stats").innerHTML = ""; for (let stat in player) { if (typeof player[stat] === "string" || typeof player[stat] === "number") { - document.getElementById("stats").innerHTML += stat + ": " + player[stat] + "
"; + if (player[stat] !== player.spriteUrl) { + document.getElementById("stats").innerHTML += stat + ": " + player[stat] + "
"; + } } } document.getElementById("stats").innerHTML += "Inventory: "; @@ -110,44 +184,95 @@ function displayStats() { // Game loop function gameLoop() { - // Clear the canvas - ctx.clearRect(0, 0, canvas.width, canvas.height); - - // Draw the map - for (let y = 0; y < map.length; y++) { - for (let x = 0; x < map[y].length; x++) { - ctx.fillStyle = map[y][x].color; - // Draw the tile - ctx.fillRect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE); - // Draw the grid over the tiles - ctx.strokeStyle = 'white'; - ctx.lineWidth = 1; - ctx.strokeRect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE); - } - } + // Update the camera position to follow the player + camera.x = player.x - camera.width / 2; + camera.y = player.y - camera.height / 2; - // Draw the player - ctx.fillStyle = player.color; - ctx.fillRect(player.x, player.y, player.width, player.height); - ctx.fillRect(player.x, player.y, player.width, player.height); - // context.drawImage(playerSprite, player.x, player.y, player.width, player.height); - - // Draw items - items.forEach(item => { + // Clamp the camera position to the map boundaries + camera.x = Math.max(0, Math.min(map[0].length * TILE_SIZE - camera.width, camera.x)); + camera.y = Math.max(0, Math.min(map.length * TILE_SIZE - camera.height, camera.y)); + + // Draw the map + for (let y = 0; y < map.length; y++) { + for (let x = 0; x < map[y].length; x++) { + ctx.fillStyle = map[y][x].color; + + // Draw the tile + ctx.fillRect(x * TILE_SIZE - camera.x, y * TILE_SIZE - camera.y, TILE_SIZE, TILE_SIZE); + + // Draw the grid + ctx.strokeStyle = 'white'; + ctx.lineWidth = 1; + ctx.strokeRect(x * TILE_SIZE - camera.x, y * TILE_SIZE - camera.y, TILE_SIZE, TILE_SIZE); + } + } + + // Draw the player + ctx.fillStyle = player.color; + ctx.fillRect(player.x - camera.x, player.y - camera.y, player.width, player.height); + + // Draw items and check for collisions + items.forEach(item => { ctx.fillStyle = item.color; - ctx.fillRect(item.x, item.y, 8, 8); - }); - for (let i = 0; i < items.length; i++) { + ctx.fillRect(item.x - camera.x, item.y - camera.y, 8, 8); + }); + for (let i = 0; i < items.length; i++) { if (player.x === items[i].x && player.y === items[i].y) { player.collectItem(items[i]); items.splice(i, 1); // Remove the item from the map i--; } + } + + // Draw signs and check for collisions + signs.forEach(sign => { + let dx = player.x - sign.x; + let dy = player.y - sign.y; + let distance = Math.sqrt(dx * dx + dy * dy); + + if (distance < player.width / 2 + sign.width / 2) { + + document.getElementById("dialog").innerHTML = sign.message; + + ctx.fillStyle = 'black'; + ctx.font = '22px Serif'; + + // Wrap text within the canvas width + // FIXME: this works, but is too agressive -- it'll wrap text that *was* at the canvas' edge, but that isn't any more + const maxWidth = canvas.width - sign.x - camera.x; + let words = sign.message.split(' '); + let line = ''; + let lines = []; + for (let i = 0; i < words.length; i++) { + let testLine = line + words[i] + ' '; + let metrics = ctx.measureText(testLine); + let testWidth = metrics.width; + if (testWidth > maxWidth && i > 0) { + lines.push(line); + line = words[i] + ' '; + } else { + line = testLine; + } + } + lines.push(line); + + // Draw wrapped text + let lineHeight = 24; + let startY = sign.y - camera.y - (lines.length * lineHeight) / 2; + for (let i = 0; i < lines.length; i++) { + ctx.fillText(lines[i], sign.x - camera.x, startY + (i * lineHeight)); + } } - // Call the game loop again next frame - requestAnimationFrame(gameLoop); - displayStats(); + ctx.fillStyle = 'brown'; + ctx.beginPath(); + ctx.arc(sign.x - camera.x + sign.width / 2, sign.y - camera.y + sign.height / 2, sign.width / 2, 0, Math.PI * 2); + ctx.fill(); + }); + + // Call the game loop again next frame + requestAnimationFrame(gameLoop); + displayStats(); } diff --git a/js/game-frame/index.html b/js/game-frame/index.html index a18d1d2..8a53a06 100644 --- a/js/game-frame/index.html +++ b/js/game-frame/index.html @@ -8,12 +8,13 @@
+

+ +

-

-
-- cgit 1.4.1-2-gfad0