diff --git a/css/game.css b/css/game.css index 0460a3d..4b3528c 100644 --- a/css/game.css +++ b/css/game.css @@ -301,6 +301,32 @@ h2, h3 { opacity: 0.5; } +.buy-max-btn { + display: block; + width: 100%; + margin-top: 6px; + background: var(--accent-purple); + color: var(--text-primary); + border: none; + border-radius: 6px; + padding: 5px 12px; + cursor: pointer; + font-weight: bold; + font-size: 0.8rem; + transition: all 0.2s ease; +} + +.buy-max-btn:hover:not(:disabled) { + background: #9b59b6; + transform: scale(1.03); +} + +.buy-max-btn:disabled { + background: var(--text-muted); + cursor: not-allowed; + opacity: 0.4; +} + /* Side Panel Tabs */ .tabs { display: flex; diff --git a/dev-journal.md b/dev-journal.md index 8b6f191..f7763bb 100644 --- a/dev-journal.md +++ b/dev-journal.md @@ -266,5 +266,31 @@ firstPrestige achievement, save/load round-trip preserving essence. (Requires `pip install py_mini_racer`.) #### Next goals: -- Achievement progress tracking in stats panel - Consider adding a "next essence at X stardust" progress hint + +--- + +### Session 10 - 2026-08-22 (Morning) +**Status:** ✅ Buy Max (Bulk-Buy) Feature Added + +#### What was done: +- Added a "Buy Max" bulk-buy mechanic — a staple of idle/clicker games that was previously missing (you could only buy one building at a time, forcing many tedious clicks). +- Added `Game.getMaxAffordable(buildingId)` which computes how many of a given building the player can afford right now using an iterative cost-scaling loop. It is read-only and does not mutate state. +- Added `Game.buyMaxBuilding(buildingId)` which loops buying one at a time until unaffordable, deducting the correct total stardust and incrementing counts properly (respects prestige costReduction upgrades since it reuses `getBuildingCost`). +- Wired a "Buy Max" button onto every building card in `UI.renderBuildings()` with an affordable-count badge (e.g. "5x Max"). +- Updated `UI.updateBuildings()` so the Buy Max badge refreshes its count and enabled/disabled state every game tick, keeping it accurate as stardust accrues. +- Added CSS styling for `.buy-max-btn` (purple theme, hover scale, disabled state) to match existing building-card layout. + +#### Files modified: +- `js/game.js` - added getMaxAffordable() and buyMaxBuilding() methods +- `js/ui.js` - rendered Buy Max button per building card + tick refresh in updateBuildings() +- `css/game.css` - .buy-max-btn styles +- `dev-journal.md` - appended Session 10 entry + +#### Verification: +Wrote a headless Node.js test (`/opt/data/idle-game-test/buy-max-test.mjs`, lives OUTSIDE the repo). Evaluated the real game.js in-process with DOM/UI stubs. 6/6 checks pass: getMaxAffordable matches manual cost-scaling calc, buyMax deducts correct stardust and increments counts correctly, unaffordable purchase returns 0, getMaxAffordable is idempotent, and notifications fire on purchase. + +#### Next goals: +- Consider adding "Buy Max" to upgrades too (upgrades are one-time so less critical, but a bulk "buy all affordable" could be nice). +- Potential new building tiers or prestige milestones. +- Sound variations or background music. diff --git a/js/game.js b/js/game.js index cb3983c..4f164d1 100644 --- a/js/game.js +++ b/js/game.js @@ -233,7 +233,7 @@ const Game = { }, /** - * Buy a building + * Buy a single building */ buyBuilding(buildingId) { const cost = this.getBuildingCost(buildingId); @@ -261,6 +261,63 @@ const Game = { return false; }, + /** + * Calculate how many of a building the player can currently afford. + * Does not modify game state. + */ + getMaxAffordable(buildingId) { + const building = BUILDINGS[buildingId]; + if (!building) return 0; + + let stardust = this.state.stardust; + let count = 0; + let owned = this.state.buildings[buildingId] || 0; + + while (true) { + const cost = Math.floor(building.baseCost * Math.pow(this.config.costScaling, owned + count)); + if (stardust < cost) break; + stardust -= cost; + count++; + } + + return count; + }, + + /** + * Buy as many of a building as the player can afford at once. + * Returns the number purchased. + */ + buyMaxBuilding(buildingId) { + const building = BUILDINGS[buildingId]; + if (!building) return 0; + + let count = 0; + while (this.state.stardust >= this.getBuildingCost(buildingId)) { + const cost = this.getBuildingCost(buildingId); + this.state.stardust -= cost; + this.state.buildings[buildingId] = (this.state.buildings[buildingId] || 0) + 1; + count++; + } + + if (count > 0) { + UI.showNotification(`Bought ${count}x ${building.name}!`, 'success'); + + // Audio feedback + Audio.playPurchase(); + + // Particle effect on building purchase + const card = document.getElementById(`building-${buildingId}`); + if (card) { + const rect = card.getBoundingClientRect(); + Particles.createBuildingEffect(rect.left + rect.width / 2, rect.top + rect.height / 2); + } + + UI.update(); + } + + return count; + }, + /** * Buy an upgrade */ diff --git a/js/ui.js b/js/ui.js index bd90adc..ce083ba 100644 --- a/js/ui.js +++ b/js/ui.js @@ -69,6 +69,7 @@ const UI = {