Session 14: Buy All Upgrades (bulk-buy for upgrades) + empty-slot boost leak fix
- Added Game.buyAllUpgrades(): buys every affordable upgrade at once, respecting per-upgrade requirements and looping until nothing remains. - Added a 'Buy All' button to the Upgrades tab header; UI.updateBuyAllButton() shows/hides & enables/disables it live each tick based on affordability. - Fixed empty-slot branch of switchSlot(): added productionBoostUntil: 0 so a temporary golden-bloom boost cannot leak into a fresh slot. - Added .upgrades-header / .buy-all-btn CSS (gold accent theme). Verified: headless test buy-all-upgrades-test.mjs 24/24 pass; existing golden-bloom (22/22) and buy-max (6/6) tests re-run clean, no regressions.
This commit is contained in:
+53
@@ -124,6 +124,14 @@ const Game = {
|
||||
UI.hideImportModal();
|
||||
});
|
||||
|
||||
// Buy All upgrades button.
|
||||
const buyAllBtn = document.getElementById('buy-all-upgrades');
|
||||
if (buyAllBtn) {
|
||||
buyAllBtn.addEventListener('click', () => {
|
||||
Game.buyAllUpgrades();
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('reset-btn').addEventListener('click', () => {
|
||||
if (confirm('Are you sure you want to reset all progress? This cannot be undone!')) {
|
||||
this.reset();
|
||||
@@ -419,6 +427,48 @@ const Game = {
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Buy every upgrade the player can currently afford at once.
|
||||
* Returns an object describing what happened: { bought: count, ids: [...] }.
|
||||
* Upgrades are one-time purchases, so we loop until nothing affordable remains.
|
||||
*/
|
||||
buyAllUpgrades() {
|
||||
const bought = [];
|
||||
// Loop until a pass buys nothing (each purchase may unlock new affordability).
|
||||
let progress = true;
|
||||
while (progress) {
|
||||
progress = false;
|
||||
for (const [id, upgrade] of Object.entries(UPGRADES)) {
|
||||
if (this.state.upgrades.includes(id)) continue;
|
||||
// Respect requirements before attempting purchase.
|
||||
if (upgrade.requirements) {
|
||||
let met = true;
|
||||
for (const [reqId, reqCount] of Object.entries(upgrade.requirements)) {
|
||||
if ((this.state.buildings[reqId] || 0) < reqCount) {
|
||||
met = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!met) continue;
|
||||
}
|
||||
if (this.state.stardust >= upgrade.cost) {
|
||||
this.state.stardust -= upgrade.cost;
|
||||
this.state.upgrades.push(id);
|
||||
bought.push(id);
|
||||
progress = true; // another purchase may have unlocked more affordability
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bought.length > 0) {
|
||||
UI.showNotification(`Bought ${bought.length} upgrade(s)!`, 'success');
|
||||
Audio.playPurchase();
|
||||
UI.update();
|
||||
}
|
||||
|
||||
return { bought: bought.length, ids: bought };
|
||||
},
|
||||
|
||||
/**
|
||||
* Golden Starbloom - random event system.
|
||||
* A rare, time-limited bloom occasionally appears on screen. Clicking it
|
||||
@@ -758,6 +808,9 @@ const Game = {
|
||||
totalPrestigeEssence: this.state.totalPrestigeEssence || 0,
|
||||
prestigeUpgrades: this.state.prestigeUpgrades || [],
|
||||
goldenBlooms: { active: false, timer: 0, lastSpawn: Date.now(), totalClaimed: 0 },
|
||||
// Reset the temporary golden-bloom production boost so a boost
|
||||
// from a previous slot cannot leak into a fresh slot.
|
||||
productionBoostUntil: 0,
|
||||
activeSlot: slot,
|
||||
autoSave: this.state.autoSave
|
||||
};
|
||||
|
||||
@@ -189,15 +189,52 @@ const UI = {
|
||||
for (const [id, upgrade] of Object.entries(UPGRADES)) {
|
||||
const card = document.getElementById(`upgrade-${id}`);
|
||||
if (!card) continue;
|
||||
|
||||
|
||||
const purchased = Game.state.upgrades.includes(id);
|
||||
const costEl = card.querySelector('.upgrade-cost');
|
||||
|
||||
|
||||
if (costEl && !purchased) {
|
||||
const canAfford = Game.state.stardust >= upgrade.cost;
|
||||
costEl.textContent = canAfford ? `${Game.formatNumber(upgrade.cost)} ✨` : `❌ ${Game.formatNumber(upgrade.cost)} ✨`;
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle the "Buy All" button: show it once at least one unowned,
|
||||
// requirement-met upgrade exists, and disable it when nothing is
|
||||
// affordable. Its enabled state is refreshed live each tick.
|
||||
this.updateBuyAllButton();
|
||||
},
|
||||
|
||||
/**
|
||||
* Show/hide and enable/disable the "Buy All upgrades" button based on
|
||||
* whether any unowned upgrade (whose requirements are met) is currently
|
||||
* affordable. Read of game state only; never mutates it.
|
||||
*/
|
||||
updateBuyAllButton() {
|
||||
const btn = document.getElementById('buy-all-upgrades');
|
||||
if (!btn) return;
|
||||
|
||||
let affordableAny = false;
|
||||
for (const [id, upgrade] of Object.entries(UPGRADES)) {
|
||||
if (Game.state.upgrades.includes(id)) continue;
|
||||
if (Game.state.stardust < upgrade.cost) continue;
|
||||
// Requirement check mirrors Game.buyUpgrade / buyAllUpgrades.
|
||||
if (upgrade.requirements) {
|
||||
let met = true;
|
||||
for (const [reqId, reqCount] of Object.entries(upgrade.requirements)) {
|
||||
if ((Game.state.buildings[reqId] || 0) < reqCount) {
|
||||
met = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!met) continue;
|
||||
}
|
||||
affordableAny = true;
|
||||
break;
|
||||
}
|
||||
|
||||
btn.style.display = affordableAny ? '' : 'none';
|
||||
btn.disabled = !affordableAny;
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user