diff --git a/canvas.js b/canvas.js index 9f1ecdd..66274df 100644 --- a/canvas.js +++ b/canvas.js @@ -118,6 +118,22 @@ export function drawSprite(spriteObject, x, y, scale) { ); } +/** + * Draws a sprite with alpha transparency. + * + * @param {Sprite} sprite - The sprite to draw + * @param {number} x - X position + * @param {number} y - Y position + * @param {number} scale - Scale factor + * @param {number} alpha - Alpha transparency (0-1) + */ +export function drawSpriteWithAlpha(sprite, x, y, scale, alpha) { + ctx.save(); + ctx.globalAlpha = alpha; + drawSprite(sprite, x, y, scale); + ctx.restore(); +} + /** * Draws a cup (or any object with sprite, x, y, scale, frameIndex). * diff --git a/canvasController.js b/canvasController.js index 1fbfcb2..325b4af 100644 --- a/canvasController.js +++ b/canvasController.js @@ -8,6 +8,7 @@ import { ctx, createSprite, drawSprite, + drawSpriteWithAlpha, drawInstance, drawGround, drawShadow, @@ -103,6 +104,44 @@ export const sprites = { }) }; +// Customer positions (alternating left/right) +const customerPositions = [ + { x: 300, y: 180 }, // left + { x: 700, y: 180 } // right +]; + +// Active customer state +export let activeCustomer = { + spriteKey: null, + positionIndex: 0, + alpha: 0 +}; + +// All customer sprite keys +const customerKeys = [ + 'customer1', 'customer2', 'customer3', 'customer4', 'customer5', + 'customer6', 'customer7', 'customer8', 'customer9', 'customer10', + 'customer11', 'customer12', 'customer13', 'customer14', 'customer15' +]; + +/** + * Sets the active customer for rendering. + * @param {string|null} spriteKey - Customer sprite key or null + * @param {number} positionIndex - 0 for left, 1 for right + * @param {number} alpha - Transparency (0-1) + */ +export function setActiveCustomer(spriteKey, positionIndex, alpha) { + activeCustomer = { spriteKey, positionIndex, alpha }; +} + +/** + * Gets a random customer sprite key. + * @returns {string} Random customer key + */ +export function getRandomCustomerKey() { + return customerKeys[Math.floor(Math.random() * customerKeys.length)]; +} + /** * Checks if all sprites are loaded. * @returns {boolean} @@ -167,10 +206,10 @@ export function createCup(x, y, scale = 0.1) { * @type {Array} */ export const cups = [ - createCup(455, 325, 0.15), - createCup(495, 325, 0.15), - createCup(535, 325, 0.15), - createCup(575, 325, 0.15), + createCup(455, 285, 0.15), + createCup(495, 285, 0.15), + createCup(535, 285, 0.15), + createCup(575, 285, 0.15), ]; /** @@ -196,14 +235,16 @@ export function render() { // Lemonade stand drawSprite(sprites.maker, 430, 160, 0.55); drawSprite(sprites.stand, 350, 50, 0.6); - drawSprite(sprites.pitcher, 620, 290, 0.3); + drawSprite(sprites.pitcher, 620, 260, 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); + // Active customer (with fade) + if (activeCustomer.spriteKey && activeCustomer.alpha > 0) { + const pos = customerPositions[activeCustomer.positionIndex]; + drawSpriteWithAlpha(sprites[activeCustomer.spriteKey], pos.x, pos.y, 0.45, activeCustomer.alpha); + } // Invoke callback if set if (onRenderCallback) onRenderCallback(); diff --git a/index.html b/index.html index 4a193dd..e5caefe 100644 --- a/index.html +++ b/index.html @@ -58,7 +58,7 @@
-

Day 1

+

Game

Cups sold: 0

Earnings: $0.00

diff --git a/index.js b/index.js index 04d104b..cb5f1d7 100644 --- a/index.js +++ b/index.js @@ -4,7 +4,7 @@ */ 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 { sprites, cups, render, whenSpritesReady, setActiveCustomer, getRandomCustomerKey } from './canvasController.js'; import { createReactiveState, updateBindings } from './binding.js'; // Initialize game state @@ -36,36 +36,78 @@ function generateSalesAttempts(maxCups, cupsSold) { return attempts; } +/** + * Fades a customer in or out using requestAnimationFrame. + * @param {string} spriteKey - Customer sprite key + * @param {number} posIndex - Position index (0=left, 1=right) + * @param {number} fromAlpha - Starting alpha + * @param {number} toAlpha - Ending alpha + * @param {number} duration - Duration in ms + * @param {Function} onComplete - Callback when complete + */ +function fadeCustomer(spriteKey, posIndex, fromAlpha, toAlpha, duration, onComplete) { + const startTime = performance.now(); + function step() { + const elapsed = performance.now() - startTime; + const progress = Math.min(elapsed / duration, 1); + const alpha = fromAlpha + (toAlpha - fromAlpha) * progress; + setActiveCustomer(spriteKey, posIndex, alpha); + render(); + if (progress < 1) { + requestAnimationFrame(step); + } else { + onComplete(); + } + } + step(); +} + function animateSales(attempts, onComplete) { let i = 0; let cupIndex = 0; + let posIndex = 0; function nextAttempt() { if (i >= attempts.length) { - sprites.maker.frameIndex = 0; // Maker idle + setActiveCustomer(null, 0, 0); + sprites.maker.frameIndex = 0; render(); onComplete(); return; } - 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(); + // Pick random customer, alternate position + const customerKey = getRandomCustomerKey(); + posIndex = (posIndex + 1) % 2; + + // Fade in customer + fadeCustomer(customerKey, posIndex, 0, 1, 500, () => { + if (attempts[i]) { + // Buy: maker active, empty cup, wait, refill, maker idle, fade out + sprites.maker.frameIndex = 1; + cups[cupIndex].empty(); render(); - cupIndex = (cupIndex + 1) % cups.length; - i++; - nextAttempt(); - }, 400); - } else { - // Pass: skip instantly - i++; - nextAttempt(); - } + setTimeout(() => { + sprites.maker.frameIndex = 0; + cups[cupIndex].fill(); + render(); + cupIndex = (cupIndex + 1) % cups.length; + // Fade out customer + fadeCustomer(customerKey, posIndex, 1, 0, 500, () => { + i++; + nextAttempt(); + }); + }, 600); + } else { + // Pass: brief pause then fade out + setTimeout(() => { + fadeCustomer(customerKey, posIndex, 1, 0, 500, () => { + i++; + nextAttempt(); + }); + }, 500); + } + }); } nextAttempt(); diff --git a/style.css b/style.css index d7e256f..544d532 100644 --- a/style.css +++ b/style.css @@ -113,7 +113,7 @@ body { } .game_section .section_hint { - font-size: 11px; + font-size: 13px; margin: 2px 0 8px; }