// https://www.redblobgames.com/grids/hexagons/ const fs = require('fs') // 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 = [ "W", "E", "SE", "SW", "NW", "NE" ]; 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) { if (t > hex_road[list[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(100,102); // 99 is exit hex def_offmap(124,127); def_offmap(149,150); // 148 is exit hex def_offmap(173,174); def_offmap(197,199); def_offmap(222,224); // 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); // Qattara Depression and east of it def_offmap(145,146); 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("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("West Line", 151, 152); 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", 99: "Map Edge", 148: "Map Edge", 197: "Map Edge", 175: "Map Edge", 4: "Malta", 47: "Exited", 53: "Exited", 48: "Return for Refit", 49: "Alexandria Queue", 102: "Return for Refit", 127: "El Agheila Queue", 145: "Qattara Depression", 146: "Qattara Depression", 147: "Qattara Depression", 164: "Siwa Oasis NE NW", 123: "Alexandria SW SE", } function nameregion(name, region) { for (let x of region) if (!(x in hex_name) && hex_exists[x]) hex_name[x] = name } function namearound(name) { let hex_id = 0 for (let x in hex_name) if (hex_name[x] === name) hex_id = x | 0 if (hex_id === 0) throw new Error("place not found: " + name) for (let s = 0; s < 6; ++s) { let next_id = hex_id + nextdir[s]; if (hex_exists[next_id] && hex_name[next_id] === undefined) hex_name[next_id] = name + " " + nextname[s] } } namearound("Jalo Oasis") namearound("Jarabub Oasis") namearound("Siwa Oasis") namearound("Maaten Giofer") namearound("Mersa Matruh") namearound("Bir el Kenayis") namearound("Msus") namearound("Derna") namearound("Mechili") namearound("Tengeder") namearound("Haraga") namearound("Ft. Maddalena") namearound("Bir Hacheim") namearound("Bir Gubi") namearound("Alexandria") namearound("El Alamein") namearound("Fuka") namearound("El Himeimat") namearound("Bir Khamsa") namearound("Bir Fuad") nameregion("Western Desert", [119, 143, 144, 167, 190, 191 ]) nameregion("Qattara Depression", [ 144, 145, 146, 147, 167, 168, 169, 191, 192, 193, 215, 216, 217 ]) nameregion("Jalo Oasis Trail", [ 157, 133, 181 ]) nameregion("Libya Trail", [ 182, 183, 184, 185 ]) nameregion("Libya", regions.Libya) nameregion("Egypt", regions.Egypt) let units = []; const ARMOR = 0 const INFANTRY = 1 const ANTITANK = 2 const ARTILLERY = 3 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 klass = 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] = 1 units.push({nationality, type, class: klass, speed, appearance, steps, elite, label, name}); } function sort_nation(u) { if (u.nationality === 'italian') return 0 if (u.nationality === 'german') return 1 return 2 } function sort_subnation(u) { function isn(x) { let [a, b] = u.name.split('/'); if (b) b = b.split('+'); return a.endsWith(x) || (b && b[0] === x) } switch (true) { case isn("IN"): return 3 case isn("SA"): return 4 case isn("AU"): return 5 case isn("NZ"): return 6 case isn("FF"): return 7 case isn("Pol"): return 8 } return 2 } function sort_name(u) { if (u.nationality === 'italian') return 0 if (u.nationality === 'german') return 1 return sort_subnation(u) } function cmp_block(a, b) { let x = sort_name(a) * 10000 + a.class * 1000 + (1-a.elite) * 10 + (4-a.speed) * 100 + (4-a.steps) let y = sort_name(b) * 10000 + b.class * 1000 + (1-b.elite) * 10 + (4-b.speed) * 100 + (4-b.steps) // let x = sort_nation(a) * 10000 + a.class * 1000 + (1-a.elite) * 10 + (4-a.speed) * 100 + sort_subnation(a) // let y = sort_nation(b) * 10000 + b.class * 1000 + (1-b.elite) * 10 + (4-b.speed) * 100 + sort_subnation(b) if (x < y) return -1 if (x > y) return 1 if (parseInt(a.name) < parseInt(b.name)) return -1 if (parseInt(a.name) > parseInt(b.name)) return 1 if (a.name < b.name) return -1 if (a.name > b.name) return 1 return 0 } 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"); units.sort(cmp_block) let all_hexes = hex_exists.map((x,i) => x ? i : 0).filter(x => x > 0) function gen_array(name, fn) { data += "const " + name + " = [" + units.map(fn).join(",") + "]\n" } let data = "// DON'T EDIT - AUTOGENERATED\n" data += "const all_hexes = " + JSON.stringify(all_hexes) + "\n" data += "const hex_exists = " + JSON.stringify(hex_exists) + "\n" data += "const hex_road = " + JSON.stringify(hex_road) + "\n" data += "const side_road = " + JSON.stringify(side_road) + "\n" data += "const side_limit = " + JSON.stringify(side_limit) + "\n" data += "const hex_name = " + JSON.stringify(hex_name) + "\n" data += "const regions = " + JSON.stringify(regions) + "\n" gen_array("unit_name", u => JSON.stringify(u.name)) gen_array("unit_appearance", u => JSON.stringify(u.appearance)) gen_array("unit_max_steps", u => u.steps) gen_array("unit_elite", u => u.elite) gen_array("unit_class", u => u.class) gen_array("unit_speed", u => u.speed) data += "if (typeof module !== 'undefined')\n\tmodule.exports = {\n" data += "\t\tall_hexes, hex_exists, hex_name, hex_road, side_road, side_limit, regions,\n" data += "\t\tunit_name, unit_appearance, unit_max_steps, unit_elite, unit_class, unit_speed,\n" data += "\t}\n" fs.writeFileSync("data.js", data) fs.writeFileSync("rawdata.js", "exports.units = " + JSON.stringify(units)) // Show ranges let test = "" function gen_test_range(fn) { let ranges = []; let start = -1; for (let p = 0; p < units.length; ++p) { if (fn(units[p])) { if (start < 0) start = p; } else { if (start >= 0) { ranges.push([start,p-1]) start = -1; } } } if (start >= 0) ranges.push([start,units.length-1]) return ranges.map(([a,b])=>`(u >= ${a} && u <= ${b})`).join(" || ") } function gen_test(name, fn) { test += "function " + name + "(u) { return " + gen_test_range(fn) + " }\n" } gen_test("is_armor_unit", u => u.class === ARMOR) gen_test("is_artillery_unit", u => u.class === ARTILLERY) gen_test("is_infantry_unit", u => u.class === INFANTRY) gen_test("is_antitank_unit", u => u.class === ANTITANK) gen_test("is_recon_unit", u => u.speed === 4) gen_test("is_mechanized_unit", u => u.speed === 3) gen_test("is_motorized_unit", u => u.speed === 2) gen_test("is_leg_unit", u => u.speed === 1) gen_test("is_elite_unit", u => u.elite) gen_test("is_italian_unit", u => u.nationality === 'italian') gen_test("is_german_unit", u => u.nationality === 'german') gen_test("is_axis_unit", u => u.nationality !== 'allied') gen_test("is_allied_unit", u => u.nationality === 'allied') console.log(test)