From 3454df077c5d32750d4fc6c5e1b6b7d6abb03fe0 Mon Sep 17 00:00:00 2001 From: Stephanie Gredell Date: Wed, 24 Dec 2025 00:40:21 -0800 Subject: [PATCH] add customer loading --- canvasController.js | 57 ++++++++++++++++++++++++++++++++--- game.js | 21 +++++++++++++ index.html | 18 ++++------- index.js | 73 +++++++++++++++++++++++++++++++++------------ style.css | 2 ++ 5 files changed, 136 insertions(+), 35 deletions(-) diff --git a/canvasController.js b/canvasController.js index 0860b69..1fbfcb2 100644 --- a/canvasController.js +++ b/canvasController.js @@ -55,6 +55,51 @@ export const sprites = { }), seesaw: createSprite({ source: 'seesaw.png' + }), + customer1: createSprite({ + source: 'customer_1.png' + }), + customer2: createSprite({ + source: 'customer_2.png' + }), + customer3: createSprite({ + source: 'customer_3.png' + }), + customer4: createSprite({ + source: 'customer_4.png' + }), + customer5: createSprite({ + source: 'customer_5.png' + }), + customer6: createSprite({ + source: 'customer_6.png' + }), + customer7: createSprite({ + source: 'customer_7.png' + }), + customer8: createSprite({ + source: 'customer_8.png' + }), + customer9: createSprite({ + source: 'customer_9.png' + }), + customer10: createSprite({ + source: 'customer_10.png' + }), + customer11: createSprite({ + source: 'customer_11.png' + }), + customer12: createSprite({ + source: 'customer_12.png' + }), + customer13: createSprite({ + source: 'customer_13.png' + }), + customer14: createSprite({ + source: 'customer_14.png' + }), + customer15: createSprite({ + source: 'customer_15.png' }) }; @@ -136,7 +181,7 @@ export function render() { // Background drawGround(sprites.grass); - + // Background trees drawSprite(sprites.tree, 600, 50, 0.25); drawSprite(sprites.tree, 200, 50, 0.2); @@ -149,13 +194,17 @@ export function render() { drawShadow(500, 360, 180, 50); // Lemonade stand - drawSprite(sprites.maker, 430, 160, 0.6); - drawSprite(sprites.stand, 350, 50, 0.7); + drawSprite(sprites.maker, 430, 160, 0.55); + drawSprite(sprites.stand, 350, 50, 0.6); drawSprite(sprites.pitcher, 620, 290, 0.3); // Draw all cup instances cups.forEach(cup => drawInstance(cup)); + // Customers + drawSprite(sprites.customer2, 300, 180, 0.45); + drawSprite(sprites.customer5, 700, 180, 0.45); + // Invoke callback if set if (onRenderCallback) onRenderCallback(); -} \ No newline at end of file +} diff --git a/game.js b/game.js index cf815ae..0a2de76 100644 --- a/game.js +++ b/game.js @@ -443,3 +443,24 @@ export function calculate_cost_per_cup(game_state, recipe) { cost_per_cup: Math.round(cost * 100) / 100, } } + +/** + * Calculate the maximum cups that can be produced from available supplies. + * @param {Supplies} supplies - Available supplies + * @param {Recipe} recipe - Recipe per cup + * @returns {number} Maximum cups producible (floored) + */ +export function calculate_maximum_cups_available(supplies, recipe) { + console.assert(supplies, 'supplies must be defined'); + console.assert(recipe, 'recipe must be defined'); + + // Can't make lemonade without lemons + if (recipe.lemons === 0) return 0; + + return Math.floor(Math.min( + recipe.lemons > 0 ? supplies.lemons / recipe.lemons : Infinity, + recipe.sugar > 0 ? supplies.sugar / recipe.sugar : Infinity, + recipe.ice > 0 ? supplies.ice / recipe.ice : Infinity, + supplies.cups + )); +} \ No newline at end of file diff --git a/index.html b/index.html index 01cc481..4a193dd 100644 --- a/index.html +++ b/index.html @@ -16,6 +16,12 @@
+
+

Weather

+ Weather +

sunny

+
+

Supplies

@@ -51,18 +57,6 @@
-
-

Total Earnings

- -

$0.00

-
- -
-

Weather

- Weather -

sunny

-
-

Day 1

Cups sold: 0

diff --git a/index.js b/index.js index 1136ae8..04d104b 100644 --- a/index.js +++ b/index.js @@ -3,7 +3,7 @@ * Initializes game state, wires up UI events, and coordinates modules. */ -import { init_game, set_price_per_cup, calculate_supply_cost, calculate_cost_per_cup, make_lemonade, set_weather } from './game.js'; +import { init_game, set_price_per_cup, calculate_supply_cost, calculate_cost_per_cup, make_lemonade, set_weather, calculate_maximum_cups_available } from './game.js'; import { sprites, cups, render, whenSpritesReady } from './canvasController.js'; import { createReactiveState, updateBindings } from './binding.js'; @@ -25,23 +25,55 @@ updateWeatherIcon(); let isAnimating = false; -function animateCupFills(count, onComplete) { - let filled = 0; - sprites.maker.frameIndex = 1; // Maker active - render(); +function generateSalesAttempts(maxCups, cupsSold) { + const attempts = Array(maxCups).fill(false); + for (let i = 0; i < cupsSold; i++) attempts[i] = true; + // Fisher-Yates shuffle + for (let i = attempts.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [attempts[i], attempts[j]] = [attempts[j], attempts[i]]; + } + return attempts; +} - const interval = setInterval(() => { - if (filled < count && filled < cups.length) { - cups[filled].fill(); - render(); - filled++; - } else { - clearInterval(interval); +function animateSales(attempts, onComplete) { + let i = 0; + let cupIndex = 0; + + function nextAttempt() { + if (i >= attempts.length) { sprites.maker.frameIndex = 0; // Maker idle render(); onComplete(); + return; } - }, 400); + + if (attempts[i]) { + // Buy: maker active, empty cup, wait, refill, maker idle + sprites.maker.frameIndex = 1; + cups[cupIndex].empty(); + render(); + setTimeout(() => { + sprites.maker.frameIndex = 0; + cups[cupIndex].fill(); + render(); + cupIndex = (cupIndex + 1) % cups.length; + i++; + nextAttempt(); + }, 400); + } else { + // Pass: skip instantly + i++; + nextAttempt(); + } + } + + nextAttempt(); +} + +function fillAllCups() { + cups.forEach(cup => cup.fill()); + render(); } function resetCups() { @@ -230,22 +262,25 @@ function startDay() { } const { recipe } = gameState; - if (recipe.lemons <= 0 && recipe.sugar <= 0 && recipe.ice <= 0) { - alert('Set a recipe with at least one ingredient!'); + if (recipe.lemons <= 0) { + alert('Your recipe needs lemons!'); return; } - if (isAnimating) return; // Prevent double-click + if (isAnimating) return; isAnimating = true; startDayBtn.disabled = true; - resetCups(); + // Fill all cups at start + fillAllCups(); + const maxCups = calculate_maximum_cups_available(gameState.supplies, gameState.recipe); const result = make_lemonade(gameState); const cupsSold = result.cups_sold; - // Animate cup fills - animateCupFills(cupsSold, () => { + const attempts = generateSalesAttempts(maxCups, cupsSold); + + animateSales(attempts, () => { setState(result); // Randomize weather for next day diff --git a/style.css b/style.css index e28fffe..d7e256f 100644 --- a/style.css +++ b/style.css @@ -4,6 +4,8 @@ body { background: linear-gradient(180deg, #cfefff 0%, #f7ffe5 100%); min-height: 100vh; font-family: 'Inter', sans-serif; + padding: 0; + margin: 0; } .game_header {