diff options
-rw-r--r-- | data.js | 2 | ||||
-rw-r--r-- | play.html | 2 | ||||
-rw-r--r-- | play.js | 75 | ||||
-rw-r--r-- | rules.js | 325 | ||||
-rw-r--r-- | tools/gendata.js | 8 |
5 files changed, 335 insertions, 77 deletions
@@ -1,2 +1,2 @@ -const data = {"areas":[{"id":12,"name":"Oran","type":1,"zone":"V","x":430.6,"y":588.8},{"id":13,"name":"Algiers","type":1,"zone":"IV","x":1185.6,"y":346.8},{"id":14,"name":"Constantine","type":1,"zone":"II","x":2066.6,"y":315.8},{"id":9,"name":"France","type":3,"zone":null,"x":1605,"y":185.3,"w":266,"h":212.4},{"id":10,"name":"Morocco","type":3,"zone":null,"x":109,"y":1765,"w":94,"h":94},{"id":11,"name":"Tunisia","type":3,"zone":null,"x":2499.9,"y":1667.6,"w":94,"h":94},{"id":15,"name":"Barika","type":0,"zone":"I","x":1708.1,"y":1117.9},{"id":16,"name":"Batna","type":2,"zone":"I","x":2185.6,"y":1390.9},{"id":17,"name":"Biskra","type":2,"zone":"I","x":1853.6,"y":1620.9},{"id":18,"name":"Tebessa","type":0,"zone":"I","x":2299.6,"y":1120.9},{"id":19,"name":"Setif","type":0,"zone":"II","x":1917.6,"y":800.8},{"id":20,"name":"Souk Ahras","type":0,"zone":"II","x":2347.6,"y":848.9},{"id":21,"name":"Phillippeville","type":0,"zone":"II","x":2200.6,"y":584.9},{"id":22,"name":"Tizi Ouzou","type":0,"zone":"III","x":1473.6,"y":578.9},{"id":23,"name":"Bordj Bou Arreridj","type":0,"zone":"III","x":1465.6,"y":832.9},{"id":24,"name":"Bougie","type":0,"zone":"III","x":1703.6,"y":616.9},{"id":25,"name":"Medea","type":0,"zone":"IV","x":1212,"y":727},{"id":26,"name":"Orleansville","type":0,"zone":"IV","x":982,"y":780.2},{"id":27,"name":"Mecheria","type":2,"zone":"V","x":234,"y":1485.7},{"id":28,"name":"Tlemcen","type":0,"zone":"V","x":173,"y":1140.9},{"id":29,"name":"Sidi Bel Abbes","type":0,"zone":"V","x":476,"y":1038},{"id":30,"name":"Mostaganem","type":0,"zone":"V","x":741,"y":863.9},{"id":31,"name":"Saida","type":2,"zone":"V","x":501,"y":1419.1},{"id":32,"name":"Mascara","type":2,"zone":"V","x":785,"y":1302.9},{"id":33,"name":"Ain Sefra","type":2,"zone":"V","x":752,"y":1670.9},{"id":34,"name":"Laghouat","type":2,"zone":"V","x":1191,"y":1615.6},{"id":35,"name":"Sidi Aissa","type":2,"zone":"VI","x":1385,"y":1186},{"id":36,"name":"Ain Qussera","type":0,"zone":"VI","x":1070.6,"y":1235.6}],"zones":{"V":["Oran","Mecheria","Tlemcen","Sidi Bel Abbes","Mostaganem","Saida","Mascara","Ain Sefra","Laghouat"],"IV":["Algiers","Medea","Orleansville"],"II":["Constantine","Setif","Souk Ahras","Phillippeville"],"I":["Barika","Batna","Biskra","Tebessa"],"III":["Tizi Ouzou","Bordj Bou Arreridj","Bougie"],"VI":["Sidi Aissa","Ain Qussera"]},"locations":{"DEPLOY":1,"ELIMINATED":2,"I":3,"II":4,"III":5,"IV":6,"V":7,"VI":8,"France":9,"Morocco":10,"Tunisia":11,"Oran":12,"Algiers":13,"Constantine":14,"Barika":15,"Batna":16,"Biskra":17,"Tebessa":18,"Setif":19,"Souk Ahras":20,"Phillippeville":21,"Tizi Ouzou":22,"Bordj Bou Arreridj":23,"Bougie":24,"Medea":25,"Orleansville":26,"Mecheria":27,"Tlemcen":28,"Sidi Bel Abbes":29,"Mostaganem":30,"Saida":31,"Mascara":32,"Ain Sefra":33,"Laghouat":34,"Sidi Aissa":35,"Ain Qussera":36},"units":[{"side":1,"type":0,"class":"fr_xx_2"},{"side":1,"type":0,"class":"fr_xx_4"},{"side":1,"type":0,"class":"fr_xx_9"},{"side":1,"type":0,"class":"fr_xx_12"},{"side":1,"type":0,"class":"fr_xx_13"},{"side":1,"type":0,"class":"fr_xx_14"},{"side":1,"type":0,"class":"fr_xx_19"},{"side":1,"type":0,"class":"fr_xx_20"},{"side":1,"type":0,"class":"fr_xx_21"},{"side":1,"type":0,"class":"fr_xx_29"},{"side":1,"type":0,"class":"fr_xx_27"},{"side":1,"type":0,"class":"fr_xx_25"},{"side":1,"type":0,"class":"fr_xx_7"},{"side":1,"type":1,"class":"fr_x"},{"side":1,"type":1,"class":"fr_x"},{"side":1,"type":1,"class":"fr_x"},{"side":1,"type":1,"class":"fr_x"},{"side":1,"type":2,"class":"fr_elite_x_para"},{"side":1,"type":2,"class":"fr_elite_x_para"},{"side":1,"type":2,"class":"fr_elite_x_para"},{"side":1,"type":2,"class":"fr_elite_x_inf"},{"side":1,"type":2,"class":"fr_elite_x_marine"},{"side":1,"type":2,"class":"fr_elite_x_marine"},{"side":1,"type":2,"class":"fr_elite_x_marine"},{"side":1,"type":3,"class":"alg_x"},{"side":1,"type":3,"class":"alg_x"},{"side":1,"type":3,"class":"alg_x"},{"side":1,"type":3,"class":"alg_x"},{"side":1,"type":3,"class":"alg_x"},{"side":1,"type":3,"class":"alg_x"},{"side":1,"type":4,"class":"alg_police"},{"side":1,"type":4,"class":"alg_police"},{"side":1,"type":4,"class":"alg_police"},{"side":1,"type":4,"class":"alg_police"},{"side":1,"type":4,"class":"alg_police"},{"side":1,"type":4,"class":"alg_police"},{"side":1,"type":4,"class":"alg_police"},{"side":1,"type":4,"class":"alg_police"},{"side":1,"type":4,"class":"alg_police"},{"side":1,"type":4,"class":"alg_police"},{"side":0,"type":5,"class":"fln_failek"},{"side":0,"type":5,"class":"fln_failek"},{"side":0,"type":5,"class":"fln_failek"},{"side":0,"type":5,"class":"fln_failek"},{"side":0,"type":5,"class":"fln_failek"},{"side":0,"type":5,"class":"fln_failek"},{"side":0,"type":5,"class":"fln_failek"},{"side":0,"type":5,"class":"fln_failek"},{"side":0,"type":5,"class":"fln_failek"},{"side":0,"type":5,"class":"fln_failek"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":8,"class":"fln_front"},{"side":0,"type":8,"class":"fln_front"},{"side":0,"type":8,"class":"fln_front"},{"side":0,"type":8,"class":"fln_front"},{"side":0,"type":8,"class":"fln_front"},{"side":0,"type":8,"class":"fln_front"},{"side":0,"type":8,"class":"fln_front"},{"side":0,"type":8,"class":"fln_front"},{"side":0,"type":8,"class":"fln_front"},{"side":0,"type":8,"class":"fln_front"},{"side":0,"type":8,"class":"fln_front"},{"side":0,"type":8,"class":"fln_front"},{"side":0,"type":8,"class":"fln_front"},{"side":0,"type":8,"class":"fln_front"},{"side":0,"type":8,"class":"fln_front"},{"side":0,"type":8,"class":"fln_front"}]} +const data = {"areas":[{"id":12,"name":"Oran","type":1,"zone":"V","x":430.6,"y":588.8},{"id":13,"name":"Algiers","type":1,"zone":"IV","x":1185.6,"y":346.8},{"id":14,"name":"Constantine","type":1,"zone":"II","x":2066.6,"y":315.8},{"id":9,"name":"France","type":3,"zone":null,"x":1605,"y":185.3,"w":266,"h":212.4},{"id":10,"name":"Morocco","type":3,"zone":null,"x":109,"y":1765,"w":94,"h":94},{"id":11,"name":"Tunisia","type":3,"zone":null,"x":2499.9,"y":1667.6,"w":94,"h":94},{"id":15,"name":"Barika","type":0,"zone":"I","x":1708.1,"y":1117.9},{"id":16,"name":"Batna","type":2,"zone":"I","x":2185.6,"y":1390.9},{"id":17,"name":"Biskra","type":2,"zone":"I","x":1853.6,"y":1620.9},{"id":18,"name":"Tebessa","type":0,"zone":"I","x":2299.6,"y":1120.9},{"id":19,"name":"Setif","type":0,"zone":"II","x":1917.6,"y":800.8},{"id":20,"name":"Souk Ahras","type":0,"zone":"II","x":2347.6,"y":848.9},{"id":21,"name":"Phillippeville","type":0,"zone":"II","x":2200.6,"y":584.9},{"id":22,"name":"Tizi Ouzou","type":0,"zone":"III","x":1473.6,"y":578.9},{"id":23,"name":"Bordj Bou Arreridj","type":0,"zone":"III","x":1465.6,"y":832.9},{"id":24,"name":"Bougie","type":0,"zone":"III","x":1703.6,"y":616.9},{"id":25,"name":"Medea","type":0,"zone":"IV","x":1212,"y":727},{"id":26,"name":"Orleansville","type":0,"zone":"IV","x":982,"y":780.2},{"id":27,"name":"Mecheria","type":2,"zone":"V","x":234,"y":1485.7},{"id":28,"name":"Tlemcen","type":0,"zone":"V","x":173,"y":1140.9},{"id":29,"name":"Sidi Bel Abbes","type":0,"zone":"V","x":476,"y":1038},{"id":30,"name":"Mostaganem","type":0,"zone":"V","x":741,"y":863.9},{"id":31,"name":"Saida","type":2,"zone":"V","x":501,"y":1419.1},{"id":32,"name":"Mascara","type":2,"zone":"V","x":785,"y":1302.9},{"id":33,"name":"Ain Sefra","type":2,"zone":"V","x":752,"y":1670.9},{"id":34,"name":"Laghouat","type":2,"zone":"V","x":1191,"y":1615.6},{"id":35,"name":"Sidi Aissa","type":2,"zone":"VI","x":1385,"y":1186},{"id":36,"name":"Ain Qussera","type":0,"zone":"VI","x":1070.6,"y":1235.6}],"zones":{"V":["Oran","Mecheria","Tlemcen","Sidi Bel Abbes","Mostaganem","Saida","Mascara","Ain Sefra","Laghouat"],"IV":["Algiers","Medea","Orleansville"],"II":["Constantine","Setif","Souk Ahras","Phillippeville"],"I":["Barika","Batna","Biskra","Tebessa"],"III":["Tizi Ouzou","Bordj Bou Arreridj","Bougie"],"VI":["Sidi Aissa","Ain Qussera"]},"locations":{"DEPLOY":1,"ELIMINATED":2,"I":3,"II":4,"III":5,"IV":6,"V":7,"VI":8,"France":9,"Morocco":10,"Tunisia":11,"Oran":12,"Algiers":13,"Constantine":14,"Barika":15,"Batna":16,"Biskra":17,"Tebessa":18,"Setif":19,"Souk Ahras":20,"Phillippeville":21,"Tizi Ouzou":22,"Bordj Bou Arreridj":23,"Bougie":24,"Medea":25,"Orleansville":26,"Mecheria":27,"Tlemcen":28,"Sidi Bel Abbes":29,"Mostaganem":30,"Saida":31,"Mascara":32,"Ain Sefra":33,"Laghouat":34,"Sidi Aissa":35,"Ain Qussera":36},"free_deploy_locations":[3,4,5,6,7,8],"units":[{"side":1,"type":0,"class":"fr_xx_2"},{"side":1,"type":0,"class":"fr_xx_4"},{"side":1,"type":0,"class":"fr_xx_9"},{"side":1,"type":0,"class":"fr_xx_12"},{"side":1,"type":0,"class":"fr_xx_13"},{"side":1,"type":0,"class":"fr_xx_14"},{"side":1,"type":0,"class":"fr_xx_19"},{"side":1,"type":0,"class":"fr_xx_20"},{"side":1,"type":0,"class":"fr_xx_21"},{"side":1,"type":0,"class":"fr_xx_29"},{"side":1,"type":0,"class":"fr_xx_27"},{"side":1,"type":0,"class":"fr_xx_25"},{"side":1,"type":0,"class":"fr_xx_7"},{"side":1,"type":1,"class":"fr_x"},{"side":1,"type":1,"class":"fr_x"},{"side":1,"type":1,"class":"fr_x"},{"side":1,"type":1,"class":"fr_x"},{"side":1,"type":2,"class":"fr_elite_x_para"},{"side":1,"type":2,"class":"fr_elite_x_para"},{"side":1,"type":2,"class":"fr_elite_x_para"},{"side":1,"type":2,"class":"fr_elite_x_inf"},{"side":1,"type":2,"class":"fr_elite_x_marine"},{"side":1,"type":2,"class":"fr_elite_x_marine"},{"side":1,"type":2,"class":"fr_elite_x_marine"},{"side":1,"type":3,"class":"alg_x"},{"side":1,"type":3,"class":"alg_x"},{"side":1,"type":3,"class":"alg_x"},{"side":1,"type":3,"class":"alg_x"},{"side":1,"type":3,"class":"alg_x"},{"side":1,"type":3,"class":"alg_x"},{"side":1,"type":4,"class":"alg_police"},{"side":1,"type":4,"class":"alg_police"},{"side":1,"type":4,"class":"alg_police"},{"side":1,"type":4,"class":"alg_police"},{"side":1,"type":4,"class":"alg_police"},{"side":1,"type":4,"class":"alg_police"},{"side":1,"type":4,"class":"alg_police"},{"side":1,"type":4,"class":"alg_police"},{"side":1,"type":4,"class":"alg_police"},{"side":1,"type":4,"class":"alg_police"},{"side":0,"type":5,"class":"fln_failek"},{"side":0,"type":5,"class":"fln_failek"},{"side":0,"type":5,"class":"fln_failek"},{"side":0,"type":5,"class":"fln_failek"},{"side":0,"type":5,"class":"fln_failek"},{"side":0,"type":5,"class":"fln_failek"},{"side":0,"type":5,"class":"fln_failek"},{"side":0,"type":5,"class":"fln_failek"},{"side":0,"type":5,"class":"fln_failek"},{"side":0,"type":5,"class":"fln_failek"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":6,"class":"fln_band"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":7,"class":"fln_cadre"},{"side":0,"type":8,"class":"fln_front"},{"side":0,"type":8,"class":"fln_front"},{"side":0,"type":8,"class":"fln_front"},{"side":0,"type":8,"class":"fln_front"},{"side":0,"type":8,"class":"fln_front"},{"side":0,"type":8,"class":"fln_front"},{"side":0,"type":8,"class":"fln_front"},{"side":0,"type":8,"class":"fln_front"},{"side":0,"type":8,"class":"fln_front"},{"side":0,"type":8,"class":"fln_front"},{"side":0,"type":8,"class":"fln_front"},{"side":0,"type":8,"class":"fln_front"},{"side":0,"type":8,"class":"fln_front"},{"side":0,"type":8,"class":"fln_front"},{"side":0,"type":8,"class":"fln_front"},{"side":0,"type":8,"class":"fln_front"}]} if (typeof module !== 'undefined') module.exports = data @@ -49,7 +49,7 @@ main { background-color: dimgray } .box{position:absolute;box-sizing:border-box;border:4px solid transparent} .box.loc{border-radius:50%} .box.action{border-color:lemonchiffon;} -.box.loc.action{background-color:#fff5;border-color:white;} +.box.loc.action{border-color:lemonchiffon;} .box.selected{border-color:yellow;} .box.tip { border-color: white; } @@ -21,6 +21,27 @@ const BOX_NAMES = ["UG", "OPS", "PTL", "OC"] const unit_count = 120 +function is_gov_unit(u) { return (u >= 0 && u <= 39) } +function is_fln_unit(u) { return (u >= 40 && u <= 119) } + +function set_has(set, item) { + if (!set) + return false + 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 true + } + return false +} + let ui = { board: document.getElementById("map"), map: document.getElementById("map"), @@ -73,7 +94,7 @@ const UNIT_LOC_SHIFT = 3 const UNIT_LOC_MASK = 255 << UNIT_LOC_SHIFT function is_unit_neutralized(u) { - return (game.units[u] & UNIT_NEUTRALIZED_MASK) === UNIT_NEUTRALIZED_MASK + return (view.units[u] & UNIT_NEUTRALIZED_MASK) === UNIT_NEUTRALIZED_MASK } function unit_loc(u) { @@ -96,6 +117,24 @@ function is_unit_not_dispersed(u) { return (view.units[u] & UNIT_DISPERSED_MASK) !== UNIT_DISPERSED_MASK } +function is_unit_moved(u) { + return set_has(view.moved, u) +} + +function is_unit_action(unit) { + return !!(view.actions && view.actions.unit && view.actions.unit.includes(unit)) +} + +function is_unit_selected(unit) { + if (Array.isArray(view.selected)) + return view.selected.includes(unit) + return view.selected === unit +} + +function is_loc_action(x) { + return !!(view.actions && view.actions.loc && view.actions.loc.includes(x)) +} + let action_register = [] function register_action(e, action, id) { @@ -136,9 +175,10 @@ let on_init_once = false function build_units() { function build_unit(u) { + let side = is_gov_unit(u) ? "gov" : "fln" let elt = ui.units[u] = document.createElement("div") let klass = data.units[u].class - elt.className = `counter unit u${u} ${klass}` + elt.className = `counter unit ${side} u${u} ${klass}` elt.addEventListener("mousedown", on_click_unit) // elt.addEventListener("mouseenter", on_focus_unit) // elt.addEventListener("mouseleave", on_blur) @@ -207,10 +247,12 @@ function on_init() { // Areas for (let i = 0; i < data.areas.length; ++i) { + let id = data.areas[i].id let name = data.areas[i].name let type = data.areas[i].type let e = document.createElement("div") e.id = `area-${name}` + e.my_id = id e.className = "box" e.style.left = data.areas[i].x / SCALE + "px" e.style.top = data.areas[i].y / SCALE + "px" @@ -225,12 +267,12 @@ function on_init() { if (type !== COUNTRY) { for (let j = 0; j < 4; ++j) { - let e = ui.locations[i * 4 + j] = document.createElement("div") + let e = ui.locations[id * 4 + j] = document.createElement("div") let box_name = BOX_NAMES[j] e.id = `ops-${name}-${box_name}` e.className = "box stack loc" e.addEventListener("mousedown", on_click_loc) - e.loc = i * 4 + j + e.dataset.loc = i * 4 + j e.style.left = (data.areas[i].x + (j % 2) * 99) / SCALE + "px" e.style.top = (data.areas[i].y + Math.floor(j / 2) * 99) / SCALE + "px" e.style.width = 94 / SCALE + "px" @@ -243,8 +285,17 @@ function on_init() { build_units() } -function update_map() { +function update_unit(e, u) { + e.classList.toggle("disrupted", is_unit_neutralized(u)) + e.classList.toggle("airmobile", is_unit_airmobile(u)) + e.classList.toggle("dispersed", is_unit_dispersed(u)) + e.classList.toggle("action", !view.battle && is_unit_action(u)) + e.classList.toggle("selected", !view.battle && is_unit_selected(u)) + e.classList.toggle("moved", is_unit_moved(u)) + e.classList.toggle("eliminated", unit_loc(u) === ELIMINATED) +} +function update_map() { ui.tracker[view.turn].appendChild(ui.markers.turn) ui.tracker[view.fln_ap].appendChild(ui.markers.fln_ap) ui.tracker[view.fln_psl].appendChild(ui.markers.fln_psl) @@ -262,8 +313,10 @@ function update_map() { if (loc) { e.loc = loc - if (loc === DEPLOY) { - if (!ui.fln_supply.contains(e)) + if (data.free_deploy_locations.includes(loc) || loc === DEPLOY) { + if (is_gov_unit(u) && !ui.gov_supply.contains(e)) + ui.gov_supply.appendChild(e) + if (is_fln_unit(u) && !ui.fln_supply.contains(e)) ui.fln_supply.appendChild(e) } else if (loc === ELIMINATED) { @@ -273,10 +326,18 @@ function update_map() { if (!ui.locations[loc].contains(e)) ui.locations[loc].appendChild(e) } + update_unit(e, u) } else { e.remove() } } + + for (let i = 0; i < ui.locations.length; ++i) { + let e = ui.locations[i] + if (e) { + e.classList.toggle("action", is_loc_action(ui.locations[i].loc)) + } + } } function on_update() { @@ -5,6 +5,10 @@ const GOV = "Government" const BOTH = "Both" const unit_count = 120 +const first_gov_unit = 0 +const last_gov_unit = 39 +const first_fln_unit = 40 +const last_fln_unit = 119 const FR_XX = 0 const FR_X = 1 @@ -25,9 +29,73 @@ var game = null var view = null const { - areas, zones, locations, units + areas, zones, locations, units, free_deploy_locations } = require("./data.js") +var first_friendly_unit, last_friendly_unit +var first_enemy_unit, last_enemy_unit + +function set_active_player() { + clear_undo() + if (game.active !== game.phasing) { + game.active = game.phasing + update_aliases() + } +} + +function set_passive_player() { + clear_undo() + let nonphasing = (game.phasing === GOV ? FLN : GOV) + if (game.active !== nonphasing) { + game.active = nonphasing + update_aliases() + } +} + +function set_enemy_player() { + if (is_active_player()) + set_passive_player() + else + set_active_player() +} + +function is_active_player() { + return game.active === game.phasing +} + +function is_passive_player() { + return game.active !== game.phasing +} + +function is_gov_player() { + return game.active === GOV +} + +function is_fln_player() { + return game.active === FLN +} + +function update_aliases() { + if (game.active === GOV) { + first_friendly_unit = first_gov_unit + last_friendly_unit = last_gov_unit + first_enemy_unit = first_fln_unit + last_enemy_unit = last_fln_unit + } else { + first_friendly_unit = first_fln_unit + last_friendly_unit = last_fln_unit + first_enemy_unit = first_gov_unit + last_enemy_unit = last_gov_unit + } +} + +function load_state(state) { + if (game !== state) { + game = state + update_aliases() + } +} + // === UNIT STATE === // location (8 bits), dispersed (1 bit), airmobile (1 bit), neutralized (1 bit) @@ -159,42 +227,28 @@ function find_free_unit_by_type(type) { throw new Error("cannot find free unit of type: " + type) } -// function find_unit(name) { -// for (let u = 0; u < unit_count; ++u) -// if (unit_name[u] === name) -// return u -// throw new Error("cannot find named block: " + name + unit_name) -// } - -// function is_axis_unit(u) { -// return u >= first_axis_unit && u <= last_axis_unit -// } - -// function is_german_unit(u) { -// return (u >= 14 && u <= 33) -// } +// === ITERATORS === -// function is_elite_unit(u) { -// return unit_elite[u] -// } - -// function is_artillery_unit(u) { -// return unit_class[u] === ARTILLERY -// } - -// function unit_cv(u) { -// if (is_elite_unit(u)) -// return unit_steps(u) * 2 -// return unit_steps(u) -// } +function for_each_friendly_unit_in_loc(x, fn) { + for (let u = first_friendly_unit; u <= last_friendly_unit; ++u) + if (unit_loc(u) === x) + fn(u) +} -// function unit_hp_per_step(u) { -// return is_elite_unit(u) ? 2 : 1 -// } +function for_each_friendly_unit_in_locs(xs, fn) { + for (let u = first_friendly_unit; u <= last_friendly_unit; ++u) + for (let x of xs) + if (unit_loc(u) === x) + fn(u) +} -// function unit_hp(u) { -// return unit_steps(u) * unit_hp_per_step(u) -// } +function has_friendly_unit_in_locs(xs) { + for (let u = first_friendly_unit; u <= last_friendly_unit; ++u) + for (let x of xs) + if (unit_loc(u) === x) + return true + return false +} // === PUBLIC FUNCTIONS === @@ -208,8 +262,16 @@ function gen_action(action, argument) { view.actions[action].push(argument) } +function gen_action_unit(u) { + gen_action('unit', u) +} + +function gen_action_loc(x) { + gen_action('loc', x) +} + exports.action = function (state, player, action, arg) { - game = state + load_state(state) let S = states[game.state] if (action in S) S[action](arg, player) @@ -221,13 +283,13 @@ exports.action = function (state, player, action, arg) { } exports.view = function(state, player) { - game = state + load_state(state) view = { log: game.log, prompt: null, scenario: game.scenario, - current: game.current, + phasing: game.phasing, turn: game.turn, fln_ap: game.fln_ap, @@ -245,6 +307,9 @@ exports.view = function(state, player) { units: game.units, } + if (player === game.active) + view.selected = game.selected + if (game.state === "game_over") { view.prompt = game.victory } else if (player !== game.active && game.active !== BOTH) { @@ -263,7 +328,7 @@ exports.view = function(state, player) { } exports.resign = function (state, player) { - game = state + load_state(state) if (game.state !== 'game_over') { if (player === FLN_NAME) goto_game_over(GOV, "FLN resigned.") @@ -275,7 +340,6 @@ exports.resign = function (state, player) { function goto_game_over(result, victory) { game.state = "game_over" - game.current = -1 game.active = "None" game.result = result game.victory = victory @@ -293,15 +357,16 @@ states.game_over = { // === SETUP === exports.setup = function (seed, scenario, options) { - game = { + load_state({ seed: seed, log: [], undo: [], - active: null, + + state: null, selected: -1, + phasing: GOV, + active: GOV, - current: 0, - state: null, scenario: null, turn: 0, @@ -329,7 +394,7 @@ exports.setup = function (seed, scenario, options) { // logging summary: null, - } + }) game.scenario = scenario setup_scenario(scenario) @@ -429,38 +494,85 @@ function setup_scenario(scenario_name) { log(`Government PSL=${game.gov_psl}`) SETUP[scenario_name]() + game.phasing = GOV } function goto_scenario_setup() { - game.active = GOV + set_active_player() game.state = "scenario_setup" - log_h1("Deployment") + log_h2(`${game.active} Deployment`) + game.selected = [] + game.summary = {} } states.scenario_setup = { inactive: "setup", prompt() { view.prompt = `Setup: ${game.active} Deployment.` - gen_action("end_deployment") + let done = true + for_each_friendly_unit_in_locs(free_deploy_locations, u => { + gen_action_unit(u) + done = false + }) + if (done) + gen_action('end_deployment') + if (game.selected.length > 0) { + for (let i = 0; i < areas.length; ++i) { + let loc = areas[i].id + for (let j = 0; j < 4; ++j) { + gen_action_loc(loc * 4 + j) + } + } + } + // XXX + gen_action("restart") + }, + unit(u) { + set_toggle(game.selected, u) + }, + loc(to) { + console.log("loc", to) + let list = game.selected + game.selected = [] + push_undo() + game.summary[to] = (game.summary[to] | 0) + list.length + for (let who of list) + set_unit_loc(who, to) }, end_deployment() { log(`Deployed`) - // let keys = Object.keys(game.summary).map(Number).sort((a,b)=>a-b) - // for (let x of keys) - // log(`>${game.summary[x]} at #${x}`) - // game.summary = null + let keys = Object.keys(game.summary).map(Number).sort((a,b)=>a-b) + for (let x of keys) + log(`>${game.summary[x]} at #${x}`) + game.summary = null end_scenario_setup() + }, + restart() { + // XXX debug + log("Restarting...") + goto_restart() } } function end_scenario_setup() { - game.turn = 1 - goto_random_event() + set_enemy_player() + if (has_friendly_unit_in_locs(free_deploy_locations)) { + goto_scenario_setup() + } else { + game.selected = -1 + game.summary = null + begin_game() + } } // === FLOW OF PLAY === +function begin_game() { + game.turn = 1 + goto_random_event() +} + function goto_random_event() { game.active = BOTH game.state = "random_event" @@ -751,6 +863,98 @@ function roll_2d6() { return roll_d6() + roll_d6() } +// Array remove and insert (faster than splice) + +function array_remove(array, index) { + let n = array.length + for (let i = index + 1; i < n; ++i) + array[i - 1] = array[i] + array.length = n - 1 +} + +function array_remove_item(array, item) { + let n = array.length + for (let i = 0; i < n; ++i) + if (array[i] === item) + return array_remove(array, i) +} + +// insert item at index (faster than splice) +function array_insert(array, index, item) { + for (let i = array.length; i > index; --i) + array[i] = array[i - 1] + array[index] = item + return array +} + +function set_clear(set) { + set.length = 0 +} + +function set_has(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 true + } + return false +} + +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 + } + return array_insert(set, a, item) +} + +function set_delete(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 array_remove(set, m) + } + return set +} + +function set_toggle(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 array_remove(set, m) + } + return array_insert(set, a, item) +} + // Fast deep copy for objects without cycles function object_copy(original) { if (Array.isArray(original)) { @@ -777,18 +981,3 @@ function object_copy(original) { } } -// Array remove and insert (faster than splice) - -function array_remove(array, index) { - let n = array.length - for (let i = index + 1; i < n; ++i) - array[i - 1] = array[i] - array.length = n - 1 -} - -function array_remove_item(array, item) { - let n = array.length - for (let i = 0; i < n; ++i) - if (array[i] === item) - return array_remove(array, i) -} diff --git a/tools/gendata.js b/tools/gendata.js index 83b8493..0f49c71 100644 --- a/tools/gendata.js +++ b/tools/gendata.js @@ -83,9 +83,16 @@ def_area("Laghouat", REMOTE, "V", 1191, 1615.6) def_area("Sidi Aissa", REMOTE, "VI", 1385, 1186) def_area("Ain Qussera", RURAL, "VI", 1070.6, 1235.6) + +let free_deploy_locations = [] +for (let l of ["I", "II", "III", "IV", "V", "VI"]) { + free_deploy_locations.push(locations[l]) +} + data.areas = areas data.zones = zones data.locations = locations +data.free_deploy_locations = free_deploy_locations let units = [] @@ -130,6 +137,7 @@ def_unit(GOV, EL_X, "fr_elite_x_marine", 3) def_unit(GOV, AL_X, "alg_x", 6) def_unit(GOV, POL, "alg_police", 10) +console.log("last_gov_unit =", units.length) def_unit(FLN, FAILEK, "fln_failek", 10) def_unit(FLN, BAND, "fln_band", 24) |