Files
deepseek 8fbe70d0b0 Repterra Web — full game: base building, power grid, taming & breeding, aquatic raiders, day/night, save/load
- Isometric canvas RTS vs dinosaur waves (fan demake of Repterra)
- Economy: houses/taxes, farms, foresters, quarries; colonist staffing
- Power grid: generators extend build range; brownout + recovery
- Defense: walls/gates, watchtowers (AA), cannon towers (ground-only)
- Taming: Primal Pen + Tamers collar weakened dinos; pets obey commands
- Breeding: tamed pairs incubate eggs at the pen; hatchlings grow up
- 7 dino species incl. flying Pteranodons and lake-raiding Suchomimus
- Telegraphed waves with direction arrows; day-15 final horde; 3 difficulties
- Day/night cycle, fog of war, minimap, synth audio, 1x-3x speeds
- Save/Load/Continue + dawn autosave (full JSON state snapshots)
- Tests: 80-assertion headless suite, browser boot + E2E, balance harness
2026-08-23 07:00:23 +00:00

147 lines
5.6 KiB
JavaScript

/* =========================================================
* REPRTERRA WEB — world.js
* Procedural map generation (per run, like Repterra):
* grass/dirt/water, forest & rock deposits, dino caves.
* ========================================================= */
'use strict';
window.RTS = window.RTS || {};
RTS.world = (function () {
const U = RTS.util;
const W = RTS.CONFIG.WORLD.W;
const H = RTS.CONFIG.WORLD.H;
// value noise with bilinear interp + octaves
function makeNoise(rng, size) {
const g = new Float32Array(size * size);
for (let i = 0; i < g.length; i++) g[i] = rng();
return function (x, y) {
const xi = Math.floor(x), yi = Math.floor(y);
const xf = x - xi, yf = y - yi;
const sx = xf * xf * (3 - 2 * xf), sy = yf * yf * (3 - 2 * yf);
const x0 = ((xi % size) + size) % size, y0 = ((yi % size) + size) % size;
const x1 = (x0 + 1) % size, y1 = (y0 + 1) % size;
const a = U.lerp(g[y0 * size + x0], g[y0 * size + x1], sx);
const b = U.lerp(g[y1 * size + x0], g[y1 * size + x1], sx);
return U.lerp(a, b, sy);
};
}
function generate(seed) {
const rng = U.makeRng(seed || ((Date.now() / 1000) | 0));
const T = {
W, H,
terrain: new Uint8Array(W * H), // 0 grass, 1 grass2, 2 dirt, 3 water
tree: new Uint8Array(W * H), // wood units on tile (0..8)
treeMax: new Uint8Array(W * H),
rock: new Uint8Array(W * H), // stone units
rockMax: new Uint8Array(W * H),
variant: new Float32Array(W * H), // visual noise
};
const n1 = makeNoise(rng, 16), n2 = makeNoise(rng, 32), n3 = makeNoise(rng, 64);
// --- base terrain + water ---
for (let y = 0; y < H; y++) {
for (let x = 0; x < W; x++) {
const i = y * W + x;
let h = n1(x / 11, y / 11) * 0.55 + n2(x / 5.5, y / 5.5) * 0.3 + n3(x / 2.6, y / 2.6) * 0.15;
T.variant[i] = n3(x * 1.7, y * 1.7);
// keep center playable
const cx = x - W / 2, cy = y - H / 2;
const dc = Math.sqrt(cx * cx + cy * cy);
h += Math.max(0, (dc - 9)) * 0.004; // slight rise away from base
if (h < 0.30 && dc > 10) { T.terrain[i] = 3; continue; } // lake
T.terrain[i] = h < 0.42 ? 1 : (h < 0.47 ? 2 : 0);
if (rng() < 0.02 && T.terrain[i] === 1) T.terrain[i] = 2;
}
}
const clearArea = (cx, cy, r) => {
for (let y = Math.max(0, cy - r); y <= Math.min(H - 1, cy + r); y++)
for (let x = Math.max(0, cx - r); x <= Math.min(W - 1, cx + r); x++) {
const i = y * W + x;
if (U.dist(x, y, cx, cy) <= r) { T.tree[i] = 0; T.rock[i] = 0; }
}
};
const unwaterArea = (cx, cy, r) => {
for (let y = Math.max(0, cy - r); y <= Math.min(H - 1, cy + r); y++)
for (let x = Math.max(0, cx - r); x <= Math.min(W - 1, cx + r); x++) {
const i = y * W + x;
if (U.dist(x, y, cx, cy) <= r && T.terrain[i] === 3) T.terrain[i] = 1;
}
};
// --- HQ site: near center ---
const hq = { x: Math.floor(W / 2), y: Math.floor(H / 2) };
while (T.terrain[hq.y * W + hq.x] === 3) hq.x--;
unwaterArea(hq.x, hq.y, 9);
clearArea(hq.x, hq.y, 7);
// --- forests: blobby clusters ---
const forests = [];
const nForest = 14;
for (let f = 0; f < nForest; f++) {
const fx = rng.int(4, W - 5), fy = rng.int(4, H - 5);
if (U.dist(fx, fy, hq.x, hq.y) < 9) continue;
const fr = rng.range(2.5, 4.8);
forests.push({ fx, fy, fr });
for (let y = Math.max(0, fy - 6); y <= Math.min(H - 1, fy + 6); y++)
for (let x = Math.max(0, fx - 6); x <= Math.min(W - 1, fx + 6); x++) {
const i = y * W + x;
const d = U.dist(x, y, fx, fy) + n3(x / 2.2, y / 2.2) * 1.6;
if (d < fr && T.terrain[i] !== 3 && !(Math.abs(x - hq.x) < 7 && Math.abs(y - hq.y) < 7)) {
T.treeMax[i] = 8; T.tree[i] = 8;
T.rock[i] = 0;
}
}
}
// --- rocks: scattered outcrops ---
const nRock = 12;
for (let f = 0; f < nRock; f++) {
const rx = rng.int(4, W - 5), ry = rng.int(4, H - 5);
if (U.dist(rx, ry, hq.x, hq.y) < 10) continue;
const rr = rng.range(1.6, 3.0);
for (let y = Math.max(0, ry - 4); y <= Math.min(H - 1, ry + 4); y++)
for (let x = Math.max(0, rx - 4); x <= Math.min(W - 1, rx + 4); x++) {
const i = y * W + x;
const d = U.dist(x, y, rx, ry) + n3(x / 1.8, y / 1.8) * 1.2;
if (d < rr && T.terrain[i] !== 3 && !(Math.abs(x - hq.x) < 8 && Math.abs(y - hq.y) < 8)) {
T.rockMax[i] = 10; T.rock[i] = 10;
T.tree[i] = 0; T.treeMax[i] = 0;
}
}
}
// guarantee some starting resources within reach of HQ
const ensureNear = (field, fieldMax, want) => {
let placed = 0, guard = 0;
while (placed < want && guard++ < 400) {
const ang = rng() * Math.PI * 2;
const dd = rng.range(4.5, 8.5);
const x = Math.round(hq.x + Math.cos(ang) * dd);
const y = Math.round(hq.y + Math.sin(ang) * dd);
if (!U.inBounds(x, y, W, H)) continue;
const i = y * W + x;
if (T.terrain[i] === 3) { T.terrain[i] = 1; }
// small patch
for (let dy = -1; dy <= 1; dy++) for (let dx = -1; dx <= 1; dx++) {
const j = (y + dy) * W + (x + dx);
if (!U.inBounds(x + dx, y + dy, W, H)) continue;
if (T.terrain[j] !== 3 && rng() < 0.75 && !T.rock[j]) {
field[j] = fieldMax[j] = field === T.tree ? 8 : 10;
}
}
placed++;
}
};
ensureNear(T.tree, T.treeMax, 4);
ensureNear(T.rock, T.rockMax, 3);
return { tiles: T, hq, seed, rng };
}
return { generate };
})();