|
|
|
@ -3,7 +3,7 @@ |
|
|
|
* Initializes game state, wires up UI events, and coordinates modules. |
|
|
|
* 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 { sprites, cups, render, whenSpritesReady } from './canvasController.js'; |
|
|
|
import { createReactiveState, updateBindings } from './binding.js'; |
|
|
|
import { createReactiveState, updateBindings } from './binding.js'; |
|
|
|
|
|
|
|
|
|
|
|
@ -25,23 +25,55 @@ updateWeatherIcon(); |
|
|
|
|
|
|
|
|
|
|
|
let isAnimating = false; |
|
|
|
let isAnimating = false; |
|
|
|
|
|
|
|
|
|
|
|
function animateCupFills(count, onComplete) { |
|
|
|
function generateSalesAttempts(maxCups, cupsSold) { |
|
|
|
let filled = 0; |
|
|
|
const attempts = Array(maxCups).fill(false); |
|
|
|
sprites.maker.frameIndex = 1; // Maker active
|
|
|
|
for (let i = 0; i < cupsSold; i++) attempts[i] = true; |
|
|
|
render(); |
|
|
|
// 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(() => { |
|
|
|
function animateSales(attempts, onComplete) { |
|
|
|
if (filled < count && filled < cups.length) { |
|
|
|
let i = 0; |
|
|
|
cups[filled].fill(); |
|
|
|
let cupIndex = 0; |
|
|
|
render(); |
|
|
|
|
|
|
|
filled++; |
|
|
|
function nextAttempt() { |
|
|
|
} else { |
|
|
|
if (i >= attempts.length) { |
|
|
|
clearInterval(interval); |
|
|
|
|
|
|
|
sprites.maker.frameIndex = 0; // Maker idle
|
|
|
|
sprites.maker.frameIndex = 0; // Maker idle
|
|
|
|
render(); |
|
|
|
render(); |
|
|
|
onComplete(); |
|
|
|
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() { |
|
|
|
function resetCups() { |
|
|
|
@ -230,22 +262,25 @@ function startDay() { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const { recipe } = gameState; |
|
|
|
const { recipe } = gameState; |
|
|
|
if (recipe.lemons <= 0 && recipe.sugar <= 0 && recipe.ice <= 0) { |
|
|
|
if (recipe.lemons <= 0) { |
|
|
|
alert('Set a recipe with at least one ingredient!'); |
|
|
|
alert('Your recipe needs lemons!'); |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (isAnimating) return; // Prevent double-click
|
|
|
|
if (isAnimating) return; |
|
|
|
isAnimating = true; |
|
|
|
isAnimating = true; |
|
|
|
startDayBtn.disabled = 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 result = make_lemonade(gameState); |
|
|
|
const cupsSold = result.cups_sold; |
|
|
|
const cupsSold = result.cups_sold; |
|
|
|
|
|
|
|
|
|
|
|
// Animate cup fills
|
|
|
|
const attempts = generateSalesAttempts(maxCups, cupsSold); |
|
|
|
animateCupFills(cupsSold, () => { |
|
|
|
|
|
|
|
|
|
|
|
animateSales(attempts, () => { |
|
|
|
setState(result); |
|
|
|
setState(result); |
|
|
|
|
|
|
|
|
|
|
|
// Randomize weather for next day
|
|
|
|
// Randomize weather for next day
|
|
|
|
|