about summary refs log tree commit diff stats
path: root/js
diff options
context:
space:
mode:
authorelioat <elioat@tilde.institute>2023-12-29 15:12:41 -0500
committerelioat <elioat@tilde.institute>2023-12-29 15:12:41 -0500
commit1d8f185880be10636e731bbc6b215601da0b169a (patch)
tree8f54816c317830637561df8127380526708a49f1 /js
parentf4bd748754c39b80d27a08fbf34d9a8e6988fcbe (diff)
downloadtour-1d8f185880be10636e731bbc6b215601da0b169a.tar.gz
*
Diffstat (limited to 'js')
-rw-r--r--js/game-frame/game.js221
-rw-r--r--js/game-frame/index.html5
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] + "<br>";
+      if (player[stat] !== player.spriteUrl) {
+        document.getElementById("stats").innerHTML += stat + ": " + player[stat] + "<br>";
+      }
     }
   }
   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 @@
 </head>
 <body>
     <main>
+        <p id="stats"></p>
+        <canvas id="gameCanvas"></canvas>
+        <p id="dialog"></p>
         <ul>
             <li id="coordinates"></li>
             <li id="canvasCoordinates"></li>
         </ul>
-        <p id="stats"></p>
-        <canvas id="gameCanvas"></canvas>
     </main>
     <script src="game.js"></script>
 </body>