Compare commits

..

1 Commits

Author SHA1 Message Date
Alexander Whitestone
3aa1c36c88 beacon: add buy mode toggle (x1/x10/MAX) with B keyboard shortcut
The game had buyMode logic (1, 10, -1 for max) but zero UI to switch
between them. Players could only buy one building at a time.

Changes:
- Added x1/x10/MAX toggle buttons above the BUILDINGS section
- Active mode highlights in gold
- Press B to cycle through modes
- buyMode persists across saves
- Init log mentions the B shortcut
2026-04-10 02:21:19 -04:00
2 changed files with 90 additions and 6 deletions

89
game.js
View File

@@ -85,6 +85,7 @@ const G = {
tick: 0,
saveTimer: 0,
secTimer: 0,
buyMode: 1, // 1, 10, or -1 (max)
// Systems
projects: [],
@@ -808,6 +809,42 @@ function getBuildingCost(id) {
return cost;
}
function getBuildingBatchCost(id, count) {
const def = BDEF.find(b => b.id === id);
if (!def || count <= 0) return {};
const currentCount = G.buildings[id] || 0;
const cost = {};
for (let i = 0; i < count; i++) {
for (const [resource, amount] of Object.entries(def.baseCost)) {
cost[resource] = (cost[resource] || 0) + Math.floor(amount * Math.pow(def.costMult, currentCount + i));
}
}
return cost;
}
function getMaxBuyable(id) {
const def = BDEF.find(b => b.id === id);
if (!def) return 0;
// Simulate buying one at a time until we can't afford
let count = 0;
const tempResources = {};
for (const r of Object.keys(def.baseCost)) tempResources[r] = G[r] || 0;
const currentCount = G.buildings[id] || 0;
for (let i = 0; i < 1000; i++) { // cap at 1000
let canAfford = true;
for (const [resource, amount] of Object.entries(def.baseCost)) {
const cost = Math.floor(amount * Math.pow(def.costMult, currentCount + i));
if (tempResources[resource] < cost) { canAfford = false; break; }
}
if (!canAfford) break;
for (const [resource, amount] of Object.entries(def.baseCost)) {
tempResources[resource] -= Math.floor(amount * Math.pow(def.costMult, currentCount + i));
}
count++;
}
return count;
}
function canAffordBuilding(id) {
const cost = getBuildingCost(id);
for (const [resource, amount] of Object.entries(cost)) {
@@ -816,6 +853,16 @@ function canAffordBuilding(id) {
return true;
}
function canAffordBatch(id) {
const count = G.buyMode === -1 ? getMaxBuyable(id) : G.buyMode;
if (count <= 0) return false;
const cost = getBuildingBatchCost(id, count);
for (const [resource, amount] of Object.entries(cost)) {
if ((G[resource] || 0) < amount) return false;
}
return true;
}
function spendBuilding(id) {
const cost = getBuildingCost(id);
for (const [resource, amount] of Object.entries(cost)) {
@@ -1045,15 +1092,26 @@ function checkProjects() {
function buyBuilding(id) {
const def = BDEF.find(b => b.id === id);
if (!def || !def.unlock()) return;
if (def.phase > G.phase + 1) return;
if (!canAffordBuilding(id)) return;
const buyCount = G.buyMode === -1 ? getMaxBuyable(id) : G.buyMode;
if (buyCount <= 0) return;
spendBuilding(id);
G.buildings[id] = (G.buildings[id] || 0) + 1;
if (buyCount === 1) {
if (!canAffordBuilding(id)) return;
spendBuilding(id);
G.buildings[id] = (G.buildings[id] || 0) + 1;
log(`Built ${def.name} (total: ${G.buildings[id]})`);
} else {
if (!canAffordBatch(id)) return;
const cost = getBuildingBatchCost(id, buyCount);
for (const [resource, amount] of Object.entries(cost)) {
G[resource] -= amount;
}
G.buildings[id] = (G.buildings[id] || 0) + buyCount;
log(`Built ${buyCount}x ${def.name} (total: ${G.buildings[id]})`);
}
updateRates();
log(`Built ${def.name} (total: ${G.buildings[id]})`);
render();
}
@@ -1273,6 +1331,15 @@ function resolveAlignment(accept) {
}
// === ACTIONS ===
function setBuyMode(mode) {
G.buyMode = mode;
// Update active button highlight
document.querySelectorAll('.buy-mode-btn').forEach(btn => {
btn.classList.toggle('active', parseInt(btn.dataset.mode) === mode);
});
renderBuildings();
}
function writeCode() {
const base = 1;
const bonus = Math.floor(G.buildings.autocoder * 0.5);
@@ -1643,7 +1710,7 @@ function saveGame() {
branchProtectionFlag: G.branchProtectionFlag || 0, nightlyWatchFlag: G.nightlyWatchFlag || 0,
nostrFlag: G.nostrFlag || 0,
milestones: G.milestones, completedProjects: G.completedProjects, activeProjects: G.activeProjects,
totalClicks: G.totalClicks, startedAt: G.startedAt,
totalClicks: G.totalClicks, startedAt: G.startedAt, buyMode: G.buyMode,
flags: G.flags,
_seenBuildings: G._seenBuildings || [],
_seenProjects: G._seenProjects || [],
@@ -1719,6 +1786,7 @@ function initGame() {
log('The screen is blank. Write your first line of code.', true);
log('Click WRITE CODE or press SPACE to start.');
log('Press B to toggle buy mode (x1 / x10 / MAX).');
log('Build AutoCode for passive production.');
log('Watch for Research Projects to appear.');
}
@@ -1740,6 +1808,9 @@ window.addEventListener('load', function () {
}
}
// Restore buy mode button highlight
setBuyMode(G.buyMode || 1);
// Game loop at 10Hz (100ms tick)
setInterval(tick, 100);
@@ -1756,4 +1827,10 @@ window.addEventListener('keydown', function (e) {
e.preventDefault();
writeCode();
}
if (e.code === 'KeyB' && e.target === document.body) {
e.preventDefault();
// Cycle: x1 -> x10 -> MAX -> x1
const next = G.buyMode === 1 ? 10 : G.buyMode === 10 ? -1 : 1;
setBuyMode(next);
}
});

View File

@@ -66,6 +66,8 @@ body{background:var(--bg);color:var(--text);font-family:'SF Mono','Cascadia Code
.unlock-toast-item.building{border-color:#4a9eff;color:#4a9eff}
.unlock-toast-item.project{border-color:#ffd700;color:#ffd700}
.unlock-toast-item.milestone{border-color:#4caf50;color:#4caf50}
.buy-mode-btn{min-width:36px}
.buy-mode-btn.active{border-color:#ffd700!important;color:#ffd700!important;background:#1a1a08!important}
</style>
</head>
<body>
@@ -103,6 +105,11 @@ body{background:var(--bg);color:var(--text);font-family:'SF Mono','Cascadia Code
</div>
<div id="alignment-ui" style="display:none"></div>
<div id="events-ui" style="display:none"></div>
<div id="buy-mode-toggle" style="display:flex;gap:4px;margin-bottom:8px">
<button class="ops-btn buy-mode-btn active" onclick="setBuyMode(1)" data-mode="1">x1</button>
<button class="ops-btn buy-mode-btn" onclick="setBuyMode(10)" data-mode="10">x10</button>
<button class="ops-btn buy-mode-btn" onclick="setBuyMode(-1)" data-mode="-1">MAX</button>
</div>
<button class="save-btn" onclick="saveGame()">Save Game</button>
<button class="reset-btn" onclick="if(confirm('Reset all progress?')){localStorage.removeItem('the-beacon-v2');location.reload()}">Reset Progress</button>
<h2>BUILDINGS</h2>