diff --git a/game.js b/game.js index 41575f2..122728f 100644 --- a/game.js +++ b/game.js @@ -118,6 +118,14 @@ const G = { // Bulk buy multiplier (1, 10, or -1 for max) buyAmount: 1, + // Code Sprint ability + sprintActive: false, + sprintTimer: 0, // seconds remaining on active sprint + sprintCooldown: 0, // seconds until sprint available again + sprintDuration: 10, // seconds of boost + sprintCooldownMax: 60,// seconds cooldown + sprintMult: 10, // code multiplier during sprint + // Time tracking playTime: 0, startTime: 0 @@ -1027,8 +1035,24 @@ function tick() { G.creativity += G.creativityRate * dt; } + // Ops overflow: auto-convert excess ops to code when near cap + // Prevents ops from sitting idle at max — every operation becomes code + if (G.ops > G.maxOps * 0.8) { + const overflowDrain = Math.min(2 * dt, G.ops - G.maxOps * 0.8); + G.ops -= overflowDrain; + const codeGain = overflowDrain * 10 * G.codeBoost; + G.code += codeGain; + G.totalCode += codeGain; + G.opsOverflowActive = true; + } else { + G.opsOverflowActive = false; + } + G.tick += dt; + // Sprint ability + tickSprint(dt); + // Combo decay if (G.comboCount > 0) { G.comboTimer -= dt; @@ -1479,6 +1503,33 @@ function doOps(action) { render(); } +function activateSprint() { + if (G.sprintActive || G.sprintCooldown > 0) return; + G.sprintActive = true; + G.sprintTimer = G.sprintDuration; + G.codeBoost *= G.sprintMult; + updateRates(); + log('CODE SPRINT! 10x code production for 10 seconds!', true); + render(); +} + +function tickSprint(dt) { + if (G.sprintActive) { + G.sprintTimer -= dt; + if (G.sprintTimer <= 0) { + G.sprintActive = false; + G.sprintTimer = 0; + G.sprintCooldown = G.sprintCooldownMax; + G.codeBoost /= G.sprintMult; + updateRates(); + log('Sprint ended. Cooling down...'); + } + } else if (G.sprintCooldown > 0) { + G.sprintCooldown -= dt; + if (G.sprintCooldown < 0) G.sprintCooldown = 0; + } +} + // === RENDERING === function renderResources() { const set = (id, val, rate) => { @@ -1494,6 +1545,11 @@ function renderResources() { set('r-users', G.users, G.userRate); set('r-impact', G.impact, G.impactRate); set('r-ops', G.ops, G.opsRate); + // Show ops overflow indicator + const opsRateEl = document.getElementById('r-ops-rate'); + if (opsRateEl && G.opsOverflowActive) { + opsRateEl.innerHTML = `▲ overflow → code`; + } set('r-trust', G.trust, G.trustRate); set('r-harmony', G.harmony, G.harmonyRate); @@ -1891,6 +1947,53 @@ function renderDebuffs() { container.innerHTML = html; } +function renderSprint() { + const container = document.getElementById('sprint-container'); + const btn = document.getElementById('sprint-btn'); + const barWrap = document.getElementById('sprint-bar-wrap'); + const bar = document.getElementById('sprint-bar'); + const label = document.getElementById('sprint-label'); + if (!container || !btn) return; + + // Show sprint UI once player has at least 1 autocoder + if (G.buildings.autocoder < 1) { + container.style.display = 'none'; + return; + } + container.style.display = 'block'; + + if (G.sprintActive) { + btn.disabled = true; + btn.style.opacity = '0.6'; + btn.textContent = `⚡ SPRINTING — ${Math.ceil(G.sprintTimer)}s`; + btn.style.borderColor = '#ff8c00'; + btn.style.color = '#ff8c00'; + barWrap.style.display = 'block'; + bar.style.width = (G.sprintTimer / G.sprintDuration * 100) + '%'; + label.textContent = `10x CODE • ${fmt(G.codeRate)}/s`; + label.style.color = '#ff8c00'; + } else if (G.sprintCooldown > 0) { + btn.disabled = true; + btn.style.opacity = '0.4'; + btn.textContent = `⚡ COOLING DOWN — ${Math.ceil(G.sprintCooldown)}s`; + btn.style.borderColor = '#555'; + btn.style.color = '#555'; + barWrap.style.display = 'block'; + bar.style.width = ((G.sprintCooldownMax - G.sprintCooldown) / G.sprintCooldownMax * 100) + '%'; + label.textContent = 'Ready soon...'; + label.style.color = '#555'; + } else { + btn.disabled = false; + btn.style.opacity = '1'; + btn.textContent = '⚡ CODE SPRINT — 10x Code for 10s'; + btn.style.borderColor = '#ffd700'; + btn.style.color = '#ffd700'; + barWrap.style.display = 'none'; + label.textContent = 'Press S or click to activate'; + label.style.color = '#666'; + } +} + function render() { renderResources(); renderPhase(); @@ -1902,6 +2005,7 @@ function render() { renderProgress(); renderCombo(); renderDebuffs(); + renderSprint(); } function renderAlignment() { @@ -1995,6 +2099,9 @@ function saveGame() { activeDebuffIds: debuffIds, totalEventsResolved: G.totalEventsResolved || 0, buyAmount: G.buyAmount || 1, + sprintActive: G.sprintActive || false, + sprintTimer: G.sprintTimer || 0, + sprintCooldown: G.sprintCooldown || 0, savedAt: Date.now() }; @@ -2010,6 +2117,28 @@ function loadGame() { const data = JSON.parse(raw); Object.assign(G, data); + // Restore sprint state properly + // codeBoost was saved with the sprint multiplier baked in + if (data.sprintActive) { + // Sprint was active when saved — check if it expired during offline time + const offSec = data.savedAt ? (Date.now() - data.savedAt) / 1000 : 0; + const remaining = (data.sprintTimer || 0) - offSec; + if (remaining > 0) { + // Sprint still going — keep boost, update timer + G.sprintActive = true; + G.sprintTimer = remaining; + G.sprintCooldown = 0; + } else { + // Sprint expired during offline — remove boost, start cooldown + G.sprintActive = false; + G.sprintTimer = 0; + G.codeBoost /= G.sprintMult; + const cdRemaining = G.sprintCooldownMax + remaining; // remaining is negative + G.sprintCooldown = Math.max(0, cdRemaining); + } + } + // If not sprintActive at save time, codeBoost is correct as-is + // Reconstitute active debuffs from saved IDs (functions can't be JSON-parsed) if (data.activeDebuffIds && data.activeDebuffIds.length > 0) { G.activeDebuffs = []; @@ -2109,7 +2238,7 @@ function initGame() { log('Click WRITE CODE or press SPACE to start.'); log('Build AutoCode for passive production.'); log('Watch for Research Projects to appear.'); - log('Keys: SPACE=Code 1=Ops->Code 2=Ops->Compute 3=Ops->Knowledge 4=Ops->Trust B=Buy x1/x10/MAX'); + log('Keys: SPACE=Code S=Code Sprint 1=Ops->Code 2=Ops->Compute 3=Ops->Knowledge 4=Ops->Trust B=Buy x1/x10/MAX'); } window.addEventListener('load', function () { @@ -2156,4 +2285,5 @@ window.addEventListener('keydown', function (e) { else if (G.buyAmount === 10) setBuyAmount(-1); else setBuyAmount(1); } + if (e.code === 'KeyS') activateSprint(); }); diff --git a/index.html b/index.html index f90b471..549edac 100644 --- a/index.html +++ b/index.html @@ -124,6 +124,11 @@ body{background:var(--bg);color:var(--text);font-family:'SF Mono','Cascadia Code +