You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

258 lines
8.1 KiB

/**
* @fileoverview Main application orchestrator.
* 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 { sprites, cups, render, whenSpritesReady } from './canvasController.js';
import { createReactiveState, updateBindings } from './binding.js';
// Initialize game state
let gameState = createReactiveState(init_game());
updateBindings(gameState);
// Weather icon element
const weatherIcon = document.querySelector('.weather_icon');
function updateWeatherIcon() {
if (weatherIcon) {
weatherIcon.src = `${gameState.weather}.png`;
weatherIcon.alt = gameState.weather;
}
}
updateWeatherIcon();
let isAnimating = false;
function animateCupFills(count, onComplete) {
let filled = 0;
sprites.maker.frameIndex = 1; // Maker active
render();
const interval = setInterval(() => {
if (filled < count && filled < cups.length) {
cups[filled].fill();
render();
filled++;
} else {
clearInterval(interval);
sprites.maker.frameIndex = 0; // Maker idle
render();
onComplete();
}
}, 400);
}
function resetCups() {
cups.forEach(cup => cup.empty());
render();
}
// Wait for all sprites to load, then render once
whenSpritesReady(() => {
render();
resetCups(); // Start with empty cups
});
// UI Elements
const goShoppingBtn = document.querySelector('.go_shopping_btn');
const shoppingModal = document.querySelector('.shopping_modal');
const shoppingModalClose = document.querySelector('.shopping_modal_close');
const changePriceBtn = document.querySelector('.change_price_button');
const priceModal = document.querySelector('.price_change_modal');
const priceModalClose = document.querySelector('.price_change_modal_close');
const priceInput = document.querySelector('.price_input');
const priceSaveBtn = document.querySelector('.price_change_save_btn');
const changeRecipeBtn = document.querySelector('.change_recipe_btn');
const recipeModal = document.querySelector('.recipe_modal');
const recipeModalClose = document.querySelector('.recipe_modal_close');
const startDayBtn = document.querySelector('.start_day_btn');
const recipeSaveBtn = document.querySelector('.recipe_save_btn');
// Shopping modal - quantity inputs and dynamic pricing
const shopQtyInputs = document.querySelectorAll('.shop_qty_input');
const shopBuyBtns = document.querySelectorAll('.shop_item_btn');
function updateShopPrice(item) {
const input = document.querySelector(`.shop_qty_input[data-item="${item}"]`);
const priceDisplay = document.querySelector(`.shop_item_price[data-price="${item}"]`);
const qty = parseInt(input.value) || 0;
const cost = calculate_supply_cost(item, qty);
priceDisplay.textContent = '$' + cost.toFixed(2);
}
// Update prices when quantity changes
shopQtyInputs.forEach(input => {
input.addEventListener('input', () => {
updateShopPrice(input.dataset.item);
});
});
// Buy button handlers
shopBuyBtns.forEach(btn => {
btn.addEventListener('click', () => {
const item = btn.dataset.item;
const input = document.querySelector(`.shop_qty_input[data-item="${item}"]`);
const qty = parseInt(input.value) || 0;
const cost = calculate_supply_cost(item, qty);
if (cost > gameState.player_money) {
alert("Not enough money!");
return;
}
setState({
player_money: gameState.player_money - cost,
supplies: {
...gameState.supplies,
[item]: gameState.supplies[item] + qty
}
});
});
});
// Event handlers
if (goShoppingBtn) {
goShoppingBtn.addEventListener('click', () => {
// Update all prices when modal opens
['lemons', 'sugar', 'ice', 'cups'].forEach(updateShopPrice);
shoppingModal.classList.add('open');
});
shoppingModalClose.addEventListener('click', () => {
shoppingModal.classList.remove('open');
})
}
if (changePriceBtn) {
changePriceBtn.addEventListener('click', () => {
priceModal.classList.add('open');
priceInput.focus();
const priceInputLength = priceInput.value.length;
priceInput.setSelectionRange(priceInputLength, priceInputLength);
});
priceModalClose.addEventListener('click', () => {
priceModal.classList.remove('open');
});
priceSaveBtn.addEventListener('click', () => {
const newState = set_price_per_cup(gameState, Number(priceInput.value));
setState(newState);
priceModal.classList.remove('open');
})
}
// Recipe modal handlers
const recipeInputs = document.querySelectorAll('.recipe_input');
const recipeCostValue = document.querySelector('.recipe_cost_value');
// Base prices for cost breakdown (matches SupplyPricing tier 1 in game.js)
const basePrices = { lemons: 0.02, sugar: 0.01, ice: 0.01, cup: 0.01 };
function updateRecipeCost() {
const lemons = parseInt(document.querySelector('.recipe_input[data-recipe="lemons"]').value) || 0;
const sugar = parseInt(document.querySelector('.recipe_input[data-recipe="sugar"]').value) || 0;
const ice = parseInt(document.querySelector('.recipe_input[data-recipe="ice"]').value) || 0;
// Update breakdown rows
document.querySelector('.recipe_cost_item[data-cost="lemons"]').textContent = '$' + (lemons * basePrices.lemons).toFixed(2);
document.querySelector('.recipe_cost_item[data-cost="sugar"]').textContent = '$' + (sugar * basePrices.sugar).toFixed(2);
document.querySelector('.recipe_cost_item[data-cost="ice"]').textContent = '$' + (ice * basePrices.ice).toFixed(2);
document.querySelector('.recipe_cost_item[data-cost="cup"]').textContent = '$' + basePrices.cup.toFixed(2);
// Update total
const result = calculate_cost_per_cup(gameState, { lemons, sugar, ice });
recipeCostValue.textContent = '$' + result.cost_per_cup.toFixed(2);
}
// Update cost when recipe inputs change
recipeInputs.forEach(input => {
input.addEventListener('input', updateRecipeCost);
});
if (changeRecipeBtn) {
changeRecipeBtn.addEventListener('click', () => {
// Set inputs to current recipe values
document.querySelector('.recipe_input[data-recipe="lemons"]').value = gameState.recipe.lemons;
document.querySelector('.recipe_input[data-recipe="sugar"]').value = gameState.recipe.sugar;
document.querySelector('.recipe_input[data-recipe="ice"]').value = gameState.recipe.ice;
updateRecipeCost();
recipeModal.classList.add('open');
});
recipeModalClose.addEventListener('click', () => {
recipeModal.classList.remove('open');
});
recipeSaveBtn.addEventListener('click', () => {
const lemons = parseInt(document.querySelector('.recipe_input[data-recipe="lemons"]').value) || 0;
const sugar = parseInt(document.querySelector('.recipe_input[data-recipe="sugar"]').value) || 0;
const ice = parseInt(document.querySelector('.recipe_input[data-recipe="ice"]').value) || 0;
const result = calculate_cost_per_cup(gameState, { lemons, sugar, ice });
setState({
recipe: { lemons, sugar, ice },
cost_per_cup: result.cost_per_cup
});
recipeModal.classList.remove('open');
});
}
// Start Day button
if (startDayBtn) {
startDayBtn.addEventListener('click', startDay);
}
// Export for debugging in console
window.gameState = gameState;
window.sprites = sprites;
window.cups = cups;
function setState(newState) {
Object.assign(gameState, newState);
if (newState.weather) {
updateWeatherIcon();
}
}
function startDay() {
if (gameState.supplies.cups <= 0) {
alert('You need cups to sell lemonade!');
return;
}
const { recipe } = gameState;
if (recipe.lemons <= 0 && recipe.sugar <= 0 && recipe.ice <= 0) {
alert('Set a recipe with at least one ingredient!');
return;
}
if (isAnimating) return; // Prevent double-click
isAnimating = true;
startDayBtn.disabled = true;
resetCups();
const result = make_lemonade(gameState);
const cupsSold = result.cups_sold;
// Animate cup fills
animateCupFills(cupsSold, () => {
setState(result);
// Randomize weather for next day
const newWeatherState = set_weather(gameState);
setState({ weather: newWeatherState.weather });
isAnimating = false;
startDayBtn.disabled = false;
});
}