Refactor: merge duplicated rules, split the largest files by topic (t21)
- One helper each for wall and mold refunds, tube edges and lanes, pipe text, piece dimensions, font lookup, reach circles and surface averaging. - previews, smooth, notebook, materials, worldgen and selection split into topic files (preview-drill, preview-selection, smooth-shaders, notebook-market, notebook-blueprints, material-tables, worldgen-helpers, selection-pickers, selection-panels). - Every suite's output identical apart from timings; seeds make the same worlds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+1
-114
@@ -1,4 +1,4 @@
|
||||
// The notebook (N): milestones, market, wallet, blueprints, controls.
|
||||
// Notebook: tab navigation, ledger, materials, pieces and controls.
|
||||
// ---------- Notebook ----------
|
||||
let nbTab = 'Ledger';
|
||||
function openNotebook(tab) { if (tab) nbTab = tab; renderNotebook(); openModal('m-notebook'); }
|
||||
@@ -55,119 +55,6 @@ function renderNotebook() {
|
||||
body.innerHTML = controlsHtml();
|
||||
}
|
||||
}
|
||||
function fmtClock(ticks) { const s = Math.max(0, Math.ceil(ticks / 60)); return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, '0')}`; }
|
||||
function orderName(o) { return `${o.who ? o.who + ': ' : ''}${o.qty.toLocaleString('en-US')} ${MD[o.m].name.toLowerCase()}${PH[o.m] === PH_ITEM && o.qty !== 1 ? 's' : ''}`; }
|
||||
function orderLeft(o) { return Math.max(0, o.due - tick); }
|
||||
const MARKET_GROUPS = [
|
||||
['Raw', [COAL, LWOOD, ACORN, SAND, LCLAY, RGRAN, RBAS, CUORE, FEORE, SNORE, ZNORE]],
|
||||
['Bulk (builders)', [RSTONE, GRAVEL, BARSB, PLANK, GREENB, BARBR]],
|
||||
['Refined', [CHARCOAL, RBRICK, CULLET, BARGL, SCRCU, SCRFE, SCRST, SCRSN, SCRZN, BARCU, BARSN, BARZN, BARFE, BARST]],
|
||||
['Alloys', [SCRBZ, SCRBS, BARBZ, BARBS]],
|
||||
['Precious', [AGORE, AUORE, SCRAG, SCRAU, BARAG, BARAU]],
|
||||
];
|
||||
function renderMarketTab(body) {
|
||||
let h = `<div class="mkt-sum"><div><div class="k">Money</div><div class="v">${fmtMoney(money)}</div></div>`
|
||||
+ `<div><div class="k">${debt > 0 ? 'Still owed on the claim' : 'The claim'}</div><div class="v" style="color:${debt > 0 ? 'var(--melt)' : 'var(--ok)'}">${debt > 0 ? fmtMoney(debt) : 'Paid off'}</div></div>`
|
||||
+ `<div><div class="k">Earned in total</div><div class="v">${fmtMoney(earned)}</div></div></div>`;
|
||||
if (debt > 0) h += `<div class="mkt-pay"><span class="note" style="margin:0 6px 0 0">Pay toward the claim:</span>${[1000, 5000].map(a => `<button class="btn" data-pay="${a}" ${money >= Math.min(a, debt) ? '' : 'disabled'}>${fmtMoney(Math.min(a, debt))}</button>`).join('')}<button class="btn primary" data-pay="all" ${money > 0 ? '' : 'disabled'}>All I can (${fmtMoney(Math.min(money, debt))})</button></div>`;
|
||||
else h += `<p class="note">The land is yours. Keep building: the ledger has bigger goals.</p>`;
|
||||
h += `<p class="note">Anything that drops through the trade dock's grate is sold on the spot, once it is cool enough to handle. Junk like dirt, ash and slag goes for nothing. Rock you dig still sells, builders order it by the bagful, and a Grinder finds ore in it. Build walls around the dock to funnel more in.</p>`;
|
||||
h += `<p class="label grouphead">Orders · ${esc(STANDING[standing()])}</p>`;
|
||||
if (orders.length) {
|
||||
h += '<div class="orderlist">' + orders.map(o => `<div class="o"><span class="t"><span class="sw" style="display:inline-block;vertical-align:-2px;margin-right:6px;background:${rgbOf(o.m)}"></span>${esc(orderName(o))}</span><span class="pay">${fmtMoney(o.pay)}</span>`
|
||||
+ `<span class="d">${o.got} of ${o.qty} delivered · ${o.mult}× market · ${fmtClock(orderLeft(o))} left</span><span></span><span class="pb"><i style="width:${(o.got / o.qty * 100).toFixed(1)}%"></i></span></div>`).join('') + '</div>';
|
||||
} else h += `<p class="note">No open orders. Buyers post new ones every minute or two, for goods you have found.</p>`;
|
||||
h += `<p class="note">Goods dropped into the dock fill a matching order first. A missed deadline costs nothing: what you delivered is paid at market price. ${ordersDone} filled so far; bigger buyers come with more.</p>`;
|
||||
h += `<p class="label grouphead">Prices</p><p class="note">Per grain, or per bar. Selling a lot of one thing lowers its price for a while; it recovers over about half an hour.</p>`;
|
||||
let rows = '', unknown = 0;
|
||||
for (const [g, list] of MARKET_GROUPS) {
|
||||
const known = list.filter(m => seen[m]);
|
||||
unknown += list.length - known.length;
|
||||
if (!known.length) continue;
|
||||
rows += `<tr><td colspan="3" class="label" style="padding-top:8px">${esc(g)}</td></tr>`;
|
||||
for (const m of known) {
|
||||
const f = priceFactor(m), now = priceNow(m);
|
||||
rows += `<tr><td><span class="sw" style="display:inline-block;vertical-align:-2px;margin-right:6px;background:${rgbOf(m)}"></span>${esc(MD[m].name)}</td><td class="num">${fmtPrice(PRICE[m])}</td><td class="num ${f < 0.97 ? 'dip' : ''}">${fmtPrice(now)}${f < 0.97 ? ` (${Math.round(f * 100)}%)` : ''}</td></tr>`;
|
||||
}
|
||||
}
|
||||
h += rows ? `<div class="tablewrap"><table class="mt"><thead><tr><th>Goods</th><th>Base</th><th>Now</th></tr></thead><tbody>${rows}</tbody></table></div>` : '<p class="note">Find something worth selling first.</p>';
|
||||
if (unknown) h += `<p class="note">${unknown} more goods to discover.</p>`;
|
||||
h += `<p class="label grouphead">Sales, minute by minute</p>`;
|
||||
const recent = saleAcc.total > 0 ? [{ total: saleAcc.total, items: saleAcc.items, now: true }].concat(shipLog) : shipLog;
|
||||
h += recent.length ? `<div class="shiplog">${recent.map(sl => `<div><span class="what">${sl.now ? 'This minute: ' : ''}${esc(itemsText(sl.items))}</span><b class="num">+${fmtMoney(sl.total)}</b></div>`).join('')}</div>` : '<p class="note">Nothing sold yet.</p>';
|
||||
body.innerHTML = h;
|
||||
body.querySelectorAll('[data-pay]').forEach(b => b.onclick = () => {
|
||||
const v = b.dataset.pay === 'all' ? money : +b.dataset.pay;
|
||||
const paid = payClaim(v);
|
||||
if (paid > 0) { playSfx('sale'); toast(`Paid ${fmtMoney(paid)} toward the claim.`); }
|
||||
renderNotebook(); hudDirty = true;
|
||||
});
|
||||
}
|
||||
function fmtPrice(v) { return v >= 10 ? '$' + Math.round(v) : '$' + v.toFixed(2); }
|
||||
function itemsText(items) {
|
||||
const e = Object.entries(items).map(([m, n]) => [+m, n]).sort((a, b) => b[1] * PRICE[b[0]] - a[1] * PRICE[a[0]]);
|
||||
const parts = e.slice(0, 3).map(([m, n]) => PH[m] === PH_ITEM ? `${n} ${MD[m].name.toLowerCase()}${n === 1 ? '' : 's'}` : `${n} ${MD[m].name.toLowerCase()}`);
|
||||
if (e.length > 3) parts.push(`${e.length - 3} more`);
|
||||
return parts.join(', ');
|
||||
}
|
||||
function renderWallet() {
|
||||
$('w-money').textContent = fmtMoney(money);
|
||||
const c = $('w-claim');
|
||||
c.className = 'line' + (debt <= 0 ? ' paid' : '');
|
||||
c.innerHTML = debt > 0 ? `<span>Claim owed</span><b>${fmtMoney(debt)}</b>` : `<span>Claim</span><b>paid off ✓</b>`;
|
||||
const h = orders.length ? orders.map(o => {
|
||||
const left = orderLeft(o);
|
||||
return `<span class="ord"><span class="sw" style="background:${rgbOf(o.m)}"></span><span class="nm">${o.got}/${esc(orderName(o))}</span><span class="tm${left < 3600 ? ' late' : ''}">${fmtClock(left)}</span><span class="pb"><i style="width:${(o.got / o.qty * 100).toFixed(1)}%"></i></span></span>`;
|
||||
}).join('') : '<span class="none">No orders yet</span>';
|
||||
const box = $('w-orders');
|
||||
if (box.innerHTML !== h) box.innerHTML = h;
|
||||
}
|
||||
function renderBlueprintTab(body) {
|
||||
const lib = libLoad();
|
||||
let h = `<p class="note">Select the Blueprint tool (5), drag over something you built to copy it, then click to place a ghost. Your bot builds ghosts from the materials in your bag when it is within reach. Right-click clears ghosts.</p>`;
|
||||
h += `<p class="label grouphead">Starter designs</p><p class="note">Optional. Tested builds to learn from or copy; place one as a ghost and your bot builds it from your bag. Clear the space first: ghosts wait for open air.</p><div class="cards">`;
|
||||
STARTER_DESIGNS.forEach((d, k) => {
|
||||
const bp = d.bp();
|
||||
h += `<div class="card"><div class="h"><span>${esc(d.name)}</span><span class="chip">${bp.bw}×${bp.bh} blocks</span></div><div class="p">${esc(d.desc)}</div><div class="p">Build cost: ${esc(costText(bpCost(bp), 1))}</div><div style="display:flex;gap:6px;flex-wrap:wrap"><button class="btn" data-starter="${k}">Place it</button></div></div>`;
|
||||
});
|
||||
h += '</div><p class="label grouphead">Your blueprints</p>';
|
||||
h += `<div class="set"><div><div>Clipboard</div><div class="d">${GAME.clip ? `${GAME.clip.bw} × ${GAME.clip.bh} blocks, ${GAME.clip.blocks.length} pieces${GAME.clip.machines.length ? `, ${GAME.clip.machines.length} machines` : ''}${GAME.clip.molds.length ? `, ${GAME.clip.molds.length} molds` : ''}. Needs ${esc(costText(bpCost(GAME.clip), 1) || 'nothing')}` : 'Empty'}</div></div><div class="ctl"><button class="btn" id="bp-save" ${GAME.clip ? '' : 'disabled'}>Save to library</button><button class="btn" id="bp-code" ${GAME.clip ? '' : 'disabled'}>Copy code</button></div></div>`;
|
||||
h += `<div class="set"><div><div>Import a blueprint code</div><div class="d">Paste a code that starts with DGBP2.</div></div><div class="ctl"><button class="btn" id="bp-import">Paste code…</button></div></div>`;
|
||||
h += `<p class="label grouphead">Library</p>`;
|
||||
if (!lib.length) h += `<p class="note">No saved blueprints yet.</p>`;
|
||||
else {
|
||||
h += '<div class="cards">';
|
||||
lib.forEach((it, k) => {
|
||||
const ok = bpValid(it.bp);
|
||||
h += `<div class="card"><div class="h"><span>${esc(it.name)}</span><span class="chip">${ok ? `${it.bp.bw}×${it.bp.bh}` : 'old format'}</span></div><div class="p">${ok ? esc(costText(bpCost(it.bp), 1) || 'free') : 'Made before the block grid. It cannot be placed.'}</div><div style="display:flex;gap:6px;flex-wrap:wrap">${ok ? `<button class="btn" data-bpload="${k}">Use</button><button class="btn ghost" data-bpcode="${k}">Copy code</button>` : ''}<button class="btn ghost danger" data-bpdel="${k}">Delete</button></div></div>`;
|
||||
});
|
||||
h += '</div>';
|
||||
}
|
||||
body.innerHTML = h;
|
||||
const sv = $('bp-save'); if (sv) sv.onclick = () => {
|
||||
dialog('Name this blueprint', '<input class="text" id="bp-name" maxlength="40" placeholder="Copper smelter">', [
|
||||
{ label: 'Cancel', cls: 'ghost' },
|
||||
{ label: 'Save', cls: 'primary', fn: () => { const n = ($('bp-name').value || 'Untitled').trim(); const l = libLoad(); l.push({ name: n, bp: GAME.clip }); if (libSave(l)) toast(`Saved “${n}”.`); renderNotebook(); } },
|
||||
]);
|
||||
setTimeout(() => $('bp-name') && $('bp-name').focus(), 30);
|
||||
};
|
||||
const cc = $('bp-code'); if (cc) cc.onclick = () => copyCode(bpEncode(GAME.clip));
|
||||
$('bp-import').onclick = () => {
|
||||
dialog('Import blueprint', '<textarea class="code" id="bp-paste" placeholder="DGBP1:…"></textarea>', [
|
||||
{ label: 'Cancel', cls: 'ghost' },
|
||||
{ label: 'Import', cls: 'primary', fn: () => { try { GAME.clip = bpDecode($('bp-paste').value); GAME.paste = true; hudDirty = true; toast('Blueprint ready. Click to place it.'); closeAllModals(); } catch (e) { toast(e.message); return true; } } },
|
||||
]);
|
||||
};
|
||||
body.querySelectorAll('[data-bpload]').forEach(b => b.onclick = () => { GAME.clip = libLoad()[+b.dataset.bpload].bp; GAME.paste = true; hudDirty = true; closeAllModals(); toast('Blueprint ready. Click to place it.'); });
|
||||
body.querySelectorAll('[data-starter]').forEach(b => b.onclick = () => { GAME.clip = STARTER_DESIGNS[+b.dataset.starter].bp(); GAME.paste = true; hudDirty = true; closeAllModals(); toast('Click to place it. R flips it.'); });
|
||||
body.querySelectorAll('[data-bpcode]').forEach(b => b.onclick = () => copyCode(bpEncode(libLoad()[+b.dataset.bpcode].bp)));
|
||||
body.querySelectorAll('[data-bpdel]').forEach(b => b.onclick = () => { const l = libLoad(); l.splice(+b.dataset.bpdel, 1); libSave(l); renderNotebook(); });
|
||||
}
|
||||
function copyCode(code) {
|
||||
const done = () => toast('Blueprint code copied.');
|
||||
const fallback = () => { dialog('Blueprint code', `<p class="note">Select the text and copy it.</p><textarea class="code" id="bp-out" readonly>${esc(code)}</textarea>`, [{ label: 'Done', cls: 'primary' }]); setTimeout(() => { const t = $('bp-out'); if (t) { t.focus(); t.select(); } }, 30); };
|
||||
try { navigator.clipboard.writeText(code).then(done, fallback); } catch (e) { fallback(); }
|
||||
}
|
||||
function controlsHtml() {
|
||||
const kb = [
|
||||
['Fly', 'W A S D'], ['Boost while flying', 'Shift'], ['Tool modifier (while not flying)', 'Shift'], ['Use tool', 'Left mouse'], ['Second action', 'Right mouse'],
|
||||
|
||||
Reference in New Issue
Block a user