- 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)
119 lines
4.9 KiB
JavaScript
119 lines
4.9 KiB
JavaScript
// Generates the ASCII hex map by painting province polygons, prints preview.
|
|
const W = 27, H = 29;
|
|
const grid = Array.from({ length: H }, () => Array(W).fill("."));
|
|
|
|
// point in polygon (hex-center space, col/row floats)
|
|
function pip(x, y, poly) {
|
|
let inside = false;
|
|
for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
|
|
const [xi, yi] = poly[i], [xj, yj] = poly[j];
|
|
if ((yi > y) !== (yj > y) && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi) inside = !inside;
|
|
}
|
|
return inside;
|
|
}
|
|
|
|
// painted in order; later paints overwrite
|
|
const PAINTS = [
|
|
["g", [[1,10],[8,9],[10,12],[10,15],[9,18],[8,22],[6,26],[3,26],[1,20],[0,14]]],
|
|
["n", [[6,11],[14,11],[16,13],[16,17],[14,21],[11,22],[8,20],[6,16]]],
|
|
["h", [[15,11],[21,11],[25,14],[24,19],[19,21],[15,18],[15,13]]],
|
|
["u", [[11,10],[17,10],[18,13],[16,16],[12,16],[10,13]]],
|
|
["x", [[15,9],[21,9],[22,12],[19,15],[16,14],[15,11]]],
|
|
["l", [[0,0],[8,0],[8,4],[6,6],[3,7],[0,6]]],
|
|
["o", [[4,4],[10,4],[11,7],[10,10],[7,11],[5,9],[3,7]]],
|
|
["s", [[7,7],[12,7],[13,10],[11,12],[8,12],[6,9]]],
|
|
["y", [[9,1],[17,1],[18,4],[16,6],[11,6],[9,4]]],
|
|
["j", [[9,4],[17,5],[17,8],[13,9],[10,7],[9,5]]],
|
|
["q", [[15,6],[22,6],[23,9],[20,12],[17,11],[15,9]]],
|
|
["a", [[11,8],[16,8],[17,11],[15,13],[11,13],[10,10]]],
|
|
];
|
|
|
|
for (const [ch, poly] of PAINTS)
|
|
for (let r = 0; r < H; r++)
|
|
for (let c = 0; c < W; c++)
|
|
if (pip(c, r, poly)) grid[r][c] = ch;
|
|
|
|
// mountain ranges (uppercase) — hand bands
|
|
function band(pts, chs) {
|
|
// mark cells near polyline
|
|
for (let r = 0; r < H; r++) for (let c = 0; c < W; c++) {
|
|
if (grid[r][c] === ".") continue;
|
|
let d = 1e9;
|
|
for (let i = 0; i < pts.length - 1; i++) {
|
|
const [x1, y1] = pts[i], [x2, y2] = pts[i + 1];
|
|
const dx = x2 - x1, dy = y2 - y1;
|
|
const t = Math.max(0, Math.min(1, ((c - x1) * dx + (r - y1) * dy) / (dx * dx + dy * dy)));
|
|
d = Math.min(d, Math.hypot(c - (x1 + t * dx), r - (y1 + t * dy)));
|
|
}
|
|
if (d < 0.75 && chs.includes(grid[r][c])) grid[r][c] = grid[r][c].toUpperCase();
|
|
}
|
|
}
|
|
band([[9,4],[11,5],[11,7]], ["j","o"]); // Taihang
|
|
band([[6,9],[9,10],[11,11]], ["o","s","g"]); // Qinling west
|
|
band([[11,12],[13,12],[15,13]], ["s","u","n"]); // Funiu
|
|
band([[2,12],[4,13],[6,15]], ["g"]); // Daba/Shu west
|
|
band([[7,17],[8,20],[7,23]], ["g","n"]); // southern highlands
|
|
band([[1,1],[3,3],[5,5]], ["l"]); // Hexi mountains
|
|
band([[16,1],[17,3],[16,5]], ["y"]); // Yan mountains
|
|
band([[19,6],[21,8]], ["q"]); // Shandong hills
|
|
|
|
console.log(grid.map((row, r) => String(r).padStart(2) + " " + row.join("")).join("\n"));
|
|
|
|
// ---------- bake ----------
|
|
const CITIES = [
|
|
["ji","y",12,2],["beiping","y",15,2],
|
|
["ye","j",12,6],["nanpi","j",15,5],
|
|
["linzi","q",17,7],["beihai","q",21,8],
|
|
["puyang","a",12,9],["chenliu","a",14,10],
|
|
["luoyang","s",9,9],["hongnong","s",8,11],
|
|
["changan","o",7,8],["anding","o",5,6],
|
|
["wuwei","l",2,3],["tianshui","l",4,5],
|
|
["xiaopei","x",16,12],["xiapi","x",19,13],
|
|
["xuchang","u",14,12],["wan","u",12,14],["runan","u",15,14],
|
|
["shouchun","h",18,15],["jianye","h",21,14],["lujiang","h",18,17],["wu","h",21,17],
|
|
["xiangyang","n",10,14],["jiangling","n",11,17],["jiangxia","n",14,15],["changsha","n",11,20],
|
|
["hanzhong","g",4,11],["chengdu","g",3,15],["zitong","g",3,13],["jiangzhou","g",6,17],["yunnan","g",6,22],
|
|
];
|
|
function snap(prov, c, r) {
|
|
let best = null, bd = 1e9;
|
|
for (let rr = 0; rr < H; rr++) for (let cc = 0; cc < W; cc++) {
|
|
if (grid[rr][cc].toLowerCase() !== prov) continue;
|
|
const d = Math.hypot(cc - c, rr - r);
|
|
if (d < bd) { bd = d; best = [cc, rr]; }
|
|
}
|
|
return best;
|
|
}
|
|
console.log("\n=== CITY COORDS ===");
|
|
const used = new Set();
|
|
for (const [id, p, c, r] of CITIES) {
|
|
const [cc, rr] = snap(p, c, r);
|
|
if (!cc && cc !== 0) { console.log(`!! ${id} NO TILE for ${p}`); continue; }
|
|
if (used.has(cc+","+rr)) console.log(`!! overlap ${id} @${cc},${rr}`);
|
|
used.add(cc+","+rr);
|
|
console.log(`["${id}", "${cc}", "${rr}"],`);
|
|
}
|
|
const RIVERS = [
|
|
["Yellow River", [[5,6],[6,7],[7,8],[8,8],[9,9],[10,9],[11,8],[12,7],[13,7],[14,8],[16,8],[18,8],[20,8]]],
|
|
["Yangtze", [[3,15],[4,15],[6,15],[8,15],[10,15],[12,15],[13,15],[15,15],[17,16],[19,16],[21,16],[23,17]]],
|
|
["Han", [[9,12],[9,13],[10,14]]],
|
|
["Huai", [[14,13],[16,13],[18,14],[19,15]]],
|
|
];
|
|
console.log("\n=== RIVERS ===");
|
|
for (const [name, pts] of RIVERS) {
|
|
const out = pts.map(([c, r]) => {
|
|
// nearest land tile
|
|
let best = null, bd = 1e9;
|
|
for (let rr = 0; rr < H; rr++) for (let cc = 0; cc < W; cc++) {
|
|
if (grid[rr][cc] === ".") continue;
|
|
const d = Math.hypot(cc - c, rr - r);
|
|
if (d < bd) { bd = d; best = [cc, rr]; }
|
|
}
|
|
return best;
|
|
});
|
|
console.log(`{ name: "${name}", pts: [${out.map(([c,r])=>`[${c},${r}]`).join(",")}] },`);
|
|
}
|
|
console.log("\n=== MAP_ROWS ===");
|
|
console.log("export const MAP_ROWS = [");
|
|
for (const row of grid) console.log(` "${row.join("")}",`);
|
|
console.log("];");
|