diff options
Diffstat (limited to 'html')
-rw-r--r-- | html/broughlike/broughlike.js | 146 | ||||
-rw-r--r-- | html/broughlike/config.js | 13 | ||||
-rw-r--r-- | html/story-teller/index.html | 13 | ||||
-rw-r--r-- | html/story-teller/js/game.js | 47 | ||||
-rw-r--r-- | html/story-teller/js/renderer.js | 44 | ||||
-rw-r--r-- | html/story-teller/js/scenes.js | 37 | ||||
-rw-r--r-- | html/story-teller/js/state.js | 43 | ||||
-rw-r--r-- | html/story-teller/styles.css | 8 |
8 files changed, 273 insertions, 78 deletions
diff --git a/html/broughlike/broughlike.js b/html/broughlike/broughlike.js index 585ae66..87718f3 100644 --- a/html/broughlike/broughlike.js +++ b/html/broughlike/broughlike.js @@ -1,14 +1,4 @@ -import { CONFIG, COLORS } from './config.js'; - -// FIXME: canvas, ctx, and tileSize are all regularly accessed. -// I'd like to refactor this to be more modular so that these can all be contained to the CONFIG object or something similar. -let { ctx, canvas, tileSize } = initializeCanvasContext(); -function initializeCanvasContext() { - const canvas = document.getElementById('gameCanvas'); - const ctx = canvas.getContext('2d'); - let tileSize = canvas.width / CONFIG.GRID_SIZE; - return { ctx, canvas, tileSize }; -} +import { CONFIG, COLORS, CANVAS } from './config.js'; const DEBUG = false; @@ -331,59 +321,59 @@ function isReachable(startX, startY, targetX, targetY) { } function drawGrid() { - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.lineWidth = 2; - ctx.strokeStyle = COLORS.grid; + CANVAS.ctx.clearRect(0, 0, CANVAS.canvas.width, CANVAS.canvas.height); + CANVAS.ctx.lineWidth = 2; + CANVAS.ctx.strokeStyle = COLORS.grid; for (let row = 0; row < CONFIG.GRID_SIZE; row++) { for (let col = 0; col < CONFIG.GRID_SIZE; col++) { - ctx.strokeRect(col * tileSize, row * tileSize, tileSize, tileSize); + CANVAS.ctx.strokeRect(col * CANVAS.tileSize, row * CANVAS.tileSize, CANVAS.tileSize, CANVAS.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(); + const x = exit.x * CANVAS.tileSize + CANVAS.tileSize / 2; + const y = exit.y * CANVAS.tileSize + CANVAS.tileSize / 2; + CANVAS.ctx.beginPath(); + CANVAS.ctx.moveTo(x, y - CANVAS.tileSize / 3); + CANVAS.ctx.lineTo(x + CANVAS.tileSize / 3, y + CANVAS.tileSize / 3); + CANVAS.ctx.lineTo(x - CANVAS.tileSize / 3, y + CANVAS.tileSize / 3); + CANVAS.ctx.closePath(); + CANVAS.ctx.fillStyle = COLORS.exit; + CANVAS.ctx.fill(); } function drawWalls() { - ctx.fillStyle = COLORS.walls; + CANVAS.ctx.fillStyle = COLORS.walls; walls.forEach(wall => { - ctx.fillRect(wall.x * tileSize, wall.y * tileSize, tileSize, tileSize); + CANVAS.ctx.fillRect(wall.x * CANVAS.tileSize, wall.y * CANVAS.tileSize, CANVAS.tileSize, CANVAS.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(); + const x = item.x * CANVAS.tileSize + CANVAS.tileSize / 2; + const y = item.y * CANVAS.tileSize + CANVAS.tileSize / 2; + CANVAS.ctx.fillStyle = item.type === 'diamond' ? COLORS.diamond : COLORS.pentagon; + CANVAS.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); + CANVAS.ctx.moveTo(x, y - CANVAS.tileSize / 4); + CANVAS.ctx.lineTo(x + CANVAS.tileSize / 4, y); + CANVAS.ctx.lineTo(x, y + CANVAS.tileSize / 4); + CANVAS.ctx.lineTo(x - CANVAS.tileSize / 4, y); } else { const sides = 5; - const radius = tileSize / 4; + const radius = CANVAS.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); + if (i === 0) CANVAS.ctx.moveTo(pointX, pointY); + else CANVAS.ctx.lineTo(pointX, pointY); } } - ctx.closePath(); - ctx.fill(); + CANVAS.ctx.closePath(); + CANVAS.ctx.fill(); }); } @@ -391,54 +381,54 @@ function drawCharacterBorder(x, y, radius, damageTaken) { const dashLength = 5; const gapLength = Math.max(1, damageTaken * 2); // More damage, larger gaps - ctx.lineWidth = 2; - ctx.strokeStyle = '#2d2d2d'; - ctx.setLineDash([dashLength, gapLength]); - ctx.beginPath(); - ctx.arc(x, y, radius, 0, 2 * Math.PI); - ctx.stroke(); - ctx.setLineDash([]); // Reset to a solid line + CANVAS.ctx.lineWidth = 2; + CANVAS.ctx.strokeStyle = '#2d2d2d'; + CANVAS.ctx.setLineDash([dashLength, gapLength]); + CANVAS.ctx.beginPath(); + CANVAS.ctx.arc(x, y, radius, 0, 2 * Math.PI); + CANVAS.ctx.stroke(); + CANVAS.ctx.setLineDash([]); // Reset to a solid line } function drawEnemies() { enemies.forEach(enemy => { - const x = enemy.x * tileSize + tileSize / 2; - const y = enemy.y * tileSize + tileSize / 2; + const x = enemy.x * CANVAS.tileSize + CANVAS.tileSize / 2; + const y = enemy.y * CANVAS.tileSize + CANVAS.tileSize / 2; const opacity = enemy.health / CONFIG.MAX_ENEMY_HEALTH; // Opacity based on health - const radius = tileSize / 3; + const radius = CANVAS.tileSize / 3; const damageTaken = CONFIG.MAX_ENEMY_HEALTH - enemy.health; - ctx.beginPath(); - ctx.arc(x, y, radius, 0, 2 * Math.PI); - ctx.fillStyle = `${enemy.color}${opacity})`; - ctx.fill(); + CANVAS.ctx.beginPath(); + CANVAS.ctx.arc(x, y, radius, 0, 2 * Math.PI); + CANVAS.ctx.fillStyle = `${enemy.color}${opacity})`; + CANVAS.ctx.fill(); drawCharacterBorder(x, y, radius, damageTaken); }); } function drawPlayer() { - const x = player.x * tileSize + tileSize / 2; - const y = player.y * tileSize + tileSize / 2; - const radius = tileSize / 3; + const x = player.x * CANVAS.tileSize + CANVAS.tileSize / 2; + const y = player.y * CANVAS.tileSize + CANVAS.tileSize / 2; + const radius = CANVAS.tileSize / 3; const playerOpacity = player.health / CONFIG.PLAYER_HEALTH; // Opacity based on health - ctx.beginPath(); + CANVAS.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); + CANVAS.ctx.moveTo(hexX, hexY); } else { - ctx.lineTo(hexX, hexY); + CANVAS.ctx.lineTo(hexX, hexY); } } - ctx.closePath(); - ctx.fillStyle = `${COLORS.player}${playerOpacity})`; - ctx.fill(); - ctx.lineWidth = 2; - ctx.strokeStyle = '#2d2d2d'; - ctx.stroke(); + CANVAS.ctx.closePath(); + CANVAS.ctx.fillStyle = `${COLORS.player}${playerOpacity})`; + CANVAS.ctx.fill(); + CANVAS.ctx.lineWidth = 2; + CANVAS.ctx.strokeStyle = '#2d2d2d'; + CANVAS.ctx.stroke(); } function drawCombatDots() { @@ -446,11 +436,11 @@ function drawCombatDots() { 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(); + CANVAS.ctx.beginPath(); + CANVAS.ctx.arc(cellX * CANVAS.tileSize + dot.x, cellY * CANVAS.tileSize + dot.y, 2, 0, Math.PI * 2); + CANVAS.ctx.fillStyle = dot.color; + CANVAS.ctx.fill(); + CANVAS.ctx.closePath(); }); } } @@ -492,8 +482,8 @@ function addCombatDots(x, y, color) { } for (let i = 0; i < CONFIG.DOTS_PER_HIT; i++) { combatDots[key].push({ - x: Math.random() * tileSize, - y: Math.random() * tileSize, + x: Math.random() * CANVAS.tileSize, + y: Math.random() * CANVAS.tileSize, color: color }); } @@ -749,13 +739,13 @@ document.addEventListener('keydown', (e) => { let touchStartX = 0; let touchStartY = 0; -canvas.addEventListener('touchstart', (e) => { +CANVAS.canvas.addEventListener('touchstart', (e) => { e.preventDefault(); // Prevent scrolling on touchstart touchStartX = e.touches[0].clientX; touchStartY = e.touches[0].clientY; }); -canvas.addEventListener('touchend', (e) => { +CANVAS.canvas.addEventListener('touchend', (e) => { e.preventDefault(); // Prevent scrolling on touchend const touchEndX = e.changedTouches[0].clientX; const touchEndY = e.changedTouches[0].clientY; @@ -782,10 +772,10 @@ canvas.addEventListener('touchend', (e) => { }, { 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 / CONFIG.GRID_SIZE; // Update tile size based on canvas dimensions + const rect = CANVAS.canvas.getBoundingClientRect(); + CANVAS.canvas.width = rect.width; + CANVAS.canvas.height = rect.height; + CANVAS.tileSize = CANVAS.updateTileSize(); // Update tile size based on canvas dimensions render(); }; diff --git a/html/broughlike/config.js b/html/broughlike/config.js index bd95035..8776b2f 100644 --- a/html/broughlike/config.js +++ b/html/broughlike/config.js @@ -30,3 +30,16 @@ export const CONFIG = { ITEMS_MAX: 3, DOTS_PER_HIT: 7 }; + +const canvas = document.getElementById('gameCanvas'); +const ctx = canvas.getContext('2d'); +const tileSize = canvas.width / CONFIG.GRID_SIZE; + +export const CANVAS = { + ctx, + canvas, + tileSize, + updateTileSize() { + return canvas.width / CONFIG.GRID_SIZE; + } +}; diff --git a/html/story-teller/index.html b/html/story-teller/index.html new file mode 100644 index 0000000..edd44c2 --- /dev/null +++ b/html/story-teller/index.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Choice-based Game</title> + <link rel="stylesheet" href="./styles.css"> +</head> +<body> + <div id="app"></div> + <script type="module" src="./js/game.js"></script> +</body> +</html> diff --git a/html/story-teller/js/game.js b/html/story-teller/js/game.js new file mode 100644 index 0000000..d3e8144 --- /dev/null +++ b/html/story-teller/js/game.js @@ -0,0 +1,47 @@ +import { createGameState, saveGameState, loadGameState, resetGameState } from './state.js'; +import { renderScene } from './renderer.js'; + +let gameState = loadGameState(); // Load state on initialization + +const updateAndRender = (newState) => { + Object.assign(gameState, newState); + saveGameState(gameState); // Save automatically after each update + renderScene(gameState, updateAndRender); +}; + +document.addEventListener('DOMContentLoaded', () => { + const app = document.getElementById('app'); + + app.innerHTML = ''; + + const controls = document.createElement('div'); + controls.id = 'controls'; + + const saveButton = document.createElement('button'); + saveButton.textContent = 'Save Progress'; + saveButton.onclick = () => saveGameState(gameState); + + const loadButton = document.createElement('button'); + loadButton.textContent = 'Load Progress'; + loadButton.onclick = () => { + const loadedState = loadGameState(); + updateAndRender(loadedState); + }; + + const resetButton = document.createElement('button'); + resetButton.textContent = 'Reset Game'; + resetButton.onclick = () => { + if (confirm('Are you sure you want to reset the game? This will erase all progress.')) { + gameState = resetGameState(); // Clear state and reset game + updateAndRender(gameState); + } + }; + + controls.appendChild(saveButton); + controls.appendChild(loadButton); + controls.appendChild(resetButton); + + app.appendChild(controls); + + updateAndRender(gameState); +}); diff --git a/html/story-teller/js/renderer.js b/html/story-teller/js/renderer.js new file mode 100644 index 0000000..82ab004 --- /dev/null +++ b/html/story-teller/js/renderer.js @@ -0,0 +1,44 @@ +import { scenes } from './scenes.js'; + +export const renderScene = (state, updateState) => { + const scene = scenes[state.currentScene]; + if (!scene) throw new Error(`Scene "${state.currentScene}" not found`); + + const app = document.getElementById('app'); + app.innerHTML = ''; + + const description = document.createElement('p'); + description.textContent = scene.description; + app.appendChild(description); + + const inventory = document.createElement('div'); + inventory.textContent = `Inventory: ${Array.from(state.inventory).join(', ') || 'None'}`; + app.appendChild(inventory); + + const log = document.createElement('div'); + log.innerHTML = `<strong>Action Log:</strong><br>${state.actionLog.join('<br>')}`; + app.appendChild(log); + + scene.choices.forEach((choice) => { + if (choice.condition && !choice.condition(state)) { + return; // Skip choices that don't meet their conditions + } + + const button = document.createElement('button'); + button.textContent = choice.text; + button.onclick = () => { + state.actionLog.push(`You chose: "${choice.text}"`); + if (choice.action) choice.action(state); // Perform any action tied to the choice + + // Consume items if specified + if (choice.consume) { + choice.consume.forEach((item) => state.inventory.delete(item)); + state.actionLog.push(`You used: ${choice.consume.join(', ')}`); + } + + if (choice.nextScene) state.currentScene = choice.nextScene; + updateState(state); // Re-render after state update + }; + app.appendChild(button); + }); +}; diff --git a/html/story-teller/js/scenes.js b/html/story-teller/js/scenes.js new file mode 100644 index 0000000..7b8db35 --- /dev/null +++ b/html/story-teller/js/scenes.js @@ -0,0 +1,37 @@ +export const scenes = { + start: { + description: "You are standing in a dark cave. There's a faint glimmer in the distance, and a torch on the ground.", + choices: [ + { text: "Explore further", nextScene: "deepCave" }, + { text: "Pick up the torch", action: (state) => state.inventory.add("torch") }, + { + text: "Use a torch to illuminate the cave", + nextScene: "litCave", + condition: (state) => state.inventory.has("torch"), + consume: ["torch"] + }, + ], + }, + deepCave: { + description: "The darkness becomes overwhelming. You can't proceed further.", + choices: [ + { text: "Go back", nextScene: "start" }, + ], + }, + litCave: { + description: "With the torchlight, you see glittering gemstones embedded in the walls.", + choices: [ + { text: "Pick up the key", action: (state) => state.inventory.add("key") }, + { text: "Collect a gemstone", action: (state) => state.inventory.add("gemstone") }, + { text: "Go back", nextScene: "start" }, + { text: "Go deeper into the cave", nextScene: "treasureRoom", condition: (state) => state.inventory.has("key") }, + ], + }, + treasureRoom: { + description: "You find a room filled with treasure. You are rich beyond your wildest dreams.", + choices: [ + { text: "Swim in the treasure", nextScene: "treasureRoom" }, + ], + }, + }; + \ No newline at end of file diff --git a/html/story-teller/js/state.js b/html/story-teller/js/state.js new file mode 100644 index 0000000..e6c4fe7 --- /dev/null +++ b/html/story-teller/js/state.js @@ -0,0 +1,43 @@ +export const createGameState = () => ({ + currentScene: 'start', + inventory: new Set(), + actionLog: [], + collectedItems: new Set(), + }); + + + +export const updateGameState = (state, updates) => ({ + ...state, + ...updates, +}); + +const STORAGE_KEY = 'gameState'; + +export const saveGameState = (state) => { + const stateToSave = { + currentScene: state.currentScene, + inventory: Array.from(state.inventory), + actionLog: state.actionLog, + }; + localStorage.setItem(STORAGE_KEY, JSON.stringify(stateToSave)); +}; + +export const loadGameState = () => { + const savedState = localStorage.getItem(STORAGE_KEY); + if (savedState) { + const parsedState = JSON.parse(savedState); + return { + currentScene: parsedState.currentScene, + inventory: new Set(parsedState.inventory), + actionLog: parsedState.actionLog, + }; + } + return createGameState(); +}; + +export const resetGameState = () => { + localStorage.removeItem(STORAGE_KEY); + return createGameState(); + }; + \ No newline at end of file diff --git a/html/story-teller/styles.css b/html/story-teller/styles.css new file mode 100644 index 0000000..51852ba --- /dev/null +++ b/html/story-teller/styles.css @@ -0,0 +1,8 @@ +body { + font-family: 'Roboto', sans-serif; + background-color: #f0f0f0; + margin: 0 auto; + display: block; + padding: 0; + max-width: 42em; +} \ No newline at end of file |