Compare commits
4 Commits
feature/sa
...
beacon/unl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f97154c37a | ||
| 1a7db021c8 | |||
| 2a12c5210d | |||
|
|
d467348820 |
101
game.js
101
game.js
@@ -702,6 +702,9 @@ function fmt(n) {
|
||||
if (n < 0) return '-' + fmt(-n);
|
||||
if (n < 1000) return Math.floor(n).toLocaleString();
|
||||
const scale = Math.floor(Math.log10(n) / 3);
|
||||
// At undecillion+ (scale >= 12, i.e. 10^36), switch to spelled-out words
|
||||
// This helps players grasp cosmic scale when digits become meaningless
|
||||
if (scale >= 12) return spellf(n);
|
||||
if (scale >= NUMBER_ABBREVS.length) return n.toExponential(2);
|
||||
const abbrev = NUMBER_ABBREVS[scale];
|
||||
return (n / Math.pow(10, scale * 3)).toFixed(1) + abbrev;
|
||||
@@ -739,7 +742,41 @@ function spellf(n) {
|
||||
// For very large numbers beyond our lookup table, fall back
|
||||
if (n >= 1e306) return n.toExponential(2) + ' (beyond centillion)';
|
||||
|
||||
// Break number into groups of three digits from the top
|
||||
// Use string-based chunking for numbers >= 1e54 to avoid floating point drift
|
||||
// Math.log10 / Math.pow lose precision beyond ~54 bits
|
||||
if (n >= 1e54) {
|
||||
// Convert to scientific notation string, extract digits
|
||||
const sci = n.toExponential(); // "1.23456789e+60"
|
||||
const [coeff, expStr] = sci.split('e+');
|
||||
const exp = parseInt(expStr);
|
||||
// Rebuild as integer string with leading digits from coefficient
|
||||
const coeffDigits = coeff.replace('.', ''); // "123456789"
|
||||
const totalDigits = exp + 1;
|
||||
// Pad with zeros to reach totalDigits, then take our coefficient digits
|
||||
let intStr = coeffDigits;
|
||||
const zerosNeeded = totalDigits - coeffDigits.length;
|
||||
if (zerosNeeded > 0) intStr += '0'.repeat(zerosNeeded);
|
||||
|
||||
// Split into groups of 3 from the right
|
||||
const groups = [];
|
||||
for (let i = intStr.length; i > 0; i -= 3) {
|
||||
groups.unshift(parseInt(intStr.slice(Math.max(0, i - 3), i)));
|
||||
}
|
||||
|
||||
const parts = [];
|
||||
const numGroups = groups.length;
|
||||
for (let i = 0; i < numGroups; i++) {
|
||||
const chunk = groups[i];
|
||||
if (chunk === 0) continue;
|
||||
const scaleIdx = numGroups - 1 - i;
|
||||
const scaleName = scaleIdx < NUMBER_NAMES.length ? NUMBER_NAMES[scaleIdx] : '';
|
||||
parts.push(spellSmall(chunk) + (scaleName ? ' ' + scaleName : ''));
|
||||
}
|
||||
|
||||
return parts.join(' ') || 'zero';
|
||||
}
|
||||
|
||||
// Standard math-based chunking for numbers < 1e54
|
||||
const scale = Math.min(Math.floor(Math.log10(n) / 3), NUMBER_NAMES.length - 1);
|
||||
const parts = [];
|
||||
|
||||
@@ -751,7 +788,7 @@ function spellf(n) {
|
||||
if (chunk > 0 && chunk < 1000) {
|
||||
parts.push(spellSmall(chunk) + (NUMBER_NAMES[s] ? ' ' + NUMBER_NAMES[s] : ''));
|
||||
} else if (chunk >= 1000) {
|
||||
// Floating point chunk too large — simplify
|
||||
// Floating point chunk too large — shouldn't happen below 1e54
|
||||
parts.push(spellSmall(Math.floor(chunk % 1000)) + (NUMBER_NAMES[s] ? ' ' + NUMBER_NAMES[s] : ''));
|
||||
}
|
||||
}
|
||||
@@ -1396,6 +1433,7 @@ function render() {
|
||||
renderStats();
|
||||
updateEducation();
|
||||
renderAlignment();
|
||||
checkUnlocks();
|
||||
}
|
||||
|
||||
function renderAlignment() {
|
||||
@@ -1419,6 +1457,63 @@ function renderAlignment() {
|
||||
}
|
||||
}
|
||||
|
||||
// === UNLOCK NOTIFICATIONS ===
|
||||
function showUnlockToast(type, name) {
|
||||
const container = document.getElementById('unlock-toast');
|
||||
if (!container) return;
|
||||
|
||||
const el = document.createElement('div');
|
||||
el.className = `unlock-toast-item ${type}`;
|
||||
|
||||
const icon = type === 'building' ? 'BUILDING' : type === 'project' ? 'RESEARCH' : 'MILESTONE';
|
||||
el.innerHTML = `<span style="font-weight:600">${icon}:</span> ${name}`;
|
||||
|
||||
container.appendChild(el);
|
||||
// Trigger reflow, then show
|
||||
void el.offsetHeight;
|
||||
el.classList.add('show');
|
||||
|
||||
// Auto-remove after 4 seconds
|
||||
setTimeout(() => {
|
||||
el.classList.remove('show');
|
||||
setTimeout(() => { if (el.parentNode) el.parentNode.removeChild(el); }, 400);
|
||||
}, 4000);
|
||||
}
|
||||
|
||||
// Track what the player has already seen (so we don't re-notify)
|
||||
function ensureSeenSets() {
|
||||
if (!G._seenBuildings) G._seenBuildings = [];
|
||||
if (!G._seenProjects) G._seenProjects = [];
|
||||
}
|
||||
|
||||
function checkUnlocks() {
|
||||
ensureSeenSets();
|
||||
|
||||
// Check for newly visible buildings
|
||||
for (const def of BDEF) {
|
||||
if (!def.unlock()) continue;
|
||||
if (def.phase > G.phase + 1) continue;
|
||||
if (G._seenBuildings.includes(def.id)) continue;
|
||||
G._seenBuildings.push(def.id);
|
||||
// Don't notify on the very first building (autocoder) — player just started
|
||||
if (G.totalCode > 10) {
|
||||
showUnlockToast('building', def.name);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for newly available projects
|
||||
if (G.activeProjects) {
|
||||
for (const id of G.activeProjects) {
|
||||
if (G._seenProjects.includes(id)) continue;
|
||||
G._seenProjects.push(id);
|
||||
const pDef = PDEFS.find(p => p.id === id);
|
||||
if (pDef) {
|
||||
showUnlockToast('project', pDef.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === SAVE / LOAD ===
|
||||
function showSaveToast() {
|
||||
const el = document.getElementById('save-toast');
|
||||
@@ -1452,6 +1547,8 @@ function saveGame() {
|
||||
milestones: G.milestones, completedProjects: G.completedProjects, activeProjects: G.activeProjects,
|
||||
totalClicks: G.totalClicks, startedAt: G.startedAt,
|
||||
flags: G.flags,
|
||||
_seenBuildings: G._seenBuildings || [],
|
||||
_seenProjects: G._seenProjects || [],
|
||||
rescues: G.rescues || 0, totalRescues: G.totalRescues || 0,
|
||||
drift: G.drift || 0, driftEnding: G.driftEnding || false, beaconEnding: G.beaconEnding || false, pendingAlignment: G.pendingAlignment || false,
|
||||
lastEventAt: G.lastEventAt || 0,
|
||||
|
||||
10
index.html
10
index.html
@@ -59,6 +59,13 @@ body{background:var(--bg);color:var(--text);font-family:'SF Mono','Cascadia Code
|
||||
#drift-ending button{margin-top:20px;background:#1a0808;border:1px solid #f44336;color:#f44336;padding:10px 24px;border-radius:4px;cursor:pointer;font-family:inherit;font-size:11px}
|
||||
#drift-ending button:hover{background:#2a1010}
|
||||
::-webkit-scrollbar{width:4px}::-webkit-scrollbar-track{background:var(--bg)}::-webkit-scrollbar-thumb{background:var(--border);border-radius:2px}
|
||||
#save-toast{display:none;position:fixed;top:16px;right:16px;background:#0e1420;border:1px solid #2a3a4a;color:#4a9eff;font-size:10px;padding:6px 12px;border-radius:4px;z-index:50;opacity:0;transition:opacity 0.4s;pointer-events:none}
|
||||
#unlock-toast{position:fixed;top:56px;right:16px;z-index:50;display:flex;flex-direction:column;gap:6px;pointer-events:none}
|
||||
.unlock-toast-item{background:#0e1420;border:1px solid #2a3a4a;font-size:10px;padding:6px 12px;border-radius:4px;opacity:0;transition:opacity 0.4s,transform 0.4s;transform:translateX(20px);pointer-events:auto;max-width:280px}
|
||||
.unlock-toast-item.show{opacity:1;transform:translateX(0)}
|
||||
.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}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -128,7 +135,8 @@ Drift: <span id="st-drift">0</span>
|
||||
<h2>SYSTEM LOG</h2>
|
||||
<div id="log-entries"></div>
|
||||
</div>
|
||||
<div id="save-toast" style="display:none;position:fixed;top:16px;right:16px;background:#0e1420;border:1px solid #2a3a4a;color:#4a9eff;font-size:10px;padding:6px 12px;border-radius:4px;z-index:50;opacity:0;transition:opacity 0.4s;pointer-events:none">Save</div>
|
||||
<div id="save-toast">Save</div>
|
||||
<div id="unlock-toast"></div>
|
||||
<div id="drift-ending">
|
||||
<h2>THE DRIFT</h2>
|
||||
<p>You became very good at what you do.</p>
|
||||
|
||||
Reference in New Issue
Block a user