summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/gencss.js7
-rw-r--r--tools/gendata.js576
-rw-r--r--tools/gentextures.sh36
-rw-r--r--tools/genunits.js227
-rw-r--r--tools/rock_overlay.pngbin0 -> 44367 bytes
-rw-r--r--tools/rock_overlay@2x.pngbin0 -> 160515 bytes
-rw-r--r--tools/scree_overlay.pngbin0 -> 21813 bytes
-rw-r--r--tools/scree_overlay@2x.pngbin0 -> 72197 bytes
-rw-r--r--tools/wetland.pbmbin0 -> 8203 bytes
9 files changed, 846 insertions, 0 deletions
diff --git a/tools/gencss.js b/tools/gencss.js
new file mode 100644
index 0000000..4981d7c
--- /dev/null
+++ b/tools/gencss.js
@@ -0,0 +1,7 @@
+// let n = 51, u = 'px';
+let n = 100 / 9, u = '%';
+for (let i = 0; i < 94; ++i) {
+ let x = (i % 10) | 0;
+ let y = (i / 10) | 0;
+ console.log(`.unit.u${i}{background-position:${x*n}${u} ${y*n}${u}}`);
+}
diff --git a/tools/gendata.js b/tools/gendata.js
new file mode 100644
index 0000000..fb0348c
--- /dev/null
+++ b/tools/gendata.js
@@ -0,0 +1,576 @@
+// https://www.redblobgames.com/grids/hexagons/
+
+// Clear
+const CLEAR = 2;
+
+// Constricted
+const GAP = 1;
+
+// Rough (impassable)
+const OFFMAP = 0;
+const RIDGE = 0;
+const MARSH = 0;
+const MOUNTAIN = 0;
+
+// Road
+const TRAIL = 1;
+const TRACK = 2;
+const HIGHWAY = 4;
+
+const map_w = 25;
+const map_h = 9;
+
+let next = [ 1, map_w, map_w-1, -1, -map_w, -(map_w-1) ];
+
+let nextdir = [ -1, 1, map_w, map_w-1, -map_w, -(map_w-1) ]
+let nextname = [ "E", "W", "NW", "NE", "SE", "SW" ];
+
+let hex_exists = new Array(map_w*map_h).fill(1);
+let side_limit = new Array(map_w*map_h*3).fill(CLEAR);
+let side_road = new Array(map_w*map_h*3).fill(0);
+let hex_road = new Array(map_w*map_h).fill(0);
+
+function add_side(s, t) {
+ if (side_limit[s])
+ side_limit[s] += "/" + t;
+ else
+ side_limit[s] = t;
+}
+
+function to_side_id(a, b) {
+ if (a > b) {
+ let c = b; b = a; a = c;
+ }
+ if (a + next[0] === b)
+ return a * 3 + 0;
+ else if (a + next[1] === b)
+ return a * 3 + 1;
+ else if (a + next[2] === b)
+ return a * 3 + 2;
+ throw new Error("not a hexside " + a + " to " + b);
+}
+
+function def_side(t,a,b) {
+ side_limit[to_side_id(a,b)] = t;
+}
+
+function def_road(t, list) {
+ for (let i = 0; i < list.length; ++i)
+ hex_road[list[i]] = t;
+ for (let i = 1; i < list.length; ++i) {
+ let s = to_side_id(list[i-1], list[i]);
+ side_road[s] = t;
+ if (side_limit[s] === MOUNTAIN)
+ side_limit[s] = GAP;
+ }
+}
+
+def_side(MARSH, 176, 152);
+def_side(MARSH, 179, 203);
+
+def_side(RIDGE, 144, 145);
+def_side(RIDGE, 120, 145);
+def_side(RIDGE, 121, 145);
+def_side(RIDGE, 121, 146);
+def_side(RIDGE, 122, 146);
+def_side(RIDGE, 144, 169);
+def_side(RIDGE, 144, 168);
+def_side(RIDGE, 143, 168);
+def_side(RIDGE, 167, 168);
+def_side(RIDGE, 167, 192);
+def_side(RIDGE, 191, 192);
+def_side(RIDGE, 191, 216);
+def_side(RIDGE, 215, 216);
+def_side(RIDGE, 217, 218);
+def_side(RIDGE, 217, 193);
+def_side(RIDGE, 192, 193);
+def_side(RIDGE, 168, 193);
+def_side(RIDGE, 168, 169);
+def_side(RIDGE, 69, 94);
+def_side(RIDGE, 67, 92);
+def_side(RIDGE, 68, 92);
+def_side(RIDGE, 68, 93);
+def_side(RIDGE, 65, 40);
+def_side(RIDGE, 65, 64);
+def_side(RIDGE, 65, 89);
+def_side(RIDGE, 65, 90);
+def_side(RIDGE, 66, 90);
+def_side(RIDGE, 66, 91);
+def_side(RIDGE, 39, 63);
+def_side(RIDGE, 37, 38);
+def_side(RIDGE, 36, 37);
+def_side(RIDGE, 36, 61);
+def_side(RIDGE, 36, 60);
+def_side(RIDGE, 109, 133);
+def_side(RIDGE, 108, 133);
+def_side(RIDGE, 204, 179);
+def_side(RIDGE, 204, 180);
+def_side(RIDGE, 205, 180);
+def_side(RIDGE, 31, 30);
+def_side(RIDGE, 31, 55);
+def_side(RIDGE, 55, 54);
+def_side(RIDGE, 78, 79);
+def_side(RIDGE, 30, 55);
+def_side(RIDGE, 54, 79);
+def_side(RIDGE, 103, 79);
+def_side(RIDGE, 176, 200);
+def_side(RIDGE, 176, 201);
+def_side(RIDGE, 177, 152);
+def_side(RIDGE, 177, 153);
+def_side(RIDGE, 178, 153);
+def_side(RIDGE, 153, 154);
+def_side(RIDGE, 129, 154);
+
+def_side(MOUNTAIN, 7, 8);
+def_side(MOUNTAIN, 7, 31);
+def_side(MOUNTAIN, 7, 32);
+def_side(MOUNTAIN, 8, 9);
+def_side(MOUNTAIN, 8, 32);
+def_side(MOUNTAIN, 8, 33);
+def_side(MOUNTAIN, 9, 33);
+def_side(MOUNTAIN, 9, 34);
+def_side(MOUNTAIN, 9, 10);
+def_side(MOUNTAIN, 32, 31);
+def_side(MOUNTAIN, 32, 33);
+def_side(MOUNTAIN, 32, 56);
+def_side(MOUNTAIN, 32, 57);
+def_side(MOUNTAIN, 33, 34);
+def_side(MOUNTAIN, 33, 57);
+def_side(MOUNTAIN, 33, 58);
+def_side(MOUNTAIN, 34, 10);
+def_side(MOUNTAIN, 34, 35);
+def_side(MOUNTAIN, 34, 58);
+def_side(MOUNTAIN, 34, 59);
+def_side(MOUNTAIN, 55, 80);
+def_side(MOUNTAIN, 56, 80);
+def_side(MOUNTAIN, 56, 55);
+def_side(MOUNTAIN, 56, 31);
+def_side(MOUNTAIN, 56, 81);
+def_side(MOUNTAIN, 56, 57);
+def_side(MOUNTAIN, 57, 58);
+
+def_side(GAP, 176, 152);
+def_side(GAP, 65, 40);
+def_side(GAP, 65, 64);
+def_side(GAP, 65, 89);
+def_side(GAP, 37, 38);
+def_side(GAP, 36, 37);
+def_side(GAP, 36, 61);
+def_side(GAP, 109, 133);
+def_side(GAP, 31, 30);
+def_side(GAP, 31, 55);
+def_side(GAP, 55, 54);
+def_side(GAP, 78, 79);
+def_side(GAP, 177, 153);
+def_side(GAP, 178, 153);
+def_side(GAP, 129, 154);
+
+def_road(HIGHWAY, [151,152,128,103,78,54,30,31,7,8,9,10,35,36,37,38,39,40,64,65,66,67,68,69,70,71,72,73,74]);
+def_road(HIGHWAY, [54,55,31]);
+def_road(HIGHWAY, [31,32,33,9]);
+def_road(HIGHWAY, [36,61,62,38]);
+
+def_road(TRACK, [78,79,80,81,82,58,59,35]);
+def_road(TRACK, [128,104,80]);
+def_road(TRACK, [83,59,60,61,86,87,88,89]);
+def_road(TRACK, [37,62,87]);
+def_road(TRACK, [64,89,113]);
+def_road(TRACK, [62,63,64]);
+def_road(TRACK, [213,189,165,141,117,93,69]);
+def_road(TRACK, [72,97,121]);
+
+def_road(TRAIL, [152,153,178,203,204,205,181,182,183,184,185,186,187]);
+def_road(TRAIL, [153,128,129,104,103]);
+def_road(TRAIL, [79,55]);
+def_road(TRAIL, [55,56,57,58,34,10]);
+def_road(TRAIL, [58,83,107,106,130,129]);
+def_road(TRAIL, [35,60,85,61]);
+def_road(TRAIL, [85,86]);
+def_road(TRAIL, [85,109,133,157,181]);
+def_road(TRAIL, [64,88]);
+def_road(TRAIL, [88,113,138,162,187]);
+def_road(TRAIL, [87,112,137,162]);
+def_road(TRAIL, [65,89,114,139,163,188,213]);
+def_road(TRAIL, [67,91,115,140,164,189]);
+
+function def_offmap(a, b) {
+ for (let hex_id = 0; hex_id < map_w * map_h; ++hex_id) {
+ if (hex_id >= a && hex_id <= b) {
+ hex_exists[hex_id] = 0;
+ side_limit[hex_id * 3 + 0] = OFFMAP;
+ side_limit[hex_id * 3 + 1] = OFFMAP;
+ side_limit[hex_id * 3 + 2] = OFFMAP;
+ }
+ for (let s = 0; s < 3; ++s) {
+ let next_id = hex_id + next[s];
+ if (next_id >= a && next_id <= b)
+ side_limit[hex_id * 3 + s] = OFFMAP;
+ }
+ }
+}
+
+def_offmap(0,6);
+def_offmap(11,29);
+def_offmap(41,53);
+def_offmap(75,77);
+def_offmap(99,102);
+def_offmap(124,127);
+def_offmap(148,150);
+def_offmap(173,175);
+def_offmap(197,199);
+def_offmap(222,224);
+
+// show spines
+def_offmap(175,175, 1);
+def_offmap(99,99, 1);
+def_offmap(148,148, 1);
+def_offmap(197,197, 1);
+
+// depression
+def_offmap(168,168);
+def_offmap(168,168);
+def_offmap(192,192);
+def_offmap(193,193);
+def_offmap(216,217);
+
+// Hexes not in 2004 edition:
+// def_offmap(200,202);
+// def_offmap(206,212);
+// def_offmap(214,221);
+
+// Terrain chart hexes
+// def_offmap(145,146);
+// def_offmap(168,172);
+// def_offmap(192,196);
+// def_offmap(216,221);
+
+// Qattara Depression and east of it
+def_offmap(145,148);
+def_offmap(168,172);
+def_offmap(192,196);
+def_offmap(216,221);
+
+let regions = {};
+function def_region(name, a, b) {
+ if (!(name in regions))
+ regions[name] = [];
+ for (let i = a; i <= b; ++i)
+ regions[name].push(i);
+}
+
+def_region("Libya", 7, 10);
+def_region("Libya", 30, 40);
+def_region("Libya", 54, 64);
+def_region("Libya", 78, 88);
+def_region("Libya", 103, 113);
+def_region("Libya", 128, 138);
+def_region("Libya", 151, 162);
+def_region("Libya", 176, 187);
+def_region("Libya", 200, 211);
+
+def_region("Egypt", 65, 74);
+def_region("Egypt", 89, 98);
+def_region("Egypt", 114, 123);
+def_region("Egypt", 139, 144);
+def_region("Egypt", 163, 167);
+def_region("Egypt", 188, 191);
+def_region("Egypt", 212, 215);
+
+def_region("Sidi Omar", 89, 89);
+def_region("Tobruk", 37, 37);
+def_region("Sollum", 65, 65);
+def_region("El Agheila", 151, 151);
+def_region("Mersa Brega", 152, 152);
+
+def_region("East Line", 36, 40);
+def_region("East Line", 60, 74);
+def_region("East Line", 85, 98);
+def_region("East Line", 112, 123);
+def_region("East Line", 137, 144);
+def_region("East Line", 162, 167);
+def_region("East Line", 187, 191);
+
+def_region("West Line", 7, 10);
+def_region("West Line", 30, 35);
+def_region("West Line", 54, 59);
+def_region("West Line", 78, 83);
+def_region("West Line", 103, 107);
+def_region("West Line", 128, 130);
+
+def_region("Jebel el Akhdar", 7, 9);
+def_region("Jebel el Akhdar", 32, 34);
+def_region("Jebel el Akhdar", 56, 57);
+
+def_region("Sebkha el Segira", 152, 152);
+def_region("Sebkha el Segira", 176, 176);
+def_region("Sebket el Jeneinen", 179, 179);
+def_region("Sebket el Jeneinen", 203, 203);
+
+def_region("Qattara Depression", 144, 146);
+def_region("Qattara Depression", 168, 169);
+def_region("Qattara Depression", 192, 193);
+def_region("Qattara Depression", 216, 217);
+
+hex_name = {
+ 7: "El Garib",
+ 8: "Cyrene",
+ 9: "El Gubba",
+ 10: "Derna",
+
+ 30: "Tocra",
+ 31: "Barce",
+ 32: "Marawa",
+ 33: "Wadi Cuff",
+ 35: "Gazala",
+ 36: "Acroma",
+ 37: "Tobruk",
+ 38: "Belhamed",
+ 39: "Gambut",
+ 40: "Bardia",
+
+ 54: "Benghazi",
+ 55: "Er Regima",
+ 56: "Charruba",
+ 58: "Mechili",
+ 59: "Rotonda Segnali",
+ 60: "Sidi Mufta",
+ 61: "Bir Harmat",
+ 62: "El Adem",
+ 63: "Sidi Rezegh",
+ 64: "Ft. Capuzzo",
+ 65: "Sollum",
+ 66: "Buq Buq",
+ 67: "Sidi Barrani",
+ 69: "Mersa Matruh",
+ 70: "Fuka",
+ 71: "El Daba",
+ 72: "El Alamein",
+ 73: "El Hamam",
+ 74: "Alexandria",
+
+ 78: "Ghemines",
+ 79: "Skeleidima",
+ 80: "Msus",
+ 83: "Tengeder",
+ 85: "Bir Hacheim",
+ 86: "Retma",
+ 87: "Bir Gubi",
+ 88: "Gabr Saleh",
+ 89: "Sidi Omar",
+ 90: "Bir Habata",
+ 91: "Sofafi",
+ 93: "Bir el Kenayis",
+ 94: "Bir Khalda",
+ 97: "Alam Halfa",
+
+ 103: "Beda Fomm",
+ 104: "Antelat",
+ 106: "Ben Gania",
+ 113: "Ft. Maddalena",
+ 115: "Bir Khamsa",
+ 116: "Bir el Qatrani",
+ 121: "El Himeimat",
+
+ 128: "Agedabia",
+ 129: "Jebel el Matar",
+ 130: "Haraga",
+ 141: "Bir Fuad",
+
+ 151: "El Agheila",
+ 152: "Mersa Brega",
+ 153: "El Haseiat",
+
+ 176: "Maaten Giofer",
+ 178: "Sahaba",
+ 187: "Jarabub Oasis",
+
+ 204: "Jalo Oasis",
+ 213: "Siwa Oasis",
+}
+
+
+function expand_names() {
+ let hex_name2 = { }
+ for (let hex_id = 0; hex_id < map_w * map_h; ++hex_id) {
+ if (hex_exists[hex_id] && !hex_name[hex_id]) {
+ for (let s = 0; s < 6; ++s) {
+ let next_id = hex_id + nextdir[s];
+ if (hex_name[next_id]) {
+ hex_name2[hex_id] = hex_name[next_id] + " " + nextname[s];
+ break;
+ }
+ }
+ }
+ }
+ for (let hex_id in hex_name2)
+ hex_name[hex_id] = hex_name2[hex_id];
+}
+
+expand_names();
+
+let units = [];
+
+const class_from_type = {
+ "recon": "armor",
+ "armor": "armor",
+ "i-tank": "armor",
+ "infantry": "infantry",
+ "mech. inf.": "infantry",
+ "mot. inf.": "infantry",
+ "para": "infantry",
+ "mob. a/t": "antitank",
+ "mot. a/t": "antitank",
+ "self prop. arty": "artillery",
+ "artillery": "artillery",
+}
+
+const speed_from_type = {
+ "recon": 4,
+ "armor": 3,
+ "i-tank": 3, // optional rule: allied i-tanks set this to 2
+ "mech. inf.": 3,
+ "mob. a/t": 3,
+ "self prop. arty": 3,
+ "mot. inf.": 2,
+ "mot. a/t": 2,
+ "infantry": 1,
+ "para": 1,
+ "artillery": 1,
+}
+
+let unit_map = {};
+
+function def_block(nationality, type, appearance, steps, elite, label, name) {
+ let type_class = class_from_type[type];
+ let speed = speed_from_type[type];
+ if (name in unit_map)
+ throw new Error("duplicate block name:", name);
+ unit_map[name] = units.length;
+ units.push({nationality, type, class: type_class, speed, appearance, steps, elite, label, name});
+}
+
+let S = "S";
+let M = "M";
+let T = "T";
+
+//def_block("italian", battle block 1);
+def_block("italian", "infantry", S, 4, 0, 12, "Tre");
+def_block("italian", "infantry", S, 4, 0, 13, "Pav");
+def_block("italian", "infantry", S, 4, 0, 14, "Bre");
+def_block("italian", "infantry", S, 3, 0, 15, "Bol");
+def_block("italian", "infantry", S, 3, 0, 16, "Sav");
+
+def_block("italian", "mech. inf.", 7, 4, 0, 21, "Tri");
+def_block("italian", "infantry", 7, 2, 0, 22, "Sab");
+def_block("italian", "armor", 11, 3, 0, 23, "Lit");
+def_block("italian", "mot. inf.", 17, 3, 0, 24, "Fas");
+def_block("italian", "mot. inf.", 19, 3, 0, 25, "Cen");
+def_block("italian", "infantry", 19, 2, 0, 26, "Pis");
+def_block("italian", "para", M, 4, 0, 27, "Fol");
+def_block("italian", "artillery", S, 2, 0, 28, "Ita");
+def_block("italian", "armor", S, 4, 0, 29, "Ari");
+
+//def_block("german", battle block 1);
+//def_block("german", battle block 2);
+//def_block("german", battle block 3);
+def_block("german", "armor", S, 3, 1, 14, "21/5");
+def_block("german", "recon", S, 1, 1, 15, "21/3");
+def_block("german", "mech. inf.", S, 3, 1, 16, "21/104");
+def_block("german", "recon", 3, 1, 1, 17, "15/33");
+def_block("german", "mob. a/t", 3, 2, 1, 18, "88mm/A");
+def_block("german", "mech. inf.", 3, 3, 1, 19, "15/115");
+
+def_block("german", "armor", 5, 3, 1, 21, "15/8");
+def_block("german", "recon", 5, 1, 1, 22, "90/580");
+def_block("german", "mot. inf.", 5, 2, 1, 23, "90/361");
+def_block("german", "mot. a/t", 5, 2, 1, 24, "50mm");
+def_block("german", "artillery", 7, 2, 1, 25, "/104");
+def_block("german", "mot. inf.", 7, 2, 1, 26, "90/200");
+def_block("german", "mob. a/t", 11, 2, 1, 27, "88mm/B");
+def_block("german", "mech. inf.", 11, 2, 1, 28, "90/sv288");
+def_block("german", "mech. inf.", 11, 2, 1, 29, "90/346");
+
+def_block("german", "para", M, 3, 1, 31, "Ram");
+def_block("german", "infantry", M, 3, 1, 32, "164/382+433");
+def_block("german", "infantry", M, 2, 1, 33, "164/125");
+def_block("german", "mech. inf.", 3, 3, 1, 34, "90/155");
+def_block("german", "mot. a/t", 17, 2, 1, 35, "76mm");
+
+// def_block("allied", bb 1
+// def_block("allied", bb 2
+// def_block("allied", bb 3
+def_block("allied", "armor", S, 2, 0, 14, "2/3");
+def_block("allied", "mech. inf.", S, 2, 0, 15, "2/SG");
+def_block("allied", "mech. inf.", S, 2, 0, 16, "4IN/3m");
+def_block("allied", "mot. inf.", S, 4, 0, 17, "9AU/20");
+def_block("allied", "mot. inf.", S, 4, 0, 18, "70/14+16");
+def_block("allied", "mot. inf.", S, 3, 0, 19, "70/23");
+
+def_block("allied", "armor", T, 1, 1, 21, "Matilda/A");
+def_block("allied", "mech. inf.", T, 3, 0, 22, "7/SG");
+def_block("allied", "mech. inf.", T, 3, 0, 23, "7/22G");
+def_block("allied", "mot. inf.", T, 3, 0, 24, "/Pol");
+def_block("allied", "mot. inf.", T, 3, 0, 25, "7AU/18");
+def_block("allied", "artillery", T, 2, 0, 26, "/Tob");
+def_block("allied", "mot. inf.", 18, 4, 0, 27, "51H/152");
+def_block("allied", "self prop. arty", 18, 2, 0, 28, "Priest");
+def_block("allied", "artillery", 18, 4, 0, 29, "/C");
+
+def_block("allied", "armor", 2, 4, 0, 31, "7/7");
+def_block("allied", "mech. inf.", 2, 3, 0, 32, "4IN/7m");
+def_block("allied", "mot. inf.", 2, 3, 0, 33, "4IN/5");
+def_block("allied", "mot. inf.", 2, 3, 0, 34, "4IN/11");
+def_block("allied", "armor", 4, 1, 1, 35, "Matilda/B");
+def_block("allied", "i-tank", 4, 4, 0, 36, "/1AT");
+def_block("allied", "armor", 4, 3, 0, 37, "7/4");
+def_block("allied", "recon", 4, 2, 0, 38, "7");
+def_block("allied", "artillery", 20, 3, 0, 39, "/D");
+
+def_block("allied", "mot. a/t", 6, 3, 0, 41, "2#");
+def_block("allied", "mech. inf.", 6, 4, 0, 42, "2NZ/4");
+def_block("allied", "mech. inf.", 6, 4, 0, 43, "2NZ/5");
+def_block("allied", "mech. inf.", 6, 4, 0, 44, "2NZ/6");
+def_block("allied", "mot. inf.", 6, 4, 0, 45, "1SA/2+5");
+def_block("allied", "mot. inf.", 6, 3, 0, 46, "1SA/1");
+def_block("allied", "recon", 8, 2, 0, 47, "1SA");
+def_block("allied", "mot. inf.", 8, 3, 0, 48, "1SA/3");
+def_block("allied", "mot. inf.", 8, 3, 0, 49, "2SA/4+6");
+
+def_block("allied", "armor", 8, 4, 0, 51, "1/22");
+def_block("allied", "i-tank", 8, 3, 0, 52, "/32AT");
+def_block("allied", "artillery", 8, 2, 0, 53, "/A");
+def_block("allied", "armor", 10, 4, 0, 54, "1/2");
+def_block("allied", "mech. inf.", 10, 3, 0, 55, "1/201G");
+def_block("allied", "mech. inf.", 10, 2, 0, 56, "1/SG");
+def_block("allied", "mot. inf.", 10, 3, 0, 57, "5IN/29");
+def_block("allied", "armor", 12, 2, 1, 58, "Grant");
+def_block("allied", "mot. a/t", 12, 3, 0, 59, "6#/A");
+
+def_block("allied", "mech. inf.", 14, 2, 0, 61, "10IN/161m");
+def_block("allied", "mot. inf.", 14, 4, 0, 62, "5IN/9+10");
+def_block("allied", "mot. inf.", 14, 4, 0, 63, "10IN/21+25");
+def_block("allied", "mot. inf.", 14, 2, 0, 64, "8IN/18");
+def_block("allied", "mot. inf.", 14, 4, 0, 65, "FF/2");
+def_block("allied", "artillery", 14, 4, 0, 66, "/B");
+def_block("allied", "armor", 20, 3, 1, 67, "Sher/B");
+def_block("allied", "armor", 20, 3, 0, 68, "8/9");
+def_block("allied", "armor", 20, 3, 0, 69, "8/24");
+
+def_block("allied", "armor", 16, 4, 0, 71, "10/8");
+def_block("allied", "armor", 16, 3, 0, 72, "10/23");
+def_block("allied", "mot. inf.", 16, 4, 0, 73, "9AU/26");
+def_block("allied", "mot. inf.", 16, 4, 0, 74, "9AU/24");
+def_block("allied", "mot. inf.", 16, 4, 0, 75, "44/131+133");
+def_block("allied", "mot. inf.", 16, 2, 0, 76, "44/132");
+def_block("allied", "armor", 18, 3, 1, 77, "Sher/A");
+def_block("allied", "mot. a/t", 18, 4, 0, 78, "6#/B");
+def_block("allied", "mot. inf.", 18, 4, 0, 79, "51H/154");
+
+console.log("const hex_exists = " + JSON.stringify(hex_exists));
+console.log("const hex_road = " + JSON.stringify(hex_road));
+console.log("const side_road = " + JSON.stringify(side_road));
+console.log("const side_limit = " + JSON.stringify(side_limit));
+console.log("const hex_name = " + JSON.stringify(hex_name));
+console.log("const regions = " + JSON.stringify(regions));
+console.log("const units = " + JSON.stringify(units));
+console.log("if (typeof module !== 'undefined') module.exports = { hex_name, hex_road, side_road, side_limit, regions, units }");
diff --git a/tools/gentextures.sh b/tools/gentextures.sh
new file mode 100644
index 0000000..cacd968
--- /dev/null
+++ b/tools/gentextures.sh
@@ -0,0 +1,36 @@
+#!/bin/bash
+
+# 2022
+# depression: c4ab8b b39e83
+# depression (old): c4bd9c
+# marsh: 939874 747e5c
+# mountain: b3b37d 9a9a6e
+# desert: ccbda4 d6c5aa
+
+cat tools/wetland.pbm | pgmtoppm "#747e5c-#939874" | ppmquant 2 | pnmtopng > texture_marsh.png
+#pngtopnm tools/scree_overlay@2x.png | pgmtoppm "black-#c4ab8b" | ppmquant 2 | pnmtopng > texture_scree.png
+pngtopnm tools/scree_overlay@2x.png | pgmtoppm "black-#c4bd9c" | ppmquant 2 | pnmtopng > texture_scree.png
+#pngtopnm tools/rock_overlay@2x.png | pnmnorm | pgmtoppm "#aaa26f-#bab079" | ppmquant 2 | pnmtopng > texture_mountain.png
+pngtopnm tools/rock_overlay@2x.png | pnmnorm | pgmtoppm "#9d9d6a-#aaaa77" | ppmquant 2 | pnmtopng > texture_mountain.png
+pngtopnm tools/rock_overlay@2x.png | pnmnorm | pgmtoppm "#ccbda4-#d6c5aa" | ppmquant 2 | pnmtopng > texture_clear.png
+pngtopnm tools/rock_overlay@2x.png | pnmnorm | pgmtoppm "#bba487-#c4ab8b" | ppmquant 2 | pnmtopng > texture_depression.png
+
+for PNG in texture_*.png
+do
+ zopflipng -y $PNG $PNG
+done
+
+exit 0
+
+OUT=textures.svg
+rm -f $OUT
+for PNG in texture_*.png
+do
+SIZE=$(file $PNG | sed 's/[^0-9]*//;s/ .*//')
+echo '<pattern id="'${PNG/.png/}'" width="'$SIZE'" height="'$SIZE'" patternUnits="userSpaceOnUse">' >> $OUT
+echo '<image width="'$SIZE'" height="'$SIZE'" xlink:href="data:image/png;base64,' >> $OUT
+base64 $PNG >> $OUT
+echo '"/>' >> $OUT
+echo '"</pattern>' >> $OUT
+echo >> $OUT
+done
diff --git a/tools/genunits.js b/tools/genunits.js
new file mode 100644
index 0000000..4e0f30e
--- /dev/null
+++ b/tools/genunits.js
@@ -0,0 +1,227 @@
+const { units } = require("../data.js");
+const print = console.log;
+
+print(`<svg xmlns="http://www.w3.org/2000/svg" width="510" height="510" version="1.2" viewBox="0 0 510 510">
+<defs>
+
+<symbol id="font_1" overflow="visible">
+<path transform="scale(1,-1) translate(-.3333333 0)" d="M .49169923 .7285156 L .49169923 0 L .29052735 0 L .29052735 .47642518 C .25797526 .45183818 .22648113 .4319407 .19604492 .4167328 C .16560872 .40152488 .1274414 .3869629 .08154297 .37304688 L .08154297 .53515627 C .1492513 .5571289 .20182292 .5835317 .23925781 .6143646 C .27669273 .6451976 .3059896 .68324789 .32714845 .7285156 L .49169923 .7285156 Z "/>
+</symbol>
+
+<symbol id="font_2" overflow="visible">
+<path transform="scale(1,-1) translate(-.3333333 0)" d="M .6230469 0 L .026367188 0 C .033203126 .05891927 .053955079 .114339198 .08862305 .16625977 C .123291019 .21818035 .1883138 .27945964 .2836914 .35009767 C .34195964 .39339195 .37923179 .42626954 .3955078 .44873048 C .41178385 .4711914 .41992188 .49251304 .41992188 .5126953 C .41992188 .5345052 .41186524 .5531413 .39575196 .5686035 C .37963868 .58406576 .359375 .5917969 .33496095 .5917969 C .3095703 .5917969 .28881837 .5838928 .27270509 .5680847 C .2565918 .5522766 .24576824 .52437338 .24023438 .484375 L .041015626 .5003357 C .048828126 .5562439 .06315104 .59987899 .083984378 .63124087 C .10481771 .6626129 .13419597 .68666586 .17211914 .70339968 C .21004232 .7201436 .26253257 .7285156 .32958985 .7285156 C .3995768 .7285156 .45402018 .7205302 .49291993 .7045593 C .53181967 .68858847 .56241866 .6640625 .5847168 .63098147 C .60701498 .5979004 .61816409 .5608266 .61816409 .51976016 C .61816409 .47608949 .6053874 .43437196 .579834 .39460755 C .5542806 .35484315 .5078125 .3111979 .4404297 .26367188 C .40039063 .23600261 .37361656 .21663411 .36010743 .2055664 C .3465983 .1944987 .33072917 .18001302 .3125 .16210938 L .6230469 .16210938 L .6230469 0 Z "/>
+</symbol>
+
+<symbol id="font_3" overflow="visible">
+<path transform="scale(1,-1) translate(-.3333333 0)" d="M .23388672 .5097656 L .045898439 .5431061 C .061523439 .60296127 .091552738 .64882406 .13598633 .6806946 C .18041992 .71257528 .24332683 .7285156 .32470704 .7285156 C .4181315 .7285156 .4856771 .7110494 .52734377 .67611697 C .56901046 .6411845 .58984377 .5972748 .58984377 .5443878 C .58984377 .51337179 .5813802 .48529054 .5644531 .46014405 C .54752609 .43500773 .52197268 .41297404 .48779298 .39404298 C .5154622 .38720704 .5366211 .37923179 .55126956 .3701172 C .57503256 .35546876 .59350588 .33618165 .60668948 .31225587 C .61987307 .28833009 .62646487 .25976563 .62646487 .2265625 C .62646487 .18489583 .6155599 .14493816 .59375 .10668945 C .5719401 .06844076 .54052737 .03898112 .49951173 .018310547 C .4584961 -.0023600262 .4046224 -.0126953129 .33789063 -.0126953129 C .27278648 -.0126953129 .22143555 -.0050455729 .18383789 .010253906 C .14624024 .025553385 .11531576 .04793294 .09106445 .07739258 C .06681315 .10685221 .048177083 .1438802 .03515625 .18847656 L .23388672 .21484375 C .24169922 .17480469 .25382487 .14705403 .27026368 .1315918 C .28670249 .116129558 .3076172 .10839844 .3330078 .10839844 C .35970054 .10839844 .38191734 .11816406 .3996582 .13769531 C .41739909 .15722656 .42626954 .18326824 .42626954 .21582031 C .42626954 .24902344 .4177246 .2747396 .40063478 .29296876 C .38354493 .3111979 .36035157 .3203125 .3310547 .3203125 C .3154297 .3203125 .2939453 .31640626 .26660157 .30859376 L .27685548 .45069886 C .2879232 .44906108 .29654948 .4482422 .30273438 .4482422 C .32877604 .4482422 .35050456 .4565684 .36791993 .47322084 C .3853353 .48987327 .39404298 .5096283 .39404298 .53248599 C .39404298 .55436709 .38753257 .5718384 .37451173 .5848999 C .36149089 .5979614 .34358726 .6044922 .32080079 .6044922 C .29736329 .6044922 .2783203 .59748336 .26367188 .5834656 C .24902344 .5694478 .23909505 .54488119 .23388672 .5097656 Z "/>
+</symbol>
+
+<symbol id="font_4" overflow="visible">
+<path transform="scale(1,-1) translate(-.3333333 0)" d="M .3828125 .13378906 L .020507813 .13378906 L .020507813 .2975006 L .3828125 .7285156 L .55615237 .7285156 L .55615237 .28808595 L .6459961 .28808595 L .6459961 .13378906 L .55615237 .13378906 L .55615237 0 L .3828125 0 L .3828125 .13378906 M .3828125 .28808595 L .3828125 .5143585 L .19140625 .28808595 L .3828125 .28808595 Z "/>
+</symbol>
+
+<symbol id="font_6" overflow="visible">
+<path transform="scale(1,-1) translate(-.3333333 0)" d="M .6166992 .5548401 L .4189453 .53027346 C .41373698 .5581258 .4050293 .57778427 .39282228 .58924868 C .38061524 .6007131 .3655599 .6064453 .34765626 .6064453 C .3154297 .6064453 .2903646 .5901286 .27246095 .5574951 C .2594401 .5340068 .24983724 .4837545 .24365235 .40673829 C .26741538 .4308268 .29182945 .44864909 .31689454 .46020509 C .34195964 .47176109 .370931 .47753907 .4038086 .47753907 C .4676107 .47753907 .52156576 .45472718 .5656738 .4091034 C .6097819 .36348979 .63183596 .3056539 .63183596 .2355957 C .63183596 .18835449 .6206868 .1451823 .5983887 .1060791 C .5760905 .066975917 .545166 .037404378 .50561526 .017364502 C .46606446 -.0026753743 .4165039 -.0126953129 .3569336 -.0126953129 C .285319 -.0126953129 .22786458 -.00047302247 .18457031 .023971558 C .14127605 .048416139 .10668945 .08744812 .08081055 .1410675 C .05493164 .19468689 .041992189 .26566569 .041992189 .3540039 C .041992189 .48339845 .06917318 .5781657 .123535159 .63830569 C .17789714 .6984456 .25325523 .7285156 .34960938 .7285156 C .40657554 .7285156 .45157878 .72190859 .48461915 .70869448 C .5176595 .69549056 .54508468 .6761678 .56689456 .6507263 C .5887044 .62528488 .60530599 .59332278 .6166992 .5548401 M .25048829 .2355957 C .25048829 .19678752 .2602539 .16637166 .27978517 .14434815 C .2993164 .1223348 .3232422 .111328128 .3515625 .111328128 C .37760417 .111328128 .39941407 .121276859 .4169922 .14117432 C .4345703 .16107178 .44335938 .1907552 .44335938 .23022461 C .44335938 .27067057 .43424479 .30149333 .41601563 .32269288 C .39778648 .34389244 .37516276 .3544922 .34814454 .3544922 C .32047526 .3544922 .2972819 .34421794 .27856446 .32366944 C .25984703 .30312095 .25048829 .27376304 .25048829 .2355957 Z "/>
+</symbol>
+
+<symbol id="cv4">
+<use href="#font_4" transform="translate(25.5,11) rotate(0) scale(12)"/>
+<use href="#font_3" transform="translate(40,25.5) rotate(90) scale(12)"/>
+<use href="#font_2" transform="translate(25.5,40) rotate(180) scale(12)"/>
+<use href="#font_1" transform="translate(11,25.5) rotate(-90) scale(12)"/>
+</symbol>
+
+<symbol id="cv3">
+<use href="#font_3" transform="translate(25.5,11) rotate(0) scale(12)"/>
+<use href="#font_2" transform="translate(40,25.5) rotate(90) scale(12)"/>
+<use href="#font_1" transform="translate(25.5,40) rotate(180) scale(12)"/>
+</symbol>
+
+<symbol id="cv2">
+<use href="#font_2" transform="translate(25.5,11) rotate(0) scale(12)"/>
+<use href="#font_1" transform="translate(40,25.5) rotate(90) scale(12)"/>
+</symbol>
+
+<symbol id="cv1">
+<use href="#font_1" transform="translate(25.5,11) rotate(0) scale(12)"/>
+</symbol>
+
+<symbol id="cv3e">
+<use href="#font_6" transform="translate(25.5,11) rotate(0) scale(12)"/>
+<use href="#font_4" transform="translate(40,25.5) rotate(90) scale(12)"/>
+<use href="#font_2" transform="translate(25.5,40) rotate(180) scale(12)"/>
+</symbol>
+
+<symbol id="cv2e">
+<use href="#font_4" transform="translate(25.5,11) rotate(0) scale(12)"/>
+<use href="#font_2" transform="translate(40,25.5) rotate(90) scale(12)"/>
+</symbol>
+
+<symbol id="cv1e">
+<use href="#font_2" transform="translate(25.5,11) rotate(0) scale(12)"/>
+</symbol>
+
+</defs>
+<g font-family="Arial,Helvetica,sans-serif" font-weight="bold">`);
+
+SYMBOLS = {
+
+"armor": `
+<path fill="#ccc" stroke="#111" stroke-linejoin="round" stroke-width="1.5" d="M38.3 38.3H12.8V12.9h25.5zm0 0"/>
+<path fill="none" stroke="#111" stroke-width="1.5" d="M33.8 25.4c0 2.7-2.4 5-5.4 5h-5.7c-3 0-5.5-2.3-5.5-5s2.5-4.9 5.5-4.9h5.7c3 0 5.4 2.2 5.4 5zm0 0"/>
+`,
+
+"armor_elite": `
+<path fill="#ccc" stroke="#111" stroke-linejoin="round" stroke-width="1.5" d="M38.3 38.3H12.8V12.9h25.5zm0 0"/>
+<path fill="#a08672" stroke="#111" stroke-width="1.5" d="M33.8 25.4c0 2.7-2.4 5-5.4 5h-5.7c-3 0-5.5-2.3-5.5-5s2.5-4.9 5.5-4.9h5.7c3 0 5.4 2.2 5.4 5zm0 0"/>
+`,
+
+"armor_itank": `
+<path fill="#ccc" stroke="#111" stroke-linejoin="round" stroke-width="1.5" d="M38.3 38.3H12.8V12.9h25.5zm0 0"/>
+<path fill="none" stroke="#111" stroke-width="1.5" d="M34.1 25.4c0 2.7-2.4 4.9-5.4 4.9H23c-3 0-5.5-2.2-5.5-5 0-2.6 2.4-4.8 5.5-4.8h5.7c3 0 5.4 2.2 5.4 4.9zm0 0"/>
+<path fill="#111" d="M27.3 28.3h-3.4v-1h1v-3.8h-1v-1h3.4v1h-1v3.7h1zm0 0"/>
+`,
+
+"artillery": `
+<path fill="#ccc" stroke="#111" stroke-linejoin="round" stroke-width="1.5" d="M38.3 38.3H12.8V12.9h25.5zm0 0"/>
+<path fill="#111" d="M29.8 25.6c0 2.3-2 4.2-4.3 4.2a4.2 4.2 0 0 1-4.2-4.2 4.2 4.2 0 0 1 4.2-4.2c2.4 0 4.3 1.8 4.3 4.2"/>
+`,
+
+"infantry": `
+<path fill="#ccc" stroke="#111" stroke-linejoin="round" stroke-width="1.5" d="M38.3 38.3H12.8V12.9h25.5zm0 0"/>
+<path fill="none" stroke="#111" stroke-width="1.5" d="M12.8 12.8 38.3 38M38.2 13l-25.4 25"/>
+`,
+
+"mechanized_infantry": `
+<path fill="#ccc" stroke="#111" stroke-linejoin="round" stroke-width="1.5" d="M38.3 38.3H12.8V12.9h25.5zm0 0"/>
+<path fill="none" stroke="#111" stroke-width="1.5" d="M12.8 12.8 38.3 38M38.2 13l-25.4 25m21-12.5c0 2.7-2.4 5-5.4 5h-5.7c-3 0-5.5-2.3-5.5-5s2.5-4.9 5.5-4.9h5.7c3 0 5.4 2.2 5.4 5zm0 0"/>
+`,
+
+"mobile_antitank": `
+<path fill="#ccc" stroke="#111" stroke-linejoin="round" stroke-width="1.5" d="M38.3 38.3H12.8V12.9h25.5zm0 0"/>
+<path fill="none" stroke="#111" stroke-width="1.5" d="M33.8 25.4c0 2.7-2.4 5-5.4 5h-5.7c-3 0-5.5-2.3-5.5-5s2.5-4.9 5.5-4.9h5.7c3 0 5.4 2.2 5.4 5zm0 0"/>
+<path fill="#fff" stroke="#111" stroke-width="1.5" d="m25.9 13-9.6 24.9m9.3-25.1 9.1 25.4"/>
+`,
+
+"motorized_antitank_new": `
+<path fill="#ccc" stroke="#111" stroke-linejoin="round" stroke-width="1.5" d="M38.3 38.3H12.8V12.9h25.5zm0 0"/>
+<path fill="#fff" stroke="#111" stroke-width="1.5" d="m25.9 13-9.6 24.9m9.3-25.1 9.1 25.4"/>
+<path fill="#111" d="M23.8 34.8c0 1.3-1 2.3-2.4 2.3-1.3 0-2.4-1-2.4-2.3s1-2.3 2.4-2.3c1.3 0 2.4 1 2.4 2.3m8.2 0c0 1.3-1 2.3-2.4 2.3-1.3 0-2.4-1-2.4-2.3s1-2.3 2.4-2.3c1.3 0 2.4 1 2.4 2.3"/>
+`,
+
+"motorized_antitank_old": `
+<path fill="#ccc" stroke="#111" stroke-linejoin="round" stroke-width="1.5" d="M38.3 38.3H12.8V12.9h25.5zm0 0"/>
+<path fill="#fff" stroke="#111" stroke-width="1.5" d="m25.9 13-9.6 24.9m9.3-25.1 9.1 25.4"/>
+`,
+
+"motorized_infantry": `
+<path fill="#ccc" stroke="#111" stroke-linejoin="round" stroke-width="1.5" d="M38.3 38.3H12.8V12.9h25.5zm0 0"/>
+<path fill="none" stroke="#111" stroke-width="1.5" d="M12.8 12.8 38.3 38M38.2 13l-25.4 25"/>
+<path fill="#111" d="M23.7 34.7c0 1.3-1 2.4-2.4 2.4-1.3 0-2.4-1-2.4-2.4s1-2.3 2.4-2.3c1.3 0 2.4 1 2.4 2.3m8.2 0c0 1.3-1 2.4-2.4 2.4a2.3 2.3 0 1 1 0-4.7c1.4 0 2.4 1 2.4 2.3"/>
+`,
+
+"paratroopers": `
+<path fill="#ccc" stroke="#111" stroke-linejoin="round" stroke-width="1.5" d="M38.3 38.3H12.8V12.9h25.5zm0 0"/>
+<path fill="none" stroke="#111" stroke-width="1.5" d="M12.8 12.8 38.3 38M38.2 13l-25.4 25m12.6-1.5v-2c0-1 1-1.8 2.2-1.8 1.2 0 2.2.9 2.2 1.9v1.9m-8.8 0v-2c0-1 1-1.8 2.2-1.8 1.2 0 2.2.9 2.2 1.9v1.9"/>
+`,
+
+"recon": `
+<path fill="#ccc" stroke="#111" stroke-linejoin="round" stroke-width="1.5" d="M38.3 38.3H12.8V12.9h25.5zm0 0"/>
+<path fill="none" stroke="#111" stroke-width="1.5" d="M33.8 25.4c0 2.7-2.4 5-5.4 5h-5.7c-3 0-5.5-2.3-5.5-5s2.5-4.9 5.5-4.9h5.7c3 0 5.4 2.2 5.4 5zM38.2 13l-25.4 25"/>
+`,
+
+"self_propelled_artillery": `
+<path fill="#ccc" stroke="#111" stroke-linejoin="round" stroke-width="1.5" d="M38.3 38.3H12.8V12.8h25.5zm0 0"/>
+<path fill="none" stroke="#111" stroke-width="1.5" d="M33.8 25.5c0 2.7-2.4 5-5.4 5h-5.7c-3 0-5.5-2.3-5.5-5s2.4-4.9 5.5-4.9h5.7c3 0 5.4 2.2 5.4 5zm0 0"/>
+<path fill="#111" d="M29 25.5a3.4 3.4 0 0 1-3.5 3.4 3.4 3.4 0 0 1-3.4-3.4 3.4 3.4 0 0 1 3.4-3.4 3.4 3.4 0 0 1 3.4 3.4"/>
+`,
+
+}
+
+const TYPEMAP = {
+ "armor": "armor",
+ "artillery": "artillery",
+ "i-tank": "armor_itank",
+ "infantry": "infantry",
+ "mech. inf.": "mechanized_infantry",
+ "mob. a/t": "mobile_antitank",
+ "mot. a/t": "motorized_antitank_new",
+ "mot. inf.": "motorized_infantry",
+ "para": "paratroopers",
+ "recon": "recon",
+ "self prop. arty": "self_propelled_artillery",
+};
+
+// 94 units
+
+let smcp = /(IN|AU|NZ|SA|AT|H|G)$/;
+smcp = /NEVER/;
+
+const COLORMAP = {
+ "italian": "#b0c5d4",
+ "german": "#f9e3b3",
+ "allied": "#cead78",
+ "Pol": "#ffffff",
+ "FF": "#8197cd",
+ "NZ": "#95c3d6",
+ "AU": "#f48480",
+ "SA": "#f9a64a",
+ "IN": "#84c88b",
+}
+
+const BLACK = {
+ italian: "#221f1f",
+ german: "#004e2f",
+ allied: "#5c3a1e"
+}
+
+let x = 0, y = 0;
+for (let u of units) {
+ let type = TYPEMAP[u.type];
+ let black = BLACK[u.nationality];
+ let fill = COLORMAP[u.nationality];
+
+ let [a, b] = u.name.split('/');
+ if (b)
+ b = b.split('+');
+
+ for (let n in COLORMAP)
+ if (a.endsWith(n) || (b && b[0] === n))
+ fill = COLORMAP[n];
+
+ print(`<g transform="translate(${x},${y})">`);
+
+ if (type === 'armor' && u.elite && u.nationality === 'allied')
+ type = 'armor_elite';
+
+ let symbol = SYMBOLS[type];
+ print(symbol.replace(/#ccc/g, fill).replace(/#111/g, black).trim());
+
+ if (u.elite)
+ print(`<use href="#cv${u.steps}e" fill="${black}"/>`);
+ else
+ print(`<use href="#cv${u.steps}" fill="${black}"/>`);
+
+ print(`<g fill="${black}">`);
+ print(`<text text-anchor="start" font-size="7" x="2" y="8">${u.appearance}</text>`);
+ print(`<text text-anchor="start" font-size="7" x="2" y="48">${a}</text>`);
+
+ if (b) {
+ if (b.length > 1) {
+ print(`<text text-anchor="end" font-size="7" x="49" y="43">${b[0]}</text>`);
+ print(`<text text-anchor="end" font-size="7" x="49" y="49">${b[1]}</text>`);
+ } else {
+ print(`<text text-anchor="end" font-size="7" x="48" y="48">${b[0]}</text>`);
+ }
+ }
+
+ print(`</g>`);
+ print(`</g>`);
+ x += 51;
+ if (x >= 510) {
+ y += 51;
+ x = 0;
+ }
+}
+print(`</g>`);
+print(`</svg>`);
diff --git a/tools/rock_overlay.png b/tools/rock_overlay.png
new file mode 100644
index 0000000..d2c6976
--- /dev/null
+++ b/tools/rock_overlay.png
Binary files differ
diff --git a/tools/rock_overlay@2x.png b/tools/rock_overlay@2x.png
new file mode 100644
index 0000000..8710ab6
--- /dev/null
+++ b/tools/rock_overlay@2x.png
Binary files differ
diff --git a/tools/scree_overlay.png b/tools/scree_overlay.png
new file mode 100644
index 0000000..cab6b5c
--- /dev/null
+++ b/tools/scree_overlay.png
Binary files differ
diff --git a/tools/scree_overlay@2x.png b/tools/scree_overlay@2x.png
new file mode 100644
index 0000000..5680582
--- /dev/null
+++ b/tools/scree_overlay@2x.png
Binary files differ
diff --git a/tools/wetland.pbm b/tools/wetland.pbm
new file mode 100644
index 0000000..aa9151c
--- /dev/null
+++ b/tools/wetland.pbm
Binary files differ