Bars no longer get thrown up into the furnace, and F is no longer needed to collect them. Blocked bars wait in the trough; the short stub stays for the next firing; F is optional upkeep (clean, chip slag, rake). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
140 lines
11 KiB
JavaScript
140 lines
11 KiB
JavaScript
// Everything the player can build (PIECES): blocks, machines and molds, with costs and
|
|
// descriptions; filter settings; wall materials and their cost.
|
|
// ---------- Building pieces ----------
|
|
// Everything you build snaps to a grid of 4x4-grain blocks. The world itself
|
|
// stays grain-level; only built pieces snap.
|
|
const BS = 4;
|
|
const BP_WALL = 1, BP_BELT = 2, BP_TUBE = 3, BP_SHAFT = 4, BP_GRATE = 5, BP_GATE = 6, BP_FILTER = 7, BP_PIPE = 8, BP_PUMP = 9, BP_OUTLET = 10;
|
|
function isPipeCode(c) { return c === BP_PIPE || c === BP_PUMP || c === BP_OUTLET; }
|
|
// Filters: each filter block has a set of materials and a mode. In drop mode (inv 0)
|
|
// the chosen materials fall through and everything else rides along on top; in carry
|
|
// mode (inv 1) the chosen ones ride along and everything else falls through. The block
|
|
// variant's top bit is which way it carries (1 right, 0 left).
|
|
let filterCfg = new Map(); // block index -> { m: [materials], inv: 0|1, pass: Uint8Array(NMAT) }
|
|
function filterable(m) { return m > 0 && m < NMAT && (PH[m] === PH_POWDER || PH[m] === PH_ITEM); }
|
|
function cleanFilterCfg(c) { return { m: [...new Set(((c && c.m) || []).filter(filterable))].sort((a, b) => a - b), inv: c && c.inv ? 1 : 0 }; }
|
|
function filterRec(c) {
|
|
const k = cleanFilterCfg(c), pass = new Uint8Array(NMAT);
|
|
for (let m = 1; m < NMAT; m++) if (filterable(m)) pass[m] = (k.m.includes(m) ? 1 : 0) ^ k.inv;
|
|
return { m: k.m, inv: k.inv, pass };
|
|
}
|
|
function filterPassAt(m, j) { const c = filterCfg.get((((j / W) | 0) >> 2) * BW + ((j % W) >> 2)); return c ? c.pass[m] : 0; }
|
|
function filterInfo(b) { const c = filterCfg.get(b); return c ? { m: c.m.slice(), inv: c.inv } : { m: [], inv: 0 }; }
|
|
function sameFilter(a, b) { return a.inv === b.inv && a.m.length === b.m.length && a.m.every((m, k) => m === b.m[k]); }
|
|
// loose material -> the solid it becomes in a wall block
|
|
const WALL_OF = new Uint8Array(NMAT);
|
|
WALL_OF[LDIRT] = DIRT; WALL_OF[RSTONE] = STONE; WALL_OF[RGRAN] = GRANITE; WALL_OF[RBAS] = BASALT; WALL_OF[LCLAY] = CLAY;
|
|
WALL_OF[LWOOD] = WOOD; WALL_OF[RBRICK] = BRICK; WALL_OF[CULLET] = GLASS; WALL_OF[BARCU] = COPPER; WALL_OF[BARFE] = IRON; WALL_OF[BARST] = STEEL;
|
|
WALL_OF[BARSB] = CUTST; WALL_OF[BARGL] = GLASS;
|
|
const WALL_LIST = [RSTONE, BARSB, LCLAY, RBRICK, LDIRT, LWOOD, CULLET, BARGL, RGRAN, RBAS, BARCU, BARFE, BARST];
|
|
const BAR_OF = new Uint8Array(NMAT); BAR_OF[COPPER] = BARCU; BAR_OF[IRON] = BARFE; BAR_OF[STEEL] = BARST;
|
|
BAR_OF[GLASS] = BARGL; BAR_OF[CUTST] = BARSB; BAR_OF[TIN] = BARSN; BAR_OF[ZINC] = BARZN; BAR_OF[SILVER] = BARAG; BAR_OF[GOLD] = BARAU; BAR_OF[BRONZE] = BARBZ; BAR_OF[BRASS] = BARBS;
|
|
const GRAINS_PER_BAR = 8;
|
|
function wallCost(v, cells) { return [[v, PH[v] === PH_ITEM ? Math.ceil(cells / GRAINS_PER_BAR) : cells]]; }
|
|
|
|
// For now every machine simply runs: no power to supply. The power pieces (shaft,
|
|
// water wheel, steam engine) are hidden from the build menu but old saves keep them.
|
|
const POWER_FREE = true;
|
|
const CASH = 255; // stands for money in cost lists
|
|
const PIECES = [
|
|
{ id: 'wall', code: BP_WALL, name: 'Wall block', kind: 'block', variants: 'wall',
|
|
desc: 'A solid block. R picks what it is made of: 16 grains of rubble, lumps or chunks, or 2 bars of metal.' },
|
|
{ id: 'belt', code: BP_BELT, name: 'Belt', kind: 'block', cost: [[CASH, 12]], dirs: ['left', 'right'],
|
|
desc: 'Carries loose grains and bars resting on top of it, left or right, in a layer up to a block deep. Water and other liquids drain through it.' },
|
|
{ id: 'tube', code: BP_TUBE, name: 'Screw tube', kind: 'block', cost: [[CASH, 25]], dirs: ['up', 'right', 'down', 'left'],
|
|
desc: 'A turning screw that draws in loose material and pushes it along, four grains wide. Water and other liquids run straight through it; the screw never carries them. Stack them pointing up to lift ore out of a mine, and turn the top one sideways to unload. Copper softens around 1000°C.' },
|
|
{ id: 'filter', code: BP_FILTER, name: 'Filter', kind: 'block', cost: [[CASH, 40]], dirs: ['left', 'right'], filter: true,
|
|
desc: 'A stretch of belt with a mesh in it. Put it in a line of belts and it joins them as one belt. Pick materials and a mode: drop them through and carry everything else along, or carry them and drop the rest. Liquids drain through.' },
|
|
{ id: 'shaft', code: BP_SHAFT, name: 'Shaft', kind: 'block', cost: [[LWOOD, 8]], hidden: POWER_FREE,
|
|
desc: 'Carries power between machines. Wooden, so keep it away from fire.' },
|
|
{ id: 'grate', code: BP_GRATE, name: 'Grate', kind: 'block', cost: [[LCLAY, 8]],
|
|
desc: 'Ceramic grille. Liquids and gases pass; powders and bars do not.' },
|
|
{ id: 'gate', code: BP_GATE, name: 'Gate', kind: 'block', cost: [[LCLAY, 8]],
|
|
desc: 'Ceramic slide gate. Press F on it to open or close every gate block touching it.' },
|
|
{ id: 'pipe', code: BP_PIPE, name: 'Pipe', kind: 'block', cost: [[CASH, 8]],
|
|
desc: 'Thin copper pipe for liquids. Sand, water and anything else loose fall past it, so a run of pipe never dams anything. Every pipe, pump and outlet touching another joins it in one line, and the line holds 16 grains of liquid per block. Drag to lay a run. Taking a pipe back spills what was in it.' },
|
|
{ id: 'pump', code: BP_PUMP, name: 'Pump', kind: 'block', cost: [[CASH, 70]], dirs: ['up', 'right', 'down', 'left'],
|
|
desc: 'Draws liquid in at the tail of its arrow (keep that side under water) and pushes it into the pipes on its other sides, about 60 grains a second while the line has room. It takes water and anything else cooler than 500°C, one liquid at a time per line. Press F to switch it on or off.' },
|
|
{ id: 'outlet', code: BP_OUTLET, name: 'Outlet', kind: 'block', cost: [[CASH, 15]], dirs: ['down', 'left', 'up', 'right'],
|
|
desc: 'Lets a pipe line\'s liquid out of the side its arrow points to, wherever there is room for it. Press F to open or close it.' },
|
|
{ id: 'wheel', name: 'Water wheel', kind: 'machine', bw: 3, bh: 3, cost: [[LWOOD, 48]], hidden: POWER_FREE,
|
|
desc: 'Makes power when liquid falls through it. More flow, more power.' },
|
|
{ id: 'fan', name: 'Fan', kind: 'machine', bw: 1, bh: 1, cost: [[CASH, 60]], dirs: ['right', 'down', 'left', 'up'],
|
|
desc: 'Blows gas and forces air into fires, making them far hotter: its stream reaches 28 grains and passes through grates but not walls. Its copper body melts at 1085°C, so keep it outside the furnace, a block clear of the wall, blowing in through a grate. Press F to switch it on or off.' },
|
|
{ id: 'cutter', name: 'Stone cutter', kind: 'machine', bw: 2, bh: 2, cost: [[CASH, 90]],
|
|
desc: 'A saw under a hopper. Stone rubble dropped in is cut into stone bricks, one from every 8 grains, which drop out underneath. Builders buy stone bricks, and they make neat cut stone walls that stand more heat than rough stone. Anything else falls straight through.' },
|
|
{ id: 'silo', name: 'Silo', kind: 'machine', bw: 3, bh: 6, cost: [[CASH, 180]],
|
|
desc: 'A steel bin on legs for one loose material: up to 4,000 grains go in through the hatch in its roof (drop them in from a belt or tube). Its gauge shows how full it is. Press F to open the gate in its cone and let it out underneath, about 60 grains a second; F again shuts it. The first material in decides what it holds until it is empty.' },
|
|
{ id: 'refiner', name: 'Refiner', kind: 'machine', bw: 3, bh: 3, cost: [[CASH, 120]],
|
|
desc: 'A crushing sieve. Drop stone, granite or basalt rubble, gravel, sand or dirt into its hopper: it grinds each grain, keeps the odd grain of ore it finds (clay, from dirt) and lets about half as much fine sand out of the chute underneath. Ore and anything else it cannot grind falls straight through. Keep the space under the chute clear, or a belt running under it.' },
|
|
{ id: 'engine', name: 'Steam engine', kind: 'machine', bw: 3, bh: 2, cost: [[BARCU, 10]], dirs: ['exhaust right', 'exhaust left'], hidden: POWER_FREE,
|
|
desc: 'Steam rising into the intake slots on its underside drives it. Condensate drips out the exhaust side.' },
|
|
{ id: 'dock', name: 'Trade dock', kind: 'machine', bw: 6, bh: 4, cost: [], hidden: true, fixed: true,
|
|
desc: 'Owned by the claim office. Anything that drops through the grate in its floor is sold on the spot. Build walls around it to funnel more in.' },
|
|
{ id: 'ingotmold', name: 'Ingot mold', kind: 'mold', mold: 'ingot', variants: 'wall',
|
|
desc: 'A long trough. Pour molten metal in: bars of 8 grains drop out of the bottom when set, up to 3. Leave one open block below it; a belt under that carries them off. F cleans the trough, chips off slag and rakes the furnace grate.' },
|
|
{ id: 'bitmold', name: 'Bit mold', kind: 'mold', mold: 'bit', variants: 'wall',
|
|
desc: 'A drill-bit-shaped cavity. Cast a harder metal in it to upgrade your drill.' },
|
|
{ id: 'tankmold', name: 'Tank mold', kind: 'mold', mold: 'tank', variants: 'wall',
|
|
desc: 'A tank-shaped cavity. Cast metal in it to carry more.' },
|
|
];
|
|
const PIECE_BY_ID = {}, PIECE_BY_CODE = [];
|
|
PIECES.forEach((p, i) => { p.index = i; PIECE_BY_ID[p.id] = p; if (p.code) PIECE_BY_CODE[p.code] = p; if (p.kind === 'machine') { p.w = p.bw * BS; p.h = p.bh * BS; } });
|
|
// power per grain of belt / tube (a block is 16 grains)
|
|
const BELT_POWER = 0.03, TUBE_POWER = 0.125, FAN_POWER = 6;
|
|
const WHEEL_K = 0.32, ENGINE_K = 40;
|
|
|
|
const MOLDS = {
|
|
ingot: {
|
|
// one long trough: metal sets level and drops bars of 8 grains below
|
|
name: 'Ingot mold', rows: [
|
|
'................',
|
|
'#oooooooooooooo#',
|
|
'#oooooooooooooo#',
|
|
'################',
|
|
]
|
|
},
|
|
bit: {
|
|
name: 'Bit mold', rows: [
|
|
'#..............#',
|
|
'##............##',
|
|
'###oooooooooo###',
|
|
'####oooooooo####',
|
|
'#####oooooo#####',
|
|
'######oooo######',
|
|
'#######oo#######',
|
|
'################',
|
|
]
|
|
},
|
|
tank: {
|
|
name: 'Tank mold', rows: [
|
|
'#..............#',
|
|
'#oo##########oo#',
|
|
'#oo##########oo#',
|
|
'#oo##########oo#',
|
|
'#oo##########oo#',
|
|
'#oooooooooooooo#',
|
|
'#oooooooooooooo#',
|
|
'################',
|
|
]
|
|
},
|
|
};
|
|
for (const k in MOLDS) {
|
|
const md = MOLDS[k]; md.w = md.rows[0].length; md.h = md.rows.length; md.bw = md.w / BS; md.bh = md.h / BS; md.walls = 0; md.cav = 0;
|
|
for (const r of md.rows) for (const ch of r) { if (ch === '#') md.walls++; else if (ch === 'o') md.cav++; }
|
|
// split the cavity into separate pieces (the ingot mold has three)
|
|
const seenC = new Set(); md.cavs = [];
|
|
for (let r = 0; r < md.h; r++) for (let c = 0; c < md.w; c++) {
|
|
if (md.rows[r][c] !== 'o' || seenC.has(r * 64 + c)) continue;
|
|
const comp = [], st = [[c, r]]; seenC.add(r * 64 + c);
|
|
while (st.length) {
|
|
const [cx, cy] = st.pop(); comp.push([cx, cy]);
|
|
for (const [nx, ny] of [[cx + 1, cy], [cx - 1, cy], [cx, cy + 1], [cx, cy - 1]]) {
|
|
if (nx < 0 || ny < 0 || nx >= md.w || ny >= md.h || md.rows[ny][nx] !== 'o' || seenC.has(ny * 64 + nx)) continue;
|
|
seenC.add(ny * 64 + nx); st.push([nx, ny]);
|
|
}
|
|
}
|
|
md.cavs.push(comp);
|
|
}
|
|
}
|