Files
deepseek f040bb6be0 Three Kingdoms: Warlord's Fate — complete playable game
- Stylized 3D ink-painting map of China (12 provinces, 32 cities, 17 factions)
- Custom warlord creation (8 origins, banner, starting city) or historical factions
- City management: 7 buildings, 5 dev tiers, recruitment from levies
- Character system: stats, traits, loyalty, relationships, wounds, capture, death, succession
- Turn-based tactical battles with formations, stances, hero skills, cinematic 3D replay
- Sieges: assault, starvation, bribery, infiltration
- Diplomacy with trust memory, alliances, NAPs, trade, marriage, espionage, betrayal
- Scripted diverging history (Dong Zhuo, Guandu, Red Cliffs...) + world crises + court events
- AI factions with distinct personalities; prisoners (execute/release/recruit/ransom)
- Procedural guqin/taiko WebAudio score; save/load; victory + dynasty chronicle screens
- View-relative camera controls; headless test suites (smoke, stress, map validator)
2026-08-23 06:59:40 +00:00

62 lines
2.7 KiB
JavaScript

// map validation script (node)
import { MAP_ROWS, PROVINCE_LETTERS, CITY_DEFS, hexToWorld, RIVERS } from "../js/data.js";
const rows = MAP_ROWS;
const H = rows.length;
const at = (c, r) => (r < 0 || r >= H || c < 0 || c >= (rows[r]?.length ?? 0)) ? "." : rows[r][c];
const isLand = ch => ch !== ".";
const provOf = ch => isLand(ch) ? ch.toLowerCase() : null;
// neighbors odd-r offset
function nbrs(c, r) {
const odd = r % 2;
return [[c-1,r],[c+1,r],[c+odd-1,r-1],[c+odd,r-1],[c+odd-1,r+1],[c+odd,r+1]];
}
// contiguity per province
for (const P of PROVINCE_LETTERS) {
const cells = [];
for (let r = 0; r < H; r++) for (let c = 0; c < (rows[r].length); c++) if (provOf(rows[r][c]) === P) cells.push([c, r]);
if (!cells.length) { console.log(`!! province ${P} EMPTY`); continue; }
const set = new Set(cells.map(([c, r]) => c + "," + r));
const seen = new Set([cells[0].join(",")]);
const stack = [cells[0]];
while (stack.length) {
const [c, r] = stack.pop();
for (const [nc, nr] of nbrs(c, r)) {
if (provOf(at(nc, nr)) === P && !seen.has(nc + "," + nr)) { seen.add(nc + "," + nr); stack.push([nc, nr]); }
}
}
console.log(`${set.size === seen.size ? "OK " : "SPLIT"} ${P}: ${cells.length} hexes${set.size !== seen.size ? " (" + (set.size - seen.size) + " orphan)" : ""}`);
}
// adjacency between provinces
const adj = new Set();
for (let r = 0; r < H; r++) for (let c = 0; c < rows[r].length; c++) {
const p = provOf(rows[r][c]); if (!p) continue;
for (const [nc, nr] of nbrs(c, r)) { const q = provOf(at(nc, nr)); if (q && q !== p) adj.add([p, q].sort().join("")); }
}
console.log("adjacency:", [...adj].sort().join(" "));
// city checks
let bad = 0;
for (const [id, name, , prov, c, r] of CITY_DEFS) {
const p = provOf(at(c, r));
if (p !== prov) { console.log(`CITY BAD: ${name} wants ${prov} but tile(${c},${r})='${at(c, r)}'`); bad++; }
}
// uniqueness of city tiles
const tiles = new Set();
for (const [id, name, , , c, r] of CITY_DEFS) { const k = c + "," + r; if (tiles.has(k)) { console.log("CITY OVERLAP at", k); bad++; } tiles.add(k); }
console.log(bad ? `${bad} city problems` : `all ${CITY_DEFS.length} cities valid & unique`);
// river points on land?
for (const rv of RIVERS) for (const [c, r] of rv.pts) if (!isLand(at(c, r))) console.log(`river ${rv.name} off-land at ${c},${r}`);
// world extent
let minX = 1e9, maxX = -1e9, minZ = 1e9, maxZ = -1e9;
for (let r = 0; r < H; r++) for (let c = 0; c < rows[r].length; c++) if (isLand(rows[r][c])) {
const [x, z] = hexToWorld(c, r);
minX = Math.min(minX, x); maxX = Math.max(maxX, x); minZ = Math.min(minZ, z); maxZ = Math.max(maxZ, z);
}
console.log(`map world extent x:[${minX.toFixed(0)},${maxX.toFixed(0)}] z:[${minZ.toFixed(0)},${maxZ.toFixed(0)}]`);