summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2022-07-04 16:41:26 +0200
committerTor Andersson <tor@ccxvii.net>2022-11-17 13:11:25 +0100
commit689061a44d01813f2cd9faf0299321d0a1d6498a (patch)
treecb6de7cf3ffd3c1b0c02f2f8b4154ae2240cb094
parente8a5f5410a0e876d889a2a8137c34bb925f65408 (diff)
downloadrommel-in-the-desert-689061a44d01813f2cd9faf0299321d0a1d6498a.tar.gz
Moves.
-rw-r--r--data.js2
-rw-r--r--play.html68
-rw-r--r--play.js49
-rw-r--r--rules.js964
-rw-r--r--tools/gendata.js2
5 files changed, 901 insertions, 184 deletions
diff --git a/data.js b/data.js
index eba7cbc..3db67a5 100644
--- a/data.js
+++ b/data.js
@@ -5,4 +5,4 @@ const side_limit = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,0,0,1,0,1,
const hex_name = {"7":"El Garib","8":"Cyrene","9":"El Gubba","10":"Derna","30":"Tocra","31":"Barce","32":"Marawa","33":"Wadi Cuff","34":"Wadi Cuff E","35":"Gazala","36":"Acroma","37":"Tobruk","38":"Belhamed","39":"Gambut","40":"Bardia","54":"Benghazi","55":"Er Regima","56":"Charruba","57":"Charruba E","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","68":"Sidi Barrani E","69":"Mersa Matruh","70":"Fuka","71":"El Daba","72":"El Alamein","73":"El Hamam","74":"Alexandria","78":"Ghemines","79":"Skeleidima","80":"Msus","81":"Msus E","82":"Tengeder W","83":"Tengeder","84":"Tengeder E","85":"Bir Hacheim","86":"Retma","87":"Bir Gubi","88":"Gabr Saleh","89":"Sidi Omar","90":"Bir Habata","91":"Sofafi","92":"Sofafi E","93":"Bir el Kenayis","94":"Bir Khalda","95":"Bir Khalda E","96":"Alam Halfa W","97":"Alam Halfa","98":"Alam Halfa E","103":"Beda Fomm","104":"Antelat","105":"Antelat E","106":"Ben Gania","107":"Ben Gania E","108":"Tengeder SE","109":"Bir Hacheim SW","110":"Bir Hacheim SE","111":"Retma SE","112":"Ft. Maddalena W","113":"Ft. Maddalena","114":"Ft. Maddalena E","115":"Bir Khamsa","116":"Bir el Qatrani","117":"Bir el Qatrani E","118":"Bir el Kenayis SE","119":"Bir Khalda SE","120":"El Himeimat W","121":"El Himeimat","122":"El Himeimat E","128":"Agedabia","129":"Jebel el Matar","130":"Haraga","131":"Haraga E","137":"Ft. Maddalena SW","138":"Ft. Maddalena SE","139":"Bir Khamsa SW","140":"Bir Fuad W","141":"Bir Fuad","142":"Bir Fuad E","151":"El Agheila","152":"Mersa Brega","153":"El Haseiat","154":"El Haseiat E","155":"Haraga SE","162":"Jarabub Oasis NW","163":"Jarabub Oasis NE","165":"Bir Fuad SW","166":"Bir Fuad SE","176":"Maaten Giofer","177":"Maaten Giofer E","178":"Sahaba","179":"Sahaba E","180":"Jalo Oasis NE","186":"Jarabub Oasis W","187":"Jarabub Oasis","188":"Jarabub Oasis E","189":"Siwa Oasis NE","200":"Maaten Giofer SW","201":"Maaten Giofer SE","202":"Sahaba SW","203":"Jalo Oasis W","204":"Jalo Oasis","205":"Jalo Oasis E","211":"Jarabub Oasis SW","212":"Siwa Oasis W","213":"Siwa Oasis","214":"Siwa Oasis E"}
const regions = {"Libya":[7,8,9,10,30,31,32,33,34,35,36,37,38,39,40,54,55,56,57,58,59,60,61,62,63,64,78,79,80,81,82,83,84,85,86,87,88,103,104,105,106,107,108,109,110,111,112,113,128,129,130,131,132,133,134,135,136,137,138,151,152,153,154,155,156,157,158,159,160,161,162,176,177,178,179,180,181,182,183,184,185,186,187,200,201,202,203,204,205,206,207,208,209,210,211],"Egypt":[65,66,67,68,69,70,71,72,73,74,89,90,91,92,93,94,95,96,97,98,114,115,116,117,118,119,120,121,122,123,139,140,141,142,143,144,163,164,165,166,167,188,189,190,191,212,213,214,215],"Sidi Omar":[89],"Tobruk":[37],"Sollum":[65],"El Agheila":[151],"Mersa Brega":[152],"East Line":[36,37,38,39,40,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,85,86,87,88,89,90,91,92,93,94,95,96,97,98,112,113,114,115,116,117,118,119,120,121,122,123,137,138,139,140,141,142,143,144,162,163,164,165,166,167,187,188,189,190,191],"West Line":[7,8,9,10,30,31,32,33,34,35,54,55,56,57,58,59,78,79,80,81,82,83,103,104,105,106,107,128,129,130],"Jebel el Akhdar":[7,8,9,32,33,34,56,57],"Sebkha el Segira":[152,176],"Sebket el Jeneinen":[179,203],"Qattara Depression":[144,145,146,168,169,192,193,216,217]}
const units = [{"nationality":"italian","type":"infantry","class":"infantry","speed":1,"appearance":"S","steps":4,"elite":0,"label":12,"name":"Tre"},{"nationality":"italian","type":"infantry","class":"infantry","speed":1,"appearance":"S","steps":4,"elite":0,"label":13,"name":"Pav"},{"nationality":"italian","type":"infantry","class":"infantry","speed":1,"appearance":"S","steps":4,"elite":0,"label":14,"name":"Bre"},{"nationality":"italian","type":"infantry","class":"infantry","speed":1,"appearance":"S","steps":3,"elite":0,"label":15,"name":"Bol"},{"nationality":"italian","type":"infantry","class":"infantry","speed":1,"appearance":"S","steps":3,"elite":0,"label":16,"name":"Sav"},{"nationality":"italian","type":"mech. inf.","class":"infantry","speed":3,"appearance":7,"steps":4,"elite":0,"label":21,"name":"Tri"},{"nationality":"italian","type":"infantry","class":"infantry","speed":1,"appearance":7,"steps":2,"elite":0,"label":22,"name":"Sab"},{"nationality":"italian","type":"armor","class":"armor","speed":3,"appearance":11,"steps":3,"elite":0,"label":23,"name":"Lit"},{"nationality":"italian","type":"mot. inf.","class":"infantry","speed":2,"appearance":17,"steps":3,"elite":0,"label":24,"name":"Fas"},{"nationality":"italian","type":"mot. inf.","class":"infantry","speed":2,"appearance":19,"steps":3,"elite":0,"label":25,"name":"Cen"},{"nationality":"italian","type":"infantry","class":"infantry","speed":1,"appearance":19,"steps":2,"elite":0,"label":26,"name":"Pis"},{"nationality":"italian","type":"para","class":"infantry","speed":1,"appearance":"M","steps":4,"elite":0,"label":27,"name":"Fol"},{"nationality":"italian","type":"artillery","class":"artillery","speed":1,"appearance":"S","steps":2,"elite":0,"label":28,"name":"Ita"},{"nationality":"italian","type":"armor","class":"armor","speed":3,"appearance":"S","steps":4,"elite":0,"label":29,"name":"Ari"},{"nationality":"german","type":"armor","class":"armor","speed":3,"appearance":"S","steps":3,"elite":1,"label":14,"name":"21/5"},{"nationality":"german","type":"recon","class":"armor","speed":4,"appearance":"S","steps":1,"elite":1,"label":15,"name":"21/3"},{"nationality":"german","type":"mech. inf.","class":"infantry","speed":3,"appearance":"S","steps":3,"elite":1,"label":16,"name":"21/104"},{"nationality":"german","type":"recon","class":"armor","speed":4,"appearance":3,"steps":1,"elite":1,"label":17,"name":"15/33"},{"nationality":"german","type":"mob. a/t","class":"antitank","speed":3,"appearance":3,"steps":2,"elite":1,"label":18,"name":"88mm/A"},{"nationality":"german","type":"mech. inf.","class":"infantry","speed":3,"appearance":3,"steps":3,"elite":1,"label":19,"name":"15/115"},{"nationality":"german","type":"armor","class":"armor","speed":3,"appearance":5,"steps":3,"elite":1,"label":21,"name":"15/8"},{"nationality":"german","type":"recon","class":"armor","speed":4,"appearance":5,"steps":1,"elite":1,"label":22,"name":"90/580"},{"nationality":"german","type":"mot. inf.","class":"infantry","speed":2,"appearance":5,"steps":2,"elite":1,"label":23,"name":"90/361"},{"nationality":"german","type":"mot. a/t","class":"antitank","speed":2,"appearance":5,"steps":2,"elite":1,"label":24,"name":"50mm"},{"nationality":"german","type":"artillery","class":"artillery","speed":1,"appearance":7,"steps":2,"elite":1,"label":25,"name":"/104"},{"nationality":"german","type":"mot. inf.","class":"infantry","speed":2,"appearance":7,"steps":2,"elite":1,"label":26,"name":"90/200"},{"nationality":"german","type":"mob. a/t","class":"antitank","speed":3,"appearance":11,"steps":2,"elite":1,"label":27,"name":"88mm/B"},{"nationality":"german","type":"mech. inf.","class":"infantry","speed":3,"appearance":11,"steps":2,"elite":1,"label":28,"name":"90/sv288"},{"nationality":"german","type":"mech. inf.","class":"infantry","speed":3,"appearance":11,"steps":2,"elite":1,"label":29,"name":"90/346"},{"nationality":"german","type":"para","class":"infantry","speed":1,"appearance":"M","steps":3,"elite":1,"label":31,"name":"Ram"},{"nationality":"german","type":"infantry","class":"infantry","speed":1,"appearance":"M","steps":3,"elite":1,"label":32,"name":"164/382+433"},{"nationality":"german","type":"infantry","class":"infantry","speed":1,"appearance":"M","steps":2,"elite":1,"label":33,"name":"164/125"},{"nationality":"german","type":"mech. inf.","class":"infantry","speed":3,"appearance":3,"steps":3,"elite":1,"label":34,"name":"90/155"},{"nationality":"german","type":"mot. a/t","class":"antitank","speed":2,"appearance":17,"steps":2,"elite":1,"label":35,"name":"76mm"},{"nationality":"allied","type":"armor","class":"armor","speed":3,"appearance":"S","steps":2,"elite":0,"label":14,"name":"2/3"},{"nationality":"allied","type":"mech. inf.","class":"infantry","speed":3,"appearance":"S","steps":2,"elite":0,"label":15,"name":"2/SG"},{"nationality":"allied","type":"mech. inf.","class":"infantry","speed":3,"appearance":"S","steps":2,"elite":0,"label":16,"name":"4IN/3m"},{"nationality":"allied","type":"mot. inf.","class":"infantry","speed":2,"appearance":"S","steps":4,"elite":0,"label":17,"name":"9AU/20"},{"nationality":"allied","type":"mot. inf.","class":"infantry","speed":2,"appearance":"S","steps":4,"elite":0,"label":18,"name":"70/14+16"},{"nationality":"allied","type":"mot. inf.","class":"infantry","speed":2,"appearance":"S","steps":3,"elite":0,"label":19,"name":"70/23"},{"nationality":"allied","type":"armor","class":"armor","speed":3,"appearance":"T","steps":1,"elite":1,"label":21,"name":"Matilda/A"},{"nationality":"allied","type":"mech. inf.","class":"infantry","speed":3,"appearance":"T","steps":3,"elite":0,"label":22,"name":"7/SG"},{"nationality":"allied","type":"mech. inf.","class":"infantry","speed":3,"appearance":"T","steps":3,"elite":0,"label":23,"name":"7/22G"},{"nationality":"allied","type":"mot. inf.","class":"infantry","speed":2,"appearance":"T","steps":3,"elite":0,"label":24,"name":"/Pol"},{"nationality":"allied","type":"mot. inf.","class":"infantry","speed":2,"appearance":"T","steps":3,"elite":0,"label":25,"name":"7AU/18"},{"nationality":"allied","type":"artillery","class":"artillery","speed":1,"appearance":"T","steps":2,"elite":0,"label":26,"name":"/Tob"},{"nationality":"allied","type":"mot. inf.","class":"infantry","speed":2,"appearance":18,"steps":4,"elite":0,"label":27,"name":"51H/152"},{"nationality":"allied","type":"self prop. arty","class":"artillery","speed":3,"appearance":18,"steps":2,"elite":0,"label":28,"name":"Priest"},{"nationality":"allied","type":"artillery","class":"artillery","speed":1,"appearance":18,"steps":4,"elite":0,"label":29,"name":"/C"},{"nationality":"allied","type":"armor","class":"armor","speed":3,"appearance":2,"steps":4,"elite":0,"label":31,"name":"7/7"},{"nationality":"allied","type":"mech. inf.","class":"infantry","speed":3,"appearance":2,"steps":3,"elite":0,"label":32,"name":"4IN/7m"},{"nationality":"allied","type":"mot. inf.","class":"infantry","speed":2,"appearance":2,"steps":3,"elite":0,"label":33,"name":"4IN/5"},{"nationality":"allied","type":"mot. inf.","class":"infantry","speed":2,"appearance":2,"steps":3,"elite":0,"label":34,"name":"4IN/11"},{"nationality":"allied","type":"armor","class":"armor","speed":3,"appearance":4,"steps":1,"elite":1,"label":35,"name":"Matilda/B"},{"nationality":"allied","type":"i-tank","class":"armor","speed":3,"appearance":4,"steps":4,"elite":0,"label":36,"name":"/1AT"},{"nationality":"allied","type":"armor","class":"armor","speed":3,"appearance":4,"steps":3,"elite":0,"label":37,"name":"7/4"},{"nationality":"allied","type":"recon","class":"armor","speed":4,"appearance":4,"steps":2,"elite":0,"label":38,"name":"7"},{"nationality":"allied","type":"artillery","class":"artillery","speed":1,"appearance":20,"steps":3,"elite":0,"label":39,"name":"/D"},{"nationality":"allied","type":"mot. a/t","class":"antitank","speed":2,"appearance":6,"steps":3,"elite":0,"label":41,"name":"2#"},{"nationality":"allied","type":"mech. inf.","class":"infantry","speed":3,"appearance":6,"steps":4,"elite":0,"label":42,"name":"2NZ/4"},{"nationality":"allied","type":"mech. inf.","class":"infantry","speed":3,"appearance":6,"steps":4,"elite":0,"label":43,"name":"2NZ/5"},{"nationality":"allied","type":"mech. inf.","class":"infantry","speed":3,"appearance":6,"steps":4,"elite":0,"label":44,"name":"2NZ/6"},{"nationality":"allied","type":"mot. inf.","class":"infantry","speed":2,"appearance":6,"steps":4,"elite":0,"label":45,"name":"1SA/2+5"},{"nationality":"allied","type":"mot. inf.","class":"infantry","speed":2,"appearance":6,"steps":3,"elite":0,"label":46,"name":"1SA/1"},{"nationality":"allied","type":"recon","class":"armor","speed":4,"appearance":8,"steps":2,"elite":0,"label":47,"name":"1SA"},{"nationality":"allied","type":"mot. inf.","class":"infantry","speed":2,"appearance":8,"steps":3,"elite":0,"label":48,"name":"1SA/3"},{"nationality":"allied","type":"mot. inf.","class":"infantry","speed":2,"appearance":8,"steps":3,"elite":0,"label":49,"name":"2SA/4+6"},{"nationality":"allied","type":"armor","class":"armor","speed":3,"appearance":8,"steps":4,"elite":0,"label":51,"name":"1/22"},{"nationality":"allied","type":"i-tank","class":"armor","speed":3,"appearance":8,"steps":3,"elite":0,"label":52,"name":"/32AT"},{"nationality":"allied","type":"artillery","class":"artillery","speed":1,"appearance":8,"steps":2,"elite":0,"label":53,"name":"/A"},{"nationality":"allied","type":"armor","class":"armor","speed":3,"appearance":10,"steps":4,"elite":0,"label":54,"name":"1/2"},{"nationality":"allied","type":"mech. inf.","class":"infantry","speed":3,"appearance":10,"steps":3,"elite":0,"label":55,"name":"1/201G"},{"nationality":"allied","type":"mech. inf.","class":"infantry","speed":3,"appearance":10,"steps":2,"elite":0,"label":56,"name":"1/SG"},{"nationality":"allied","type":"mot. inf.","class":"infantry","speed":2,"appearance":10,"steps":3,"elite":0,"label":57,"name":"5IN/29"},{"nationality":"allied","type":"armor","class":"armor","speed":3,"appearance":12,"steps":2,"elite":1,"label":58,"name":"Grant"},{"nationality":"allied","type":"mot. a/t","class":"antitank","speed":2,"appearance":12,"steps":3,"elite":0,"label":59,"name":"6#/A"},{"nationality":"allied","type":"mech. inf.","class":"infantry","speed":3,"appearance":14,"steps":2,"elite":0,"label":61,"name":"10IN/161m"},{"nationality":"allied","type":"mot. inf.","class":"infantry","speed":2,"appearance":14,"steps":4,"elite":0,"label":62,"name":"5IN/9+10"},{"nationality":"allied","type":"mot. inf.","class":"infantry","speed":2,"appearance":14,"steps":4,"elite":0,"label":63,"name":"10IN/21+25"},{"nationality":"allied","type":"mot. inf.","class":"infantry","speed":2,"appearance":14,"steps":2,"elite":0,"label":64,"name":"8IN/18"},{"nationality":"allied","type":"mot. inf.","class":"infantry","speed":2,"appearance":14,"steps":4,"elite":0,"label":65,"name":"FF/2"},{"nationality":"allied","type":"artillery","class":"artillery","speed":1,"appearance":14,"steps":4,"elite":0,"label":66,"name":"/B"},{"nationality":"allied","type":"armor","class":"armor","speed":3,"appearance":20,"steps":3,"elite":1,"label":67,"name":"Sher/B"},{"nationality":"allied","type":"armor","class":"armor","speed":3,"appearance":20,"steps":3,"elite":0,"label":68,"name":"8/9"},{"nationality":"allied","type":"armor","class":"armor","speed":3,"appearance":20,"steps":3,"elite":0,"label":69,"name":"8/24"},{"nationality":"allied","type":"armor","class":"armor","speed":3,"appearance":16,"steps":4,"elite":0,"label":71,"name":"10/8"},{"nationality":"allied","type":"armor","class":"armor","speed":3,"appearance":16,"steps":3,"elite":0,"label":72,"name":"10/23"},{"nationality":"allied","type":"mot. inf.","class":"infantry","speed":2,"appearance":16,"steps":4,"elite":0,"label":73,"name":"9AU/26"},{"nationality":"allied","type":"mot. inf.","class":"infantry","speed":2,"appearance":16,"steps":4,"elite":0,"label":74,"name":"9AU/24"},{"nationality":"allied","type":"mot. inf.","class":"infantry","speed":2,"appearance":16,"steps":4,"elite":0,"label":75,"name":"44/131+133"},{"nationality":"allied","type":"mot. inf.","class":"infantry","speed":2,"appearance":16,"steps":2,"elite":0,"label":76,"name":"44/132"},{"nationality":"allied","type":"armor","class":"armor","speed":3,"appearance":18,"steps":3,"elite":1,"label":77,"name":"Sher/A"},{"nationality":"allied","type":"mot. a/t","class":"antitank","speed":2,"appearance":18,"steps":4,"elite":0,"label":78,"name":"6#/B"},{"nationality":"allied","type":"mot. inf.","class":"infantry","speed":2,"appearance":18,"steps":4,"elite":0,"label":79,"name":"51H/154"}]
-if (typeof module !== 'undefined') module.exports = { hex_name, hex_road, side_road, side_limit, regions, units }
+if (typeof module !== 'undefined') module.exports = { hex_exists, hex_name, hex_road, side_road, side_limit, regions, units }
diff --git a/play.html b/play.html
index 77f76ab..9b888f5 100644
--- a/play.html
+++ b/play.html
@@ -66,10 +66,20 @@ th { background-color: gainsboro; }
position: absolute;
display: flex;
top: 24px;
- left: 1000px;
+ left: 1840px;
gap: 9px;
}
+#calendar2 {
+ display: flex;
+ user-select: none;
+ position: absolute;
+ top: 104px;
+ left: 1840px;
+ gap: 9px;
+ display: none;
+}
+
.month {
width: 68px;
height: 68px;
@@ -85,14 +95,14 @@ th { background-color: gainsboro; }
svg .side {
stroke-linecap: round;
- stroke-width: 6px;
+ stroke-width: 8px;
}
svg .side.axis_supply {
stroke-width: 64px;
stroke-linecap: butt;
stroke-dasharray: 8 100;
- stroke-dashoffset: -41;
+ stroke-dashoffset: -31;
stroke-opacity: 0.6;
stroke: green;
}
@@ -101,7 +111,7 @@ svg .side.allied_supply {
stroke-width: 64px;
stroke-linecap: butt;
stroke-dasharray: 8 100;
- stroke-dashoffset: -41;
+ stroke-dashoffset: -31;
stroke-opacity: 0.6;
stroke: crimson;
}
@@ -110,11 +120,37 @@ svg .side.allied_supply.axis_supply {
stroke: yellow;
}
+svg .side.axis_control {
+ stroke: green;
+ stroke-width: 8px;
+ stroke-opacity: 0.8;
+ stroke-dasharray: 54 100;
+ stroke-dashoffset: -8;
+}
+
+svg .side.allied_control {
+ stroke: brown;
+ stroke-width: 8px;
+ stroke-opacity: 0.8;
+ stroke-dasharray: 54 100;
+ stroke-dashoffset: -8;
+}
+
svg .hex.action {
stroke: white;
stroke-width: 2;
}
+svg .hex.from {
+ fill: gold;
+ fill-opacity: 0.2;
+}
+
+svg .hex.to {
+ fill: plum;
+ fill-opacity: 0.2;
+}
+
svg .hex.axis_supply {
fill: green;
fill-opacity: 0.3;
@@ -130,6 +166,16 @@ svg .hex.axis_supply.allied_supply {
fill-opacity: 0.3;
}
+svg .hex.axis_control {
+ fill: green;
+ fill-opacity: 0.2;
+}
+
+svg .hex.allied_control {
+ fill: brown;
+ fill-opacity: 0.2;
+}
+
#map .unit {
position: absolute;
}
@@ -184,6 +230,18 @@ svg .hex.axis_supply.allied_supply {
box-shadow: 0 0 0 2px yellow;
}
+.unit.disrupted {
+ border-color: crimson;
+}
+
+.unit.moved {
+ border-color: black;
+}
+
+.unit.unsupplied {
+ border-color: green;
+}
+
.unit.r0 { transform: rotate(0deg); }
.unit.r1 { transform: rotate(-90deg); }
.unit.r2 { transform: rotate(-180deg); }
@@ -740,6 +798,8 @@ svg .hex.axis_supply.allied_supply {
<div class="month">8</div>
<div class="month">9</div>
<div class="month">10</div>
+</div>
+<div id="calendar2">
<div class="month">11</div>
<div class="month">12</div>
<div class="month">13</div>
diff --git a/play.js b/play.js
index 0c116bf..125bb19 100644
--- a/play.js
+++ b/play.js
@@ -6,6 +6,26 @@ const svgNS = "http://www.w3.org/2000/svg"
const round = Math.round
const sqrt = Math.sqrt
+function set_index(set, item) {
+ let a = 0
+ let b = set.length - 1
+ while (a <= b) {
+ let m = (a + b) >> 1
+ let x = set[m]
+ if (item < x)
+ b = m - 1
+ else if (item > x)
+ a = m + 1
+ else
+ return m
+ }
+ return -1
+}
+
+function set_has(set, item) {
+ return set_index(set, item) >= 0
+}
+
let ui = {
hexes: [],
sides: [],
@@ -52,6 +72,22 @@ function is_hex_axis_supply(hex) {
return view.axis_supply[hex] > 0
}
+function is_hex_axis_controlled(hex) {
+ return set_has(view.axis_hexes, hex)
+}
+
+function is_hex_allied_controlled(hex) {
+ return set_has(view.allied_hexes, hex)
+}
+
+function is_side_axis_controlled(side) {
+ return set_has(view.axis_sides, side)
+}
+
+function is_side_allied_controlled(side) {
+ return set_has(view.allied_sides, side)
+}
+
function is_side_axis_supply_line(side) {
return view.axis_supply_line[side] > 0
}
@@ -236,7 +272,7 @@ function build_hexes() {
for (let month = 1; month <= 20; ++month) {
ui.hex_y[map_w * map_h + month] = 24 + 37
- ui.hex_x[map_w * map_h + month] = 1000 + 37 + (month-1) * 81
+ ui.hex_x[map_w * map_h + month] = 1840 + 37 + (month-1) * 81
}
document.getElementById("mapsvg").getElementById("grid").setAttribute("d", path.join(" "))
@@ -315,9 +351,16 @@ function update_map() {
e.classList.toggle("action", is_unit_action(u))
e.classList.toggle("selected", is_unit_selected(u))
+ e.classList.toggle("disrupted", is_unit_disrupted(u))
+ e.classList.toggle("moved", is_unit_moved(u))
+ // e.classList.toggle("unsupplied", !is_unit_supplied(u))
}
if (ui.hexes[hex]) {
ui.hexes[hex].classList.toggle("action", is_hex_action(hex))
+ ui.hexes[hex].classList.toggle("from", hex === view.from1 || hex === view.from2)
+ ui.hexes[hex].classList.toggle("to", hex === view.to1 || hex === view.to2)
+ ui.hexes[hex].classList.toggle("axis_control", is_hex_axis_controlled(hex))
+ ui.hexes[hex].classList.toggle("allied_control", is_hex_allied_controlled(hex))
if (view.axis_supply) {
ui.hexes[hex].classList.toggle("axis_supply", is_hex_axis_supply(hex))
for (let s = 0; s < 3; ++s)
@@ -328,6 +371,10 @@ function update_map() {
for (let s = 0; s < 3; ++s)
ui.sides[hex*3+s].classList.toggle("allied_supply", is_side_allied_supply_line(hex*3+s))
}
+ for (let s = 0; s < 3; ++s) {
+ ui.sides[hex*3+s].classList.toggle("axis_control", is_side_axis_controlled(hex*3+s))
+ ui.sides[hex*3+s].classList.toggle("allied_control", is_side_allied_controlled(hex*3+s))
+ }
}
}
}
diff --git a/rules.js b/rules.js
index 4cbce82..bc6c618 100644
--- a/rules.js
+++ b/rules.js
@@ -6,30 +6,33 @@ const max = Math.max
const min = Math.min
const abs = Math.abs
+var states = {}
+var game = null
+var view = null
+
+let { hex_exists, hex_road, side_road, side_limit, hex_name, units, regions } = require("./data")
+
function debug_hexes3(n, list) {
console.log("--", n, "--")
+ list = list.map((x,i) => hex_exists[i] ? x : "")
for (let y = 0; y < hexh; ++y)
console.log("".padStart(y*2," ") + list.slice(y*hexw, (y+1)*hexw).map(x=>String(x).padStart(3, ' ')).join(" "))
}
function debug_hexes2(n, list) {
console.log("--", n, "--")
+ list = list.map((x,i) => hex_exists[i] ? x : "")
for (let y = 0; y < hexh; ++y)
console.log("".padStart(y*2," ") + list.slice(y*hexw, (y+1)*hexw).map(x=>String(x).padStart(3, ' ')).join(" "))
}
function debug_hexes(n, list) {
console.log("--", n, "--")
+ list = list.map((x,i) => hex_exists[i] ? x : "")
for (let y = 0; y < hexh; ++y)
- console.log("".padStart(y," ") + list.slice(y*hexw, (y+1)*hexw).join(""))
+ console.log("".padStart(y," ") + list.slice(y*hexw, (y+1)*hexw).map(x=>String(x).padStart(2, ' ')).join(""))
}
-var states = {}
-var game = null
-var view = null
-
-let { hex_road, side_road, side_limit, hex_name, units, regions } = require("./data")
-
// Card deck has 42 cards, of which 28 are supply cards, and 14 are dummy cards.
// Represent draw pile and hands as [ dummy_supply_count, real_supply_count ]
const REAL_SUPPLY_COUNT = 28
@@ -38,13 +41,14 @@ const DUMMY_SUPPLY_COUNT = 14
const hexw = 25
const hexh = 9
-const hexcount = hexw * hexh
-const sidecount = hexcount * 3
+const first_hex = 7
+const last_hex = 215
+
const hexdeploy = hexw * hexh
const hexnext = [ 1, hexw, hexw-1, -1, -hexw, -(hexw-1) ]
-const first_hex = 7
-const last_hex = 215
+const hexcount = last_hex + 1
+const sidecount = hexcount * 3
const AXIS = 'Axis'
const ALLIED = 'Allied'
@@ -92,7 +96,6 @@ function calc_distance_map(supply) {
let map = new Array(hexcount)
for (let x = 0; x < hexcount; ++x)
map[x] = calc_distance(supply, x)
- debug_hexes2(hex_name[supply], map)
return map
}
@@ -132,10 +135,26 @@ function find_unit(name) {
throw new Error("cannot find named block: " + name)
}
+function is_map_hex(x) {
+ return next >= first_hex && next <= last_hex && hex_exists[next] === 1
+}
+
+function is_hex_or_adjacent_to(x, where) {
+ if (x === where) return true
+ for (let s = 0; s < 6; ++s)
+ if (x === where + hexnext[s])
+ return true
+ return false
+}
+
function find_units(list) {
return list.map(name => find_unit(name))
}
+function unit_name(u) {
+ return units[u].name
+}
+
function unit_speed(u) {
return units[u].speed
}
@@ -233,6 +252,8 @@ function is_axis_unit(u) {
}
function is_axis_hex(x) {
+ if (!hex_exists[x])
+ return false
let has_axis = false, has_allied = false
for (let u = 0; u < units.length; ++u) {
if (unit_hex(u) === x) {
@@ -246,6 +267,8 @@ function is_axis_hex(x) {
}
function is_allied_hex(x) {
+ if (!hex_exists[x])
+ return false
let has_axis = false, has_allied = false
for (let u = 0; u < units.length; ++u) {
if (unit_hex(u) === x) {
@@ -259,6 +282,8 @@ function is_allied_hex(x) {
}
function is_battle_hex(x) {
+ if (!hex_exists[x])
+ return false
let has_axis = false, has_allied = false
for (let u = 0; u < units.length; ++u) {
if (unit_hex(u) === x) {
@@ -272,6 +297,8 @@ function is_battle_hex(x) {
}
function is_empty_hex(x) {
+ if (!hex_exists[x])
+ return false
let has_axis = false, has_allied = false
for (let u = 0; u < units.length; ++u) {
if (unit_hex(u) === x) {
@@ -285,6 +312,8 @@ function is_empty_hex(x) {
}
function has_axis_unit(x) {
+ if (!hex_exists[x])
+ return false
for (let u = 0; u < units.length; ++u)
if (unit_hex(u) === x)
if (is_axis_unit(u))
@@ -293,6 +322,8 @@ function has_axis_unit(x) {
}
function has_allied_unit(x) {
+ if (!hex_exists[x])
+ return false
for (let u = 0; u < units.length; ++u)
if (unit_hex(u) === x)
if (is_allied_unit(u))
@@ -312,6 +343,70 @@ function has_enemy_unit(x) {
return has_allied_unit(x)
}
+function claim_hexside_control(side) {
+ if (game.active === AXIS) {
+ set_add(game.axis_sides, side)
+ set_delete(game.allied_sides, side)
+ } else {
+ set_add(game.allied_sides, side)
+ set_delete(game.axis_sides, side)
+ }
+}
+
+function release_hex_control(a) {
+ // no longer a battle hex: release hexsides if possible
+ set_delete(game.axis_hexes, a)
+ set_delete(game.allied_hexes, a)
+ for (let s = 0; s < 6; ++s) {
+ let b = a + hexnext[s]
+ if (b >= first_hex && b <= last_hex && hex_exists[b]) {
+ if (!is_battle_hex(b)) {
+ let side = to_side_id(a, b)
+ set_delete(game.axis_sides, side)
+ set_delete(game.allied_sides, side)
+ }
+ }
+ }
+}
+
+function is_new_battle_hex(a) {
+ if (is_battle_hex(a))
+ return !set_has(game.axis_hexes) && !set_has(game.allied_hexes)
+ return false
+}
+
+function claim_hex_control_for_defender(a) {
+ // a new battle hex: claim hex and hexsides for defender
+
+ if (game.active === AXIS)
+ set_add(game.allied_hexes, a)
+ else
+ set_add(game.axis_hexes, a)
+
+ for (let s = 0; s < 6; ++s) {
+ let b = a + hexnext[s]
+ if (b >= first_hex && b <= last_hex && hex_exists[b]) {
+ let side = to_side_id(a, b)
+ if (side_limit[side] > 0) {
+ if (game.active === AXIS) {
+ if (!set_has(game.axis_sides, side))
+ set_add(game.allied_sides, side)
+ } else {
+ if (!set_has(game.allied_sides, side))
+ set_add(game.axis_sides, side)
+ }
+ }
+ }
+ }
+}
+
+function claim_stuff() {
+ for (let x = first_hex; x <= last_hex; ++x)
+ if (hex_exists[x])
+ if (is_new_battle_hex(x))
+ claim_hex_control_for_defender(x)
+}
+
// === SUPPLY NETWORK ===
function is_side_unit(side, u) {
@@ -338,6 +433,21 @@ function to_side(a, b, s) {
return b * 3 + s - 3
}
+function to_side_id(a, b) {
+ if (a > b) {
+ let c = b
+ b = a
+ a = c
+ }
+ if (a + hexnext[0] === b)
+ return a * 3 + 0
+ else if (a + hexnext[1] === b)
+ return a * 3 + 1
+ else if (a + hexnext[2] === b)
+ return a * 3 + 2
+ throw new Error("not a hexside " + a + " to " + b);
+}
+
function ind(d, msg, here, ...extra) {
console.log(new Array(d).fill("-").join("") + msg, here, "("+hex_name[here]+")", extra.join(" "))
}
@@ -347,8 +457,6 @@ var trace_total
var trace_highway
var trace_chain
-
-
function trace_supply_highway(here, d) {
trace_highway++
ind(d, "> highway", here)
@@ -528,6 +636,219 @@ function update_supply_networks() {
update_allied_supply_network()
}
+function clear_supply_networks() {
+ game.axis_supply = null
+ game.axis_supply_line = null
+ game.allied_supply = null
+ game.allied_supply_line = null
+}
+
+// === MOVEMENT ===
+
+const path_from = [ new Array(hexcount), new Array(hexcount), new Array(hexcount), null, new Array(hexcount) ]
+const path_cost = [ new Array(hexcount), new Array(hexcount), new Array(hexcount), null, new Array(hexcount) ]
+const path_valid = new Array(hexcount)
+const path_enemy = new Array(hexcount)
+
+function search_move(start, start_cost, start_road) {
+ // recon=4, forced march=+1, rommel bonus=+1
+ let limit = 6
+
+ path_enemy.fill(0)
+ for (let u = 0; u < units.length; ++u) {
+ if (is_enemy_unit(u)) {
+ let x = unit_hex(u)
+ if (x >= first_hex && x <= last_hex)
+ path_enemy[x] = 1
+ }
+ }
+
+ search_move_bfs(path_from[0], path_cost[0], start, start_cost, 0, limit)
+ if (start_road >= 1)
+ search_move_bfs(path_from[1], path_cost[1], start, start_cost, 1, limit + 1)
+ if (start_road >= 2)
+ search_move_bfs(path_from[2], path_cost[2], start, start_cost, 2, limit + 2)
+ if (start_road >= 4)
+ search_move_bfs(path_from[4], path_cost[4], start, start_cost, 4, limit + 4)
+
+ let grid = new Array(hexcount).fill('-')
+ for (let x = first_hex; x <= last_hex; ++x) {
+ for (let speed = 4; speed >= 1; --speed) {
+ if (path_cost[0][x] <= speed)
+ grid[x] = speed
+ if (path_cost[1][x] <= speed + 1)
+ grid[x] = speed
+ if (path_cost[2][x] <= speed + 2)
+ grid[x] = speed
+ if (path_cost[4][x] <= speed + 4)
+ grid[x] = speed
+ }
+ }
+ grid[start] = '@'
+
+ debug_hexes2("reach", path_cost[0])
+}
+
+// Breadth First Search
+function search_move_bfs(from, cost, start, start_cost, road, max_cost) {
+ let queue = [ start << 4 | start_cost ]
+
+ from.fill(0)
+ cost.fill(15)
+ cost[start] = start_cost
+
+ while (queue.length > 0) {
+ let item = queue.shift()
+ let here = item >> 4
+ let here_cost = item & 15
+ let next_cost = here_cost + 1
+
+ for (let s = 0; s < 6; ++s) {
+ let next = here + hexnext[s]
+
+ // can't go off-map
+ if (next < first_hex || next > last_hex || !hex_exists[next])
+ continue
+
+ // already seen
+ if (cost[next] < 15)
+ continue
+
+ let side = to_side(here, next, s)
+ let max_side = side_limit[side]
+
+ // can't cross this hexside
+ if (max_side === 0)
+ continue
+
+ // must stay on road for current bonus
+ if (side_road[side] < road)
+ continue
+
+ // check hexside limit
+ if (path_enemy[next] && (game.side_limit[side] | 0) >= max_side)
+ continue
+
+ from[next] = here
+ cost[next] = next_cost
+
+ // must stop
+ if (path_enemy[next])
+ continue
+
+ // enough movement allowance to keep going
+ if (next_cost < max_cost)
+ queue.push(next << 4 | next_cost)
+ }
+ }
+}
+
+function can_move_to(to, road, speed) {
+ // TODO: engagement & hexside limit
+ if (road >= 4 && path_cost[4][to] <= speed + 4)
+ return true
+ if (road >= 2 && path_cost[2][to] <= speed + 2)
+ return true
+ if (road >= 1 && path_cost[1][to] <= speed + 1)
+ return true
+ if (path_cost[0][to] <= speed)
+ return true
+ return false
+}
+
+function can_move_from(from, road, speed) {
+ // TODO: engagement & hexside limit
+ return can_move_to(from, road, speed)
+}
+
+function can_force_march_to(to, road, speed) {
+ if (road >= 4 && path_cost[4][to] <= speed + 5)
+ return true
+ if (road >= 2 && path_cost[2][to] <= speed + 3)
+ return true
+ if (road >= 1 && path_cost[1][to] <= speed + 2)
+ return true
+ if (path_cost[0][to] <= speed + 1)
+ return true
+ return false
+}
+
+function pick_path(to, road, speed) {
+ let next_cost = 15, next_road = 0
+ if (path_cost[0][to] <= speed) {
+ next_cost = path_cost[0][to]
+ next_road = 0
+ }
+ if (road >= 1 && path_cost[1][to] <= speed + 1) {
+ if (path_cost[1][to] <= next_cost) {
+ next_cost = path_cost[1][to]
+ next_road = 1
+ }
+ }
+ if (road >= 2 && path_cost[2][to] <= speed + 2) {
+ if (path_cost[2][to] <= next_cost) {
+ next_cost = path_cost[2][to]
+ next_road = 2
+ }
+ }
+ if (road >= 4 && path_cost[4][to] <= speed + 4) {
+ if (path_cost[4][to] <= next_cost) {
+ next_cost = path_cost[4][to]
+ next_road = 4
+ }
+ }
+ return next_road
+}
+
+function pay_movement_cost(to, this_road, speed) {
+}
+
+function neighbor_has_friendly_unit(here) {
+ for (let s = 0; s < 6; ++s) {
+ let next = here + hexnext[s]
+ if (next >= first_hex && next <= last_hex)
+ if (has_friendly_unit(next))
+ return true
+ }
+ return false
+}
+
+function for_each_hex_and_adjacent_hex(here, fn) {
+ fn(here)
+ for (let s = 0; s < 6; ++s) {
+ let next = here + hexnext[s]
+ if (next >= first_hex && next <= last_hex && hex_exists[next])
+ fn(next)
+ }
+}
+
+function for_each_friendly_unit_in_hex(x, fn) {
+ for (let u = 0; u < units.length; ++u)
+ if (is_friendly_unit(u) && unit_hex(u) === x)
+ fn(u)
+}
+
+function max_speed_of_friendly_unit_in_hex(from) {
+ let max = 0
+ for_each_friendly_unit_in_hex(from, u => {
+ let s = unit_speed(u)
+ if (s > max)
+ max = s
+ })
+ return max
+}
+
+function find_valid_regroup_destinations(from, rommel) {
+ let speed = max_speed_of_friendly_unit_in_hex(from)
+ if (speed > 0) {
+ search_move(from, 0, 4)
+ for (let x = first_hex; x <= last_hex; ++x)
+ if (!path_valid[x])
+ if (can_move_to(x, 4, speed + rommel))
+ path_valid[x] = 1
+ }
+}
+
// === TURN ===
// Supply check
@@ -551,8 +872,8 @@ function end_player_turn() {
function goto_player_turn() {
game.rommel = 0
- game.group_moves = []
- game.regroup_moves = []
+ game.from1 = game.from2 = 0
+ game.to1 = game.to2 = 0
goto_supply_check()
}
@@ -603,12 +924,24 @@ states.turn_option = {
function goto_move_phase() {
game.state = 'select_moves'
+ if (game.active === AXIS) {
+ // Automatically select Rommel Move for 1-move turn options
+ if (game.turn_option !== 'offensive' && game.turn_option !== 'blitz')
+ game.rommel = 1
+ }
}
states.select_moves = {
inactive: "move phase",
prompt() {
- view.prompt = `Make Moves (${game.turn_option})`
+ if (game.turn_option === 'offensive') {
+ if (game.from1)
+ view.prompt = `Designate second offensive move.`
+ else
+ view.prompt = `Designate first offensive move.`
+ } else {
+ view.prompt = `Designate ${game.turn_option} move.`
+ }
gen_action('group')
gen_action('regroup')
},
@@ -618,232 +951,418 @@ states.select_moves = {
},
regroup() {
push_undo()
- game.state = 'regroup_move'
+ game.state = 'regroup_move_command_point'
},
}
+function gen_rommel_move() {
+ if (game.active === AXIS)
+ view.actions.rommel = game.rommel ? 0 : 1
+}
+
states.group_move_from = {
inactive: "group move (from)",
prompt() {
view.prompt = `Group Move: Select hex to move from.`
- for (let x = first_hex; x <= last_hex; ++x)
+ gen_rommel_move()
+ for (let x = first_hex; x <= last_hex; ++x) {
+ if (x === game.from1 && !game.to1)
+ continue
if (has_friendly_unit(x))
gen_action_hex(x)
- if (game.active === AXIS && !game.rommel)
- gen_action('rommel')
+ }
},
rommel() {
push_undo()
- game.rommel = 1
+ if (game.from1 === 0)
+ game.rommel = 1
+ else
+ game.rommel = 2
},
hex(x) {
push_undo()
- game.group_moves.push(x)
- game.state = 'group_move_who'
+ if (game.from1 === 0)
+ game.from1 = x
+ else
+ game.from2 = x
+ if (game.turn_option === 'offensive' && !game.from2)
+ game.state = 'select_moves'
+ else
+ goto_move_who()
},
}
-states.group_move_who = {
- inactive: "group move (who)",
+states.regroup_move_command_point = {
+ inactive: "regroup move (command point)",
prompt() {
- view.prompt = `Group Move: Select unit to move.`
- for (let i = 0; i < game.group_moves.length; ++i) {
- let from = game.group_moves[i]
- for (let u = 0; u < units.length; ++u) {
- if (unit_hex(u) === from && !is_unit_moved(u))
- gen_action_unit(u)
+ view.prompt = `Regroup Move: Designate the command point hex.`
+ gen_rommel_move()
+ for (let x = first_hex; x <= last_hex; ++x) {
+ if (!is_enemy_hex(x)) {
+ if (has_friendly_unit(x) || neighbor_has_friendly_unit(x))
+ gen_action_hex(x)
}
}
- gen_action('end_move')
},
- next() {
- // TODO: end move
+ rommel() {
push_undo()
- game.state = 'select_moves'
+ if (game.from1 === 0)
+ game.rommel = 1
+ else
+ game.rommel = 2
},
- unit(u) {
+ hex(x) {
push_undo()
- game.selected = [ u ]
- game.state = 'group_move_to'
- game.move_used = 0
- game.move_road = 4 // HIGHWAY
+ if (game.from1 === 0)
+ game.from1 = x
+ else
+ game.from2 = x
+ game.state = 'regroup_move_destination'
},
}
-states.group_move_to = {
- inactive: "group move (to)",
+states.regroup_move_destination = {
+ inactive: "regroup move (destination)",
prompt() {
- view.prompt = `Group Move: Select where to move.`
- let u = game.selected[0]
- let from = unit_hex(u)
- if (game.move_used > 0)
- gen_action('stop')
-
- var t0, t1
- t0 = Date.now()
- //for (let i = 0; i < 100000; ++i)
- search_path_move_dfs(u, from, game.move_used, game.move_road)
- t1 = Date.now()
- console.log("DFS", (t1 - t0) / 1000)
-
- var a = path_from.toString()
-
- t0 = Date.now()
- //for (let i = 0; i < 100000; ++i)
- search_path_move_ucs(u, from, game.move_used, game.move_road)
- t1 = Date.now()
- console.log("DFS", (t1 - t0) / 1000)
-
- var b = path_from.toString()
- if (a !== b) {
- console.log(a)
- console.log(b)
- }
-
+ view.prompt = `Regroup Move: Select destination hex.`
+ gen_rommel_move()
+ let cp, rommel = false
+ if (game.from2 === 0)
+ cp = game.from1, rommel = (game.rommel === 1 ? 1 : 0)
+ else
+ cp = game.from2, rommel = (game.rommel === 2 ? 1 : 0)
+ path_valid.fill(0)
+ for_each_hex_and_adjacent_hex(cp, x => {
+ find_valid_regroup_destinations(x, rommel)
+ })
for (let x = first_hex; x <= last_hex; ++x)
- if (path_from[x] > 0)
+ if (path_valid[x])
gen_action_hex(x)
-
- view.path_from = path_from
+ },
+ rommel() {
+ push_undo()
+ if (game.from2 === 0)
+ game.rommel = 1
+ else
+ game.rommel = 2
},
hex(x) {
push_undo()
- let u = game.selected[0]
- set_unit_hex(u, x)
+ if (game.from2 === 0)
+ game.to1 = x
+ else
+ game.to2 = x
+ if (game.turn_option === 'offensive' && !game.from2)
+ game.state = 'select_moves'
+ else
+ goto_move_who()
},
- stop() {
+}
+
+function goto_move_who() {
+ if (game.rommel === 1) {
+ if (game.from1 && game.to1)
+ log(`Regroup move from ${game.from1} to ${game.to1} (Rommel).`)
+ else if (game.from1)
+ log(`Group move from ${game.from1} (Rommel).`)
+ } else {
+ if (game.from1 && game.to1)
+ log(`Regroup move from ${game.from1} to ${game.to1}.`)
+ else if (game.from1)
+ log(`Group move from ${game.from1}.`)
+ }
+ if (game.rommel === 2) {
+ if (game.from2 && game.to2)
+ log(`Regroup move from ${game.from2} to ${game.to2} (Rommel).`)
+ else if (game.from2)
+ log(`Group move from ${game.from2} (Rommel).`)
+ } else {
+ if (game.from2 && game.to2)
+ log(`Regroup move from ${game.from2} to ${game.to2}.`)
+ else if (game.from2)
+ log(`Group move from ${game.from2}.`)
+ }
+ game.state = 'move_who'
+}
+
+function gen_group_move_who(from) {
+ for_each_friendly_unit_in_hex(from, u => {
+ if (!is_unit_moved(u))
+ gen_action_unit(u)
+ })
+}
+
+function gen_regroup_move_who(command_point, destination, rommel) {
+ search_move(destination, 0, 4)
+ for_each_hex_and_adjacent_hex(command_point, x => {
+ if (x !== destination) {
+ for_each_friendly_unit_in_hex(x, u => {
+ if (!is_unit_moved(u) && can_move_from(x, 4, unit_speed(u) + rommel))
+ gen_action_unit(u)
+ })
+ }
+ })
+}
+
+states.move_who = {
+ inactive: "move (who)",
+ prompt() {
+ view.prompt = `Move: Select unit to move.`
+ if (game.from1) {
+ if (game.to1)
+ gen_regroup_move_who(game.from1, game.to1, game.rommel === 1 ? 1 : 0)
+ else
+ gen_group_move_who(game.from1)
+ }
+ if (game.from2) {
+ if (game.to2)
+ gen_regroup_move_who(game.from2, game.to2, game.rommel === 2 ? 1 : 0)
+ else
+ gen_group_move_who(game.from2)
+ }
+ // TODO: retreat
+ gen_action('retreat')
+ gen_action('end_move')
+ },
+ unit(who) {
push_undo()
- let u = game.selected[0]
- set_unit_moved(u)
- game.selected = []
- game.state = 'group_move_who'
+ game.selected = [ who ]
+ game.state = 'move_to'
+ game.move_used = 0
+ game.move_road = 4
+ },
+ end_move() {
+ clear_supply_networks()
}
}
-var path_cost = new Array(hexcount)
-var path_from = new Array(hexcount)
+function rommel_group_move_bonus(from) {
+ if (game.rommel === 1 && from === game.from1 && !game.to1)
+ return 1
+ if (game.rommel === 2 && from === game.from2 && !game.to2)
+ return 1
+ return 0
+}
-function pq_push(queue, hex, used, road) {
- for (let i = 0, n = queue.length; i < n; ++i)
- if (queue[i][1] > used)
- return queue.splice(i, 0, [hex, used, road, hex_name[hex]])
- queue.push([hex, used, road, hex_name[hex]])
+function print_path(who, from, to, road) {
+ let p = [ hex_name[to] ]
+ while (to && to !== from) {
+ to = path_from[road][to]
+ p.unshift(hex_name[to])
+ }
+ log(unit_name(who) + " moved " + p.join(", ") + ".")
}
-// Uniform Cost Search
-function search_path_move_ucs(who, start, start_used, start_road) {
- let speed = unit_speed(who) + HIGHWAY
- path_cost.fill(100)
- path_from.fill(0)
+function print_pathX(who, start, to, road) {
+ let from = path_from[road][to]
+ log(`M ${hex_name[from]} to ${hex_name[to]}`)
+ while (from && from !== start) {
+ to = from
+ from = path_from[road][from]
+ log(`M ${hex_name[from]} to ${hex_name[to]}`)
+ }
+}
- let queue = []
- pq_push(queue, start, start_used, start_road)
- path_cost[start] = start_used
+function apply_move(move, who, from, to) {
+ let speed = unit_speed(who) + (move === game.rommel ? 1 : 0)
+ let road = pick_path(to, game.move_road, speed)
- let n = 0
- while (queue.length > 0) {
- //console.log(queue)
- let [ here, here_used, here_road ] = queue.shift()
- ++n
+ print_path(who, unit_hex(who), to, road)
- // already seen this hex from a shorter path
- if (path_cost[here] < here_used)
- continue
+ game.move_road = road
+ game.move_used = path_cost[road][to]
- for (let s = 0; s < 6; ++s) {
- let next = here + hexnext[s]
+ set_unit_moved(who)
+ set_unit_hex(who, to)
- // can't go off-map
- if (next < first_hex || next > last_hex)
- continue
+ pay_movement_cost(to, game.move_road, speed)
- let side = to_side(here, next, s)
+ if (is_battle_hex(to)) {
+ let side = to_side_id(to, path_from[road][to])
- // can't cross this hexside
- if (side_limit[side] === 0)
- continue
+ log(`cross ${side} ${hex_name[to]}/${hex_name[path_from[road][to]]}`)
- let next_road = min(here_road, side_road[side])
- let road_cost = here_road - next_road
- let next_used = here_used + road_cost + 1
+ if (game.side_limit[side])
+ game.side_limit[side] = 2
+ else
+ game.side_limit[side] = 1
- // not enough movement allowance to reach
- if (next_used > speed)
- continue
+ claim_hexside_control(side)
+ if (is_new_battle_hex(to)) {
+ claim_hex_control_for_defender(to)
+ game.battles.push(to)
+ }
+ return true
+ }
- // a shorter path has already been found
- if (next_used >= path_cost[next])
- continue
+ if (game.move_used === speed + game.move_road)
+ return true
- ind(2, "path", next, next_used, next_road)
+ return false
+}
- path_from[next] = here
- path_cost[next] = next_used
+function unit_speed_1(who) {
+ return unit_speed(who) + (game.rommel === 1 ? 1 : 0)
+}
- // must stop
- if (has_enemy_unit(next))
- continue
+function unit_speed_2(who) {
+ return unit_speed(who) + (game.rommel === 2 ? 1 : 0)
+}
- pq_push(queue, next, next_used, next_road)
- }
- }
+function can_move_regroup_1(who, from, to) {
+ if (to === game.to1 && is_hex_or_adjacent_to(from, game.from1))
+ if (can_move_to(game.to1, 4, unit_speed_1(who)))
+ return true
+ return false
+}
- console.log("UCS VISITED", n)
+function can_move_regroup_2(who, from, to) {
+ if (to === game.to2 && is_hex_or_adjacent_to(from, game.from2))
+ if (can_move_to(game.to2, 4, unit_speed_2(who)))
+ return true
+ return false
}
-var search_n
-function search_path_move_dfs(who, start, start_used, start_road) {
- path_cost.fill(100)
- path_from.fill(0)
+function can_move_group_1(who, from, to) {
+ if (from === game.from1 && !game.to1)
+ if (can_move_to(to, game.move_road, unit_speed_1(who)))
+ return true
+ return false
+}
- ind(0, "?path", start, start_used, unit_speed(who))
- search_n = 1
- path_cost[start] = 0
- path_from[start] = 0
- search_path_move_rec(unit_speed(who) + HIGHWAY, start, start_used, start_road, 2)
- console.log("DFS VISITED", search_n)
+function can_move_group_2(who, from, to) {
+ if (from === game.from2 && !game.to2)
+ if (can_move_to(to, game.move_road, unit_speed_2(who)))
+ return true
+ return false
}
-function search_path_move_rec(speed, here, here_used, here_road, d) {
- ++search_n
- for (let s = 0; s < 6; ++s) {
- let next = here + hexnext[s]
+states.move_to = {
+ inactive: "move (to)",
+ prompt() {
+ view.prompt = `Move: Select where to move.`
+ let who = game.selected[0]
+ let from = unit_hex(who)
- // can't go off-map
- if (next < first_hex || next > last_hex)
- continue
+ search_move(from, 0, 4)
- let side = to_side(here, next, s)
+ if (from === game.from1 && !game.to1)
+ for (let to = first_hex; to <= last_hex; ++to)
+ if (to != from && hex_exists[to] && can_move_group_1(who, from, to))
+ gen_action_hex(to)
- // can't cross this hexside
- if (side_limit[side] === 0)
- continue
+ if (from === game.from2 && !game.to2)
+ for (let to = first_hex; to <= last_hex; ++to)
+ if (to != from && hex_exists[to] && can_move_group_2(who, from, to))
+ gen_action_hex(to)
- let next_road = min(here_road, side_road[side])
- let road_cost = here_road - next_road
- let next_used = here_used + road_cost + 1
+ if (can_move_regroup_1(who, from, game.to1))
+ gen_action_hex(game.to1)
- // not enough movement allowance to reach
- if (next_used > speed)
- continue
+ if (can_move_regroup_2(who, from, game.to2))
+ gen_action_hex(game.to2)
+ },
+ hex(to) {
+ push_undo()
+ let who = game.selected[0]
+ let from = unit_hex(who)
- // a shorter path has already been found
- if (next_used >= path_cost[next])
- continue
+ search_move(from, 0, 4)
- ind(d, "path", next, next_used, speed)
+ if (can_move_group_1(who, from, to)) {
+ log(`group moved ${who} to ${to}`)
+ game.move_from = from
+ if (apply_move(1, who, from, to))
+ stop_move(who)
+ else
+ game.state = 'group_move_to'
+ return
+ }
- path_from[next] = here
- path_cost[next] = next_used
+ if (can_move_group_2(who, from, to)) {
+ log(`group moved ${who} to ${to}`)
+ game.move_from = from
+ if (apply_move(2, who, from, to))
+ stop_move(who)
+ else
+ game.state = 'group_move_to'
+ return
+ }
- // must stop
- if (has_enemy_unit(next))
- continue
+ if (can_move_regroup_1(who, from, to)) {
+ log(`regrouped ${who} to ${to}`)
+ apply_move(1, who, from, to)
+ stop_move(who)
+ game.state = 'move_who'
+ return
+ }
- search_path_move_rec(speed, next, next_used, next_road, d+1)
+ if (can_move_regroup_2(who, from, to)) {
+ log(`regrouped ${who} to ${to}`)
+ apply_move(2, who, from, to)
+ stop_move(who)
+ game.state = 'move_who'
+ return
+ }
+ },
+}
+
+states.group_move_to = {
+ inactive: "group move (to)",
+ prompt() {
+ view.prompt = `Group Move: Select where to move.`
+ let who = game.selected[0]
+ let from = unit_hex(who)
+ if (game.move_used > 0)
+ gen_action('stop')
+
+ search_move(from, game.move_used, game.move_road)
+
+ if (game.move_from === game.from1 && !game.to1)
+ for (let to = first_hex; to <= last_hex; ++to)
+ if (to != from && hex_exists[to] && can_move_group_1(who, game.move_from, to))
+ gen_action_hex(to)
+
+ if (game.move_from === game.from2 && !game.to2)
+ for (let to = first_hex; to <= last_hex; ++to)
+ if (to != from && hex_exists[to] && can_move_group_2(who, game.move_from, to))
+ gen_action_hex(to)
+ },
+ hex(to) {
+ let who = game.selected[0]
+ let from = unit_hex(who)
+
+ search_move(from, game.move_used, game.move_road)
+
+ if (can_move_group_1(who, game.move_from, to)) {
+ log(`group continued ${who} to ${to}`)
+ if (apply_move(1, who, from, to))
+ stop_move(who)
+ return
+ }
+
+ if (can_move_group_2(who, game.move_from, to)) {
+ log(`group continued ${who} to ${to}`)
+ if (apply_move(2, who, from, to))
+ stop_move(who)
+ return
+ }
+ },
+ stop() {
+ let who = game.selected[0]
+ stop_move(who)
}
}
+function stop_move(who) {
+ set_unit_moved(who)
+ game.move_from = 0
+ game.move_road = 4
+ game.move_used = 0
+ game.selected = []
+ game.state = 'move_who'
+}
+
// === DEPLOYMENT ===
states.free_deployment = {
@@ -898,6 +1417,7 @@ states.free_deployment = {
game.selected.length = 0
},
next() {
+ clear_undo()
if (game.active === AXIS)
game.active = ALLIED
else
@@ -1358,12 +1878,14 @@ exports.setup = function (seed, scenario, options) {
log: [],
undo: [],
+ state: null,
phasing: AXIS,
active: AXIS,
selected: null,
scenario: scenario,
month: 0,
+ first_player_turn: AXIS,
draw_pile: [ DUMMY_SUPPLY_COUNT, REAL_SUPPLY_COUNT ],
axis_hand: [ 0, 0 ],
@@ -1374,7 +1896,34 @@ exports.setup = function (seed, scenario, options) {
axis_minefields: [],
allied_minefields: [],
- first_player_turn: AXIS,
+ // supply networks
+ axis_supply: null,
+ axis_supply_line: null,
+ allied_supply: null,
+ allied_supply_line: null,
+
+ // battle hexes (defender)
+ axis_hexes: [],
+ allied_hexes: [],
+
+ // hexside control (for battle hexes)
+ axis_sides: [],
+ allied_sides: [],
+
+ // current turn option and moves
+ turn_option: null,
+ side_limit: {},
+ battles: [],
+ rommel: 0,
+ from1: 0,
+ to1: 0,
+ from2: 0,
+ to2: 0,
+
+ // current group move state
+ move_from: 0,
+ move_used: 0,
+ move_road: 4,
}
setup(scenario)
@@ -1385,18 +1934,28 @@ exports.setup = function (seed, scenario, options) {
exports.view = function(state, current) {
game = state
- //update_supply_networks()
+ // update_supply_networks()
view = {
month: game.month,
units: game.units,
+ axis_hexes: game.axis_hexes,
+ allied_hexes: game.allied_hexes,
+ axis_sides: game.axis_sides,
+ allied_sides: game.allied_sides,
selected: game.selected,
- axis_supply: game.axis_supply,
- axis_supply_line: game.axis_supply_line,
- allied_supply: game.allied_supply,
- allied_supply_line: game.allied_supply_line,
+ // axis_supply: game.axis_supply,
+ // axis_supply_line: game.axis_supply_line,
+ // allied_supply: game.allied_supply,
+ // allied_supply_line: game.allied_supply_line,
}
+ if (game.rommel) view.rommel = game.rommel
+ if (game.from1) view.from1 = game.from1
+ if (game.from2) view.from2 = game.from2
+ if (game.to1) view.to1 = game.to1
+ if (game.to2) view.to2 = game.to2
+
return common_view(current)
}
@@ -1416,7 +1975,7 @@ function gen_action_hex(x) {
function random(n) {
// https://www.ams.org/journals/mcom/1999-68-225/S0025-5718-99-00996-5/S0025-5718-99-00996-5.pdf
- return (game.seed = game.seed * 185852 % 34359738337) % n
+ return (game.seed = game.seed * 200105 % 34359738337) % n
}
function shuffle(deck) {
@@ -1428,6 +1987,53 @@ function shuffle(deck) {
}
}
+// Sorted array treated as Set (for JSON)
+function set_index(set, item) {
+ let a = 0
+ let b = set.length - 1
+ while (a <= b) {
+ let m = (a + b) >> 1
+ let x = set[m]
+ if (item < x)
+ b = m - 1
+ else if (item > x)
+ a = m + 1
+ else
+ return m
+ }
+ return -1
+}
+
+function set_has(set, item) {
+ return set_index(set, item) >= 0
+}
+
+function set_add(set, item) {
+ let a = 0
+ let b = set.length - 1
+ while (a <= b) {
+ let m = (a + b) >> 1
+ let x = set[m]
+ if (item < x)
+ b = m - 1
+ else if (item > x)
+ a = m + 1
+ else
+ return
+ }
+ set.splice(a, 0, item)
+}
+
+function set_delete(set, item) {
+ let i = set_index(set, item)
+ if (i >= 0)
+ set.splice(i, 1)
+}
+
+function set_clear(set) {
+ set.length = 0
+}
+
function remove_from_array(array, item) {
let i = array.indexOf(item)
if (i >= 0)
@@ -1539,8 +2145,9 @@ exports.resign = function (state, current) {
exports.action = function (state, current, action, arg) {
game = state
+ // Object.seal(game) // XXX: don't allow adding properties
let S = states[game.state]
- if (action in S) {
+ if (S && action in S) {
S[action](arg, current)
} else {
if (action === 'undo' && game.undo && game.undo.length > 0)
@@ -1558,7 +2165,10 @@ function common_view(current) {
view.prompt = `Waiting for ${game.active} \u2014 ${inactive}...`
} else {
view.actions = {}
- states[game.state].prompt()
+ if (states[game.state])
+ states[game.state].prompt()
+ else
+ view.prompt = "Unknown state: " + game.state
if (game.undo && game.undo.length > 0)
view.actions.undo = 1
else
diff --git a/tools/gendata.js b/tools/gendata.js
index fb0348c..75c3a9f 100644
--- a/tools/gendata.js
+++ b/tools/gendata.js
@@ -573,4 +573,4 @@ 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 }");
+console.log("if (typeof module !== 'undefined') module.exports = { hex_exists, hex_name, hex_road, side_road, side_limit, regions, units }");