3 changed files with 386 additions and 303 deletions
@ -0,0 +1,198 @@
@@ -0,0 +1,198 @@
|
||||
/** |
||||
* @fileoverview Low-level canvas utilities and sprite system. |
||||
* Provides drawing primitives and sprite management. |
||||
*/ |
||||
|
||||
/** @type {HTMLCanvasElement} */ |
||||
export const canvas = document.getElementById('scene'); |
||||
|
||||
/** @type {CanvasRenderingContext2D} */ |
||||
export const ctx = canvas.getContext('2d'); |
||||
|
||||
/** |
||||
* @typedef {Object} SpriteOptions |
||||
* @property {string} source - Path to the sprite image file |
||||
* @property {number} [frameWidth] - Width of each frame for animated sprites |
||||
* @property {number} [frameHeight] - Height of each frame for animated sprites |
||||
* @property {number} [frameCount=1] - Number of frames in the sprite sheet |
||||
* @property {number} [frameIndex] - Current frame index for animation |
||||
*/ |
||||
|
||||
/** |
||||
* @typedef {Object} Sprite |
||||
* @property {string} source - Path to the sprite image file |
||||
* @property {HTMLImageElement} image - The loaded image element |
||||
* @property {boolean} ready - Whether the image has finished loading |
||||
* @property {number|null} frameWidth - Width of each frame (null for single-frame sprites) |
||||
* @property {number|null} frameHeight - Height of each frame (null for single-frame sprites) |
||||
* @property {number} frameCount - Total number of frames in the sprite |
||||
* @property {number} [frameIndex] - Current frame index for animation |
||||
*/ |
||||
|
||||
/** |
||||
* @typedef {Object} Cup |
||||
* @property {Sprite} sprite - Reference to the cup sprite template |
||||
* @property {number} x - X position on canvas |
||||
* @property {number} y - Y position on canvas |
||||
* @property {number} scale - Render scale |
||||
* @property {number} frameIndex - Current frame (0 = empty, 1 = filled) |
||||
* @property {boolean} filled - Whether the cup is filled |
||||
* @property {Function} fill - Fills the cup |
||||
* @property {Function} empty - Empties the cup |
||||
* @property {Function} isFilled - Returns whether cup is filled |
||||
*/ |
||||
|
||||
/** |
||||
* Creates a new sprite object from the given options. |
||||
* |
||||
* @param {SpriteOptions} options - Configuration options for the sprite |
||||
* @param {Function} [onLoad] - Optional callback when sprite loads |
||||
* @returns {Sprite} The created sprite object |
||||
*/ |
||||
export function createSprite(options, onLoad) { |
||||
const image = new Image(); |
||||
|
||||
const sprite = { |
||||
source: options.source, |
||||
image, |
||||
ready: false, |
||||
|
||||
// optionals
|
||||
frameWidth: options.frameWidth || null, |
||||
frameHeight: options.frameHeight || null, |
||||
frameCount: options.frameCount ?? 1, |
||||
frameIndex: options.frameIndex || 0 |
||||
}; |
||||
|
||||
image.onload = () => { |
||||
sprite.ready = true; |
||||
if (onLoad) onLoad(); |
||||
}; |
||||
|
||||
image.src = options.source; |
||||
return sprite; |
||||
} |
||||
|
||||
/** |
||||
* Draws a sprite onto the canvas at the specified position. |
||||
* |
||||
* @param {Sprite} spriteObject - The sprite to draw |
||||
* @param {number} x - The x-coordinate on the canvas |
||||
* @param {number} y - The y-coordinate on the canvas |
||||
* @param {number} scale - Scale factor for rendering |
||||
* @returns {void} |
||||
*/ |
||||
export function drawSprite(spriteObject, x, y, scale) { |
||||
if (!spriteObject.ready) return; |
||||
|
||||
const frameIndex = spriteObject.frameIndex; |
||||
|
||||
const isFramedSprite = spriteObject.frameWidth !== null |
||||
|| spriteObject.frameHeight !== null; |
||||
|
||||
if (!isFramedSprite) { |
||||
const drawWidth = spriteObject.image.naturalWidth * scale; |
||||
const drawHeight = spriteObject.image.naturalHeight * scale; |
||||
|
||||
ctx.drawImage(spriteObject.image, x, y, drawWidth, drawHeight); |
||||
return; |
||||
} |
||||
|
||||
// framed horizontal sprite
|
||||
const sourceX = frameIndex * spriteObject.frameWidth; |
||||
const sourceY = 0; |
||||
|
||||
const drawWidth = spriteObject.frameWidth * scale; |
||||
const drawHeight = spriteObject.frameHeight * scale; |
||||
|
||||
ctx.drawImage( |
||||
spriteObject.image, |
||||
sourceX, |
||||
sourceY, |
||||
spriteObject.frameWidth, |
||||
spriteObject.frameHeight, |
||||
x, |
||||
y, |
||||
drawWidth, |
||||
drawHeight |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Draws a cup (or any object with sprite, x, y, scale, frameIndex). |
||||
* |
||||
* @param {Cup} instance - The cup to draw |
||||
* @returns {void} |
||||
*/ |
||||
export function drawInstance(instance) { |
||||
const { sprite, x, y, scale, frameIndex } = instance; |
||||
|
||||
if (!sprite.ready) return; |
||||
|
||||
const isFramedSprite = sprite.frameWidth !== null |
||||
|| sprite.frameHeight !== null; |
||||
|
||||
if (!isFramedSprite) { |
||||
const drawWidth = sprite.image.naturalWidth * scale; |
||||
const drawHeight = sprite.image.naturalHeight * scale; |
||||
ctx.drawImage(sprite.image, x, y, drawWidth, drawHeight); |
||||
return; |
||||
} |
||||
|
||||
// framed horizontal sprite
|
||||
const sourceX = frameIndex * sprite.frameWidth; |
||||
const drawWidth = sprite.frameWidth * scale; |
||||
const drawHeight = sprite.frameHeight * scale; |
||||
|
||||
ctx.drawImage( |
||||
sprite.image, |
||||
sourceX, |
||||
0, |
||||
sprite.frameWidth, |
||||
sprite.frameHeight, |
||||
x, |
||||
y, |
||||
drawWidth, |
||||
drawHeight |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Draws the ground using a grass sprite as a repeating pattern. |
||||
* |
||||
* @param {Sprite} grassSprite - The grass sprite to use as pattern |
||||
* @param {number} [groundHeight=250] - Height of the ground area |
||||
* @returns {void} |
||||
*/ |
||||
export function drawGround(grassSprite, groundHeight = 250) { |
||||
if (!grassSprite.ready) return; |
||||
|
||||
const pattern = ctx.createPattern(grassSprite.image, 'repeat'); |
||||
ctx.fillStyle = pattern; |
||||
|
||||
ctx.fillRect(0, canvas.height - groundHeight, canvas.width, groundHeight); |
||||
} |
||||
|
||||
/** |
||||
* Draws an oval shadow. |
||||
* |
||||
* @param {number} x - Center x position |
||||
* @param {number} y - Center y position |
||||
* @param {number} radiusX - Horizontal radius |
||||
* @param {number} radiusY - Vertical radius |
||||
* @param {string} [color='rgba(0, 0, 0, 0.3)'] - Shadow color |
||||
*/ |
||||
export function drawShadow(x, y, radiusX, radiusY, color = 'rgba(0, 0, 0, 0.3)') { |
||||
ctx.beginPath(); |
||||
ctx.ellipse(x, y, radiusX, radiusY, 0, 0, Math.PI * 2); |
||||
ctx.fillStyle = color; |
||||
ctx.fill(); |
||||
} |
||||
|
||||
/** |
||||
* Clears the entire canvas. |
||||
*/ |
||||
export function clearCanvas() { |
||||
ctx.clearRect(0, 0, canvas.width, canvas.height); |
||||
} |
||||
|
||||
@ -0,0 +1,161 @@
@@ -0,0 +1,161 @@
|
||||
/** |
||||
* @fileoverview Canvas controller for scene management. |
||||
* Handles sprites, scene objects, rendering, and game loop. |
||||
*/ |
||||
|
||||
import { |
||||
canvas, |
||||
ctx, |
||||
createSprite, |
||||
drawSprite, |
||||
drawInstance, |
||||
drawGround, |
||||
drawShadow, |
||||
clearCanvas |
||||
} from './canvas.js'; |
||||
|
||||
/** @type {Function|null} Callback to invoke on render */ |
||||
let onRenderCallback = null; |
||||
|
||||
/** |
||||
* Collection of sprite templates (shared image/frame data) |
||||
* @type {Object.<string, import('./canvas.js').Sprite>} |
||||
*/ |
||||
export const sprites = { |
||||
stand: createSprite({ source: 'stand.png' }), |
||||
cup: createSprite({ |
||||
source: 'cups.png', |
||||
frameWidth: 251, |
||||
frameHeight: 330, |
||||
frameCount: 2 |
||||
}), |
||||
maker: createSprite({ |
||||
source: 'maker.png', |
||||
frameWidth: 562 / 2, |
||||
frameHeight: 432, |
||||
frameCount: 2 |
||||
}), |
||||
ice: createSprite({ |
||||
source: 'ice.png' |
||||
}), |
||||
pitcher: createSprite({ |
||||
source: 'pitcher_full.png' |
||||
}), |
||||
lemons: createSprite({ |
||||
source: 'lemons.png' |
||||
}), |
||||
grass: createSprite({ |
||||
source: 'grass.png' |
||||
}), |
||||
tree: createSprite({ |
||||
source: 'tree.png' |
||||
}), |
||||
slide: createSprite({ |
||||
source: 'slide.png' |
||||
}), |
||||
seesaw: createSprite({ |
||||
source: 'seesaw.png' |
||||
}) |
||||
}; |
||||
|
||||
/** |
||||
* Checks if all sprites are loaded. |
||||
* @returns {boolean} |
||||
*/ |
||||
export function allSpritesReady() { |
||||
return Object.values(sprites).every(sprite => sprite.ready); |
||||
} |
||||
|
||||
/** |
||||
* Waits for all sprites to load, then calls the callback. |
||||
* @param {Function} callback - Function to call when all sprites are ready |
||||
*/ |
||||
export function whenSpritesReady(callback) { |
||||
function check() { |
||||
if (allSpritesReady()) { |
||||
callback(); |
||||
} else { |
||||
requestAnimationFrame(check); |
||||
} |
||||
} |
||||
check(); |
||||
} |
||||
|
||||
/** |
||||
* Creates a cup instance. |
||||
* |
||||
* @param {number} x - X position |
||||
* @param {number} y - Y position |
||||
* @param {number} [scale=0.1] - Render scale |
||||
* @returns {import('./canvas.js').Cup} |
||||
*/ |
||||
export function createCup(x, y, scale = 0.1) { |
||||
return { |
||||
sprite: sprites.cup, |
||||
x, |
||||
y, |
||||
scale, |
||||
frameIndex: 0, |
||||
filled: false, |
||||
|
||||
/** Fills the cup */ |
||||
fill() { |
||||
this.filled = true; |
||||
this.frameIndex = 1; |
||||
}, |
||||
|
||||
/** Empties the cup */ |
||||
empty() { |
||||
this.filled = false; |
||||
this.frameIndex = 0; |
||||
}, |
||||
|
||||
/** @returns {boolean} Whether the cup is filled */ |
||||
isFilled() { |
||||
return this.filled; |
||||
} |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* Game objects - cups on the stand |
||||
* @type {Array<import('./canvas.js').Cup>} |
||||
*/ |
||||
export const cups = [ |
||||
createCup(455, 325, 0.15), |
||||
createCup(495, 325, 0.15), |
||||
createCup(535, 325, 0.15), |
||||
createCup(575, 325, 0.15), |
||||
]; |
||||
|
||||
/** |
||||
* Renders the entire scene. |
||||
*/ |
||||
export function render() { |
||||
clearCanvas(); |
||||
|
||||
// Background
|
||||
drawGround(sprites.grass); |
||||
|
||||
// Background trees
|
||||
drawSprite(sprites.tree, 600, 50, 0.25); |
||||
drawSprite(sprites.tree, 200, 50, 0.2); |
||||
|
||||
// Playground equipment
|
||||
drawSprite(sprites.slide, 880, 80, 0.4); |
||||
drawSprite(sprites.seesaw, 200, 280, 0.30); |
||||
|
||||
// Shadow under the stand
|
||||
drawShadow(500, 360, 180, 50); |
||||
|
||||
// Lemonade stand
|
||||
drawSprite(sprites.maker, 430, 160, 0.6); |
||||
drawSprite(sprites.stand, 350, 50, 0.7); |
||||
drawSprite(sprites.pitcher, 620, 290, 0.3); |
||||
|
||||
// Draw all cup instances
|
||||
cups.forEach(cup => drawInstance(cup)); |
||||
|
||||
// Invoke callback if set
|
||||
if (onRenderCallback) onRenderCallback(); |
||||
} |
||||
@ -1,314 +1,38 @@
@@ -1,314 +1,38 @@
|
||||
/** |
||||
* @fileoverview Lemonade stand game rendering module. |
||||
* Handles sprite loading and canvas rendering. |
||||
* @fileoverview Main application orchestrator. |
||||
* Initializes game state, wires up UI events, and coordinates modules. |
||||
*/ |
||||
|
||||
import { init_game, make_lemonade, set_recipe, set_weather, Weather } from './game.js'; |
||||
|
||||
/** @type {HTMLCanvasElement} */ |
||||
const canvas = document.getElementById('scene'); |
||||
import { init_game } from './game.js'; |
||||
import { sprites, cups, render, whenSpritesReady } from './canvasController.js'; |
||||
|
||||
// Initialize game state
|
||||
let gameState = init_game(); |
||||
|
||||
/** @type {CanvasRenderingContext2D} */ |
||||
const ctx = canvas.getContext('2d'); |
||||
|
||||
/** @type {number} Global scale factor for rendering */ |
||||
const scale = 0.4; |
||||
|
||||
/** |
||||
* @typedef {Object} SpriteOptions |
||||
* @property {string} source - Path to the sprite image file |
||||
* @property {number} [frameWidth] - Width of each frame for animated sprites |
||||
* @property {number} [frameHeight] - Height of each frame for animated sprites |
||||
* @property {number} [frameCount=1] - Number of frames in the sprite sheet |
||||
* @property {number} [frameIndex] - Current frame index for animation |
||||
*/ |
||||
|
||||
/** |
||||
* @typedef {Object} Sprite |
||||
* @property {string} source - Path to the sprite image file |
||||
* @property {HTMLImageElement} image - The loaded image element |
||||
* @property {boolean} ready - Whether the image has finished loading |
||||
* @property {number|null} frameWidth - Width of each frame (null for single-frame sprites) |
||||
* @property {number|null} frameHeight - Height of each frame (null for single-frame sprites) |
||||
* @property {number} frameCount - Total number of frames in the sprite |
||||
* @property {number} [frameIndex] - Current frame index for animation |
||||
*/ |
||||
|
||||
/** |
||||
* @typedef {Object} Cup |
||||
* @property {Sprite} sprite - Reference to the cup sprite template |
||||
* @property {number} x - X position on canvas |
||||
* @property {number} y - Y position on canvas |
||||
* @property {number} scale - Render scale |
||||
* @property {number} frameIndex - Current frame (0 = empty, 1 = filled) |
||||
* @property {boolean} filled - Whether the cup is filled |
||||
* @property {Function} fill - Fills the cup |
||||
* @property {Function} empty - Empties the cup |
||||
* @property {Function} isFilled - Returns whether cup is filled |
||||
*/ |
||||
|
||||
/** |
||||
* Collection of sprite templates (shared image/frame data) |
||||
* @type {Object.<string, Sprite>} |
||||
*/ |
||||
const sprites = { |
||||
stand: createSprite({ source: 'stand.png' }), |
||||
cup: createSprite({ |
||||
source: 'cups.png', |
||||
frameWidth: 251, |
||||
frameHeight: 330, |
||||
frameCount: 2 |
||||
}), |
||||
maker: createSprite({ |
||||
source: 'maker.png', |
||||
frameWidth: 562 / 2, |
||||
frameHeight: 432, |
||||
frameCount: 2 |
||||
}), |
||||
ice: createSprite({ |
||||
source: 'ice.png' |
||||
}), |
||||
pitcher: createSprite({ |
||||
source: 'pitcher_full.png' |
||||
}), |
||||
lemons: createSprite({ |
||||
source: 'lemons.png' |
||||
}), |
||||
grass: createSprite({ |
||||
source: 'grass.png' |
||||
}), |
||||
tree: createSprite({ |
||||
source: 'tree.png' |
||||
}), |
||||
slide: createSprite({ |
||||
source: 'slide.png' |
||||
}), |
||||
seesaw: createSprite({ |
||||
source: 'seesaw.png' |
||||
}) |
||||
}; |
||||
|
||||
/** |
||||
* Creates a cup instance. |
||||
* |
||||
* @param {number} x - X position |
||||
* @param {number} y - Y position |
||||
* @param {number} [scale=0.1] - Render scale |
||||
* @returns {Cup} |
||||
*/ |
||||
function createCup(x, y, scale = 0.1) { |
||||
return { |
||||
sprite: sprites.cup, |
||||
x, |
||||
y, |
||||
scale, |
||||
frameIndex: 0, |
||||
filled: false, |
||||
|
||||
/** Fills the cup */ |
||||
fill() { |
||||
this.filled = true; |
||||
this.frameIndex = 1; |
||||
}, |
||||
|
||||
/** Empties the cup */ |
||||
empty() { |
||||
this.filled = false; |
||||
this.frameIndex = 0; |
||||
}, |
||||
|
||||
/** @returns {boolean} Whether the cup is filled */ |
||||
isFilled() { |
||||
return this.filled; |
||||
} |
||||
}; |
||||
} |
||||
|
||||
// Game objects
|
||||
const cups = [ |
||||
createCup(455, 325, 0.15), |
||||
createCup(495, 325, 0.15), |
||||
createCup(535, 325, 0.15), |
||||
createCup(575, 325, 0.15), |
||||
|
||||
] |
||||
|
||||
/** |
||||
* Creates a new sprite object from the given options. |
||||
* Automatically triggers a render when the image loads. |
||||
* |
||||
* @param {SpriteOptions} options - Configuration options for the sprite |
||||
* @returns {Sprite} The created sprite object |
||||
*/ |
||||
function createSprite(options) { |
||||
const image = new Image(); |
||||
|
||||
const sprite = { |
||||
source: options.source, |
||||
image, |
||||
ready: false, |
||||
|
||||
// optionals
|
||||
frameWidth: options.frameWidth || null, |
||||
frameHeight: options.frameHeight || null, |
||||
frameCount: options.frameCount ?? 1, |
||||
frameIndex: options.frameIndex || 0 |
||||
}; |
||||
|
||||
image.onload = () => { |
||||
sprite.ready = true; |
||||
// Wait for all sprites to load, then render once
|
||||
whenSpritesReady(() => { |
||||
render(); |
||||
} |
||||
|
||||
image.src = options.source; |
||||
return sprite; |
||||
} |
||||
|
||||
/** |
||||
* Draws a sprite onto the canvas at the specified position. |
||||
* |
||||
* @param {Sprite} spriteObject - The sprite to draw |
||||
* @param {number} x - The x-coordinate on the canvas |
||||
* @param {number} y - The y-coordinate on the canvas |
||||
* @param {number} scale - Scale factor for rendering |
||||
* @returns {void} |
||||
*/ |
||||
function drawSprite(spriteObject, x, y, scale) { |
||||
if (!spriteObject.ready) return; |
||||
|
||||
const frameIndex = spriteObject.frameIndex; |
||||
|
||||
const isFramedSprite = spriteObject.frameWidth !== null |
||||
|| spriteObject.frameHeight !== null; |
||||
|
||||
if (!isFramedSprite) { |
||||
const drawWidth = spriteObject.image.naturalWidth * scale; |
||||
const drawHeight = spriteObject.image.naturalHeight * scale; |
||||
|
||||
ctx.drawImage(spriteObject.image, x, y, drawWidth, drawHeight); |
||||
return; |
||||
} |
||||
|
||||
// framed horizontal sprite
|
||||
const sourceX = frameIndex * spriteObject.frameWidth; |
||||
const sourceY = 0; |
||||
|
||||
const drawWidth = spriteObject.frameWidth * scale; |
||||
const drawHeight = spriteObject.frameHeight * scale; |
||||
|
||||
ctx.drawImage( |
||||
spriteObject.image, |
||||
sourceX, |
||||
sourceY, |
||||
spriteObject.frameWidth, |
||||
spriteObject.frameHeight, |
||||
x, |
||||
y, |
||||
drawWidth, |
||||
drawHeight |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Draws a cup (or any object with sprite, x, y, scale, frameIndex). |
||||
* |
||||
* @param {Cup} instance - The cup to draw |
||||
* @returns {void} |
||||
*/ |
||||
function drawInstance(instance) { |
||||
const { sprite, x, y, scale, frameIndex } = instance; |
||||
|
||||
if (!sprite.ready) return; |
||||
|
||||
const isFramedSprite = sprite.frameWidth !== null |
||||
|| sprite.frameHeight !== null; |
||||
|
||||
if (!isFramedSprite) { |
||||
const drawWidth = sprite.image.naturalWidth * scale; |
||||
const drawHeight = sprite.image.naturalHeight * scale; |
||||
ctx.drawImage(sprite.image, x, y, drawWidth, drawHeight); |
||||
return; |
||||
} |
||||
|
||||
// framed horizontal sprite
|
||||
const sourceX = frameIndex * sprite.frameWidth; |
||||
const drawWidth = sprite.frameWidth * scale; |
||||
const drawHeight = sprite.frameHeight * scale; |
||||
|
||||
ctx.drawImage( |
||||
sprite.image, |
||||
sourceX, |
||||
0, |
||||
sprite.frameWidth, |
||||
sprite.frameHeight, |
||||
x, |
||||
y, |
||||
drawWidth, |
||||
drawHeight |
||||
); |
||||
} |
||||
|
||||
function drawGround() { |
||||
if (!sprites.grass.ready) return; |
||||
|
||||
const pattern = ctx.createPattern(sprites.grass.image, 'repeat'); |
||||
ctx.fillStyle = pattern; |
||||
|
||||
const groundHeight = 250; |
||||
ctx.fillRect(0, canvas.height - groundHeight, canvas.width, groundHeight); |
||||
} |
||||
|
||||
/** |
||||
* Draws an oval shadow. |
||||
* |
||||
* @param {number} x - Center x position |
||||
* @param {number} y - Center y position |
||||
* @param {number} radiusX - Horizontal radius |
||||
* @param {number} radiusY - Vertical radius |
||||
* @param {string} [color='rgba(0, 0, 0, 0.25)'] - Shadow color |
||||
*/ |
||||
function drawShadow(x, y, radiusX, radiusY, color = 'rgba(0, 0, 0, 0.3)') { |
||||
ctx.beginPath(); |
||||
ctx.ellipse(x, y, radiusX, radiusY, 0, 0, Math.PI * 2); |
||||
ctx.fillStyle = color; |
||||
ctx.fill(); |
||||
} |
||||
|
||||
function render() { |
||||
ctx.clearRect(0, 0, canvas.width, canvas.height); |
||||
|
||||
drawGround(); |
||||
drawSprite(sprites.tree, 600, 50, 0.25); |
||||
drawSprite(sprites.tree, 200, 50, 0.2); |
||||
|
||||
// Slide on the right
|
||||
drawSprite(sprites.slide, 880, 80, 0.4); |
||||
|
||||
// Seesaw
|
||||
drawSprite(sprites.seesaw, 200, 280, 0.30); |
||||
|
||||
|
||||
// Shadow under the stand
|
||||
drawShadow(500, 360, 180, 50); |
||||
|
||||
drawSprite(sprites.maker, 430, 160, 0.6); |
||||
drawSprite(sprites.stand, 350, 50, 0.7); |
||||
drawSprite(sprites.pitcher, 620, 290, 0.3); |
||||
|
||||
|
||||
// Draw all cup instances
|
||||
cups.forEach(cup => drawInstance(cup)); |
||||
|
||||
} |
||||
|
||||
render(); |
||||
|
||||
// Example: fill the first cup after 1 second
|
||||
setTimeout(() => { |
||||
// Example: fill the first cup after 1 second
|
||||
setTimeout(() => { |
||||
cups[0].fill(); |
||||
sprites.maker.frameIndex = 1; |
||||
render(); |
||||
}, 1000); |
||||
}, 1000); |
||||
}); |
||||
|
||||
// UI Elements
|
||||
const goShoppingBtn = document.querySelector('.go-shopping-btn'); |
||||
|
||||
// Event handlers
|
||||
if (goShoppingBtn) { |
||||
goShoppingBtn.addEventListener('click', () => { |
||||
console.log('Go shopping clicked!'); |
||||
// TODO: Open shopping modal/UI
|
||||
}); |
||||
} |
||||
|
||||
// Export for debugging in console
|
||||
window.gameState = gameState; |
||||
window.sprites = sprites; |
||||
window.cups = cups; |
||||
|
||||
Loading…
Reference in new issue