summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data.js2
-rw-r--r--images/back-forth.svg1
-rw-r--r--images/shaking-hands.svg1
-rw-r--r--play.css30
-rw-r--r--play.html10
-rw-r--r--play.js11
-rw-r--r--rules.js790
-rw-r--r--tools/gendata.js33
8 files changed, 739 insertions, 139 deletions
diff --git a/data.js b/data.js
index cf3eef4..0feac9c 100644
--- a/data.js
+++ b/data.js
@@ -1,2 +1,2 @@
-const data = {"first_pop":1,"first_city":0,"last_city":10,"first_dept":11,"last_pop":22,"last_dept":26,"first_foreign":27,"last_foreign":31,"first_loc":32,"last_loc":49,"coastal_spaces":[11,12,13,17],"card_name":[null,"1st Division","Ospina & Mora","Tapias","Caño Limón - Coveñas","Occidental & Ecopetrol","Oil Spill","7th Special Forces","Fuerza Aérea Colombiana","High Mountain Battalions","Blackhawks","National Defense & Security Council","Plan Colombia","Plan Meteoro","Tres Esquinas","War Tax","Coffee Prices","Madrid Donors","NSPD-18","General Offensive","Mono Jojoy","Raúl Reyes","Alfonso Cano","DoD Contractors","Operación Jaque","Ejército de Liberación Nacional","Gramaje","Misil Antiaéreo","Hugo Chávez","Kill Zone","Peace Commission","Betancourt","Secuestrados","Sucumbíos","Airdropped AKs","Crop Substitution","Zona de Convivencia","Former Military","National Coordination Center","Soldados Campesinos","Demobilization","Mancuso","Senado & Cámara","Calima Front","Colombia Nueva","Los Derechos Humanos","Limpieza","Pinto & del Rosario","Unión Sindical Obrera","Bloques","Carabineros","Pipeline Repairs","Castaño","Criminal Air Force","Deserters & Defectors","DEA Agents","Drogas La Rebaja","Op Millennium","General Serrano","Salcedo","The Chess Player","Air Bridge","Amazonía","Narco-War","Cocaine Labs","Poppies","Tingo María","Mexican Traffickers","Narco-Subs","Riverines & Fast Boats","Ayahuasca Tourism","Darién","Sicarios","Propaganda!","Propaganda!","Propaganda!","Propaganda!"],"card_order":[null,[0,1,2,3],[0,1,2,3],[0,1,2,3],[0,1,3,2],[0,1,3,2],[0,1,3,2],[0,2,1,3],[0,2,1,3],[0,2,1,3],[0,2,3,1],[0,2,3,1],[0,2,3,1],[0,3,1,2],[0,3,1,2],[0,3,1,2],[0,3,2,1],[0,3,2,1],[0,3,2,1],[1,0,2,3],[1,0,2,3],[1,0,2,3],[1,0,3,2],[1,0,3,2],[1,0,3,2],[1,2,0,3],[1,2,0,3],[1,2,0,3],[1,2,3,0],[1,2,3,0],[1,2,3,0],[1,3,0,2],[1,3,0,2],[1,3,0,2],[1,3,2,0],[1,3,2,0],[1,3,2,0],[2,0,1,3],[2,0,1,3],[2,0,1,3],[2,0,3,1],[2,0,3,1],[2,0,3,1],[2,1,0,3],[2,1,0,3],[2,1,0,3],[2,1,3,0],[2,1,3,0],[2,1,3,0],[2,3,0,1],[2,3,0,1],[2,3,0,1],[2,3,1,0],[2,3,1,0],[2,3,1,0],[3,0,1,2],[3,0,1,2],[3,0,1,2],[3,0,2,1],[3,0,2,1],[3,0,2,1],[3,1,0,2],[3,1,0,2],[3,1,0,2],[3,1,2,0],[3,1,2,0],[3,1,2,0],[3,2,0,1],[3,2,0,1],[3,2,0,1],[3,2,1,0],[3,2,1,0],[3,2,1,0],null,null,null,null],"space_name":["Bogotá","Cali","Medellín","Bucaramanga","Ibagué","Santa Marta","Cartagena","Cúcuta","Neiva","Pasto","Sincelejo","Atlántico","Chocó","Nariño","Meta West","Guaviare","Putumayo","Cesar","Antioquia","Santander","Huila","Arauca","Meta East","Vichada","Guainía","Vaupés","Amazonas","Brasil","Ecuador","Panamá","Perú","Venezuela","Santa Marta / Cartagena","Cali / Pasto","Neiva / Pasto","Bogotá / San José","Pasto / Tumaco","Cali / Buenaventura","Cartagena / Sincelejo","Sincelejo / Medellín","Medellín / Ibagué","Ibagué / Cali","Bucaramanga / Ibagué / Bogotá","Bogotá / Neiva","Cúcuta / Arauca","Bogotá / Yopal","Santa Marta / Ayacucho","Bucaramanga / Ayacucho","Sincelejo / Ayacucho","Cúcuta / Ayacucho"],"spaces":[{"type":"city","id":"Bogota","pop":8,"adjacent":[35,42,43,45,19,22,14,20]},{"type":"city","id":"Cali","pop":3,"adjacent":[12,13,20,33,37,41]},{"type":"city","id":"Medellin","pop":3,"adjacent":[12,18,39,40]},{"type":"city","id":"Bucaramanga","pop":2,"adjacent":[18,19,42,47]},{"type":"city","id":"Ibague","pop":2,"adjacent":[12,18,20,40,41,42]},{"type":"city","id":"Santa Marta","pop":2,"adjacent":[11,17,32,46]},{"type":"city","id":"Cartagena","pop":1,"adjacent":[11,32,38]},{"type":"city","id":"Cucuta","pop":1,"adjacent":[19,31,44,49]},{"type":"city","id":"Neiva","pop":1,"adjacent":[14,16,20,34,43]},{"type":"city","id":"Pasto","pop":1,"adjacent":[13,16,20,28,33,34,36]},{"type":"city","id":"Sincelejo","pop":1,"adjacent":[11,12,18,38,39,48]},{"type":"forest","id":"Atlantico","pop":1,"adjacent":[5,6,10,17,18,19,32,38,46,47,48,49]},{"type":"forest","id":"Choco","pop":1,"adjacent":[1,2,4,10,13,18,20,29,37,39,40,41]},{"type":"forest","id":"Narino","pop":1,"adjacent":[1,9,12,20,28,33,36,37]},{"type":"forest","id":"Meta West","pop":1,"adjacent":[0,8,15,16,20,22,35,43]},{"type":"forest","id":"Guaviare","pop":1,"adjacent":[14,16,22,23,24,25,35]},{"type":"forest","id":"Putumayo","pop":1,"adjacent":[8,9,14,15,20,25,26,28,30,34]},{"type":"mountain","id":"Cesar","pop":1,"adjacent":[5,11,18,19,31,46,47,48,49]},{"type":"mountain","id":"Antioquia","pop":2,"adjacent":[2,3,4,10,11,12,17,19,20,39,40,42,46,47,48,49]},{"type":"mountain","id":"Santander","pop":2,"adjacent":[0,3,7,11,17,18,20,21,22,31,45,46,47,48,49]},{"type":"mountain","id":"Huila","pop":2,"adjacent":[0,1,4,8,9,12,13,14,16,18,19,33,34,41,42,43]},{"type":"grassland","id":"Arauca","pop":1,"adjacent":[19,22,23,31,44,45]},{"type":"grassland","id":"Meta East","pop":1,"adjacent":[0,14,15,19,21,23,35,45]},{"type":"grassland","id":"Vichada","pop":0,"adjacent":[15,21,22,24,31]},{"type":"forest","id":"Guainia","pop":0,"adjacent":[15,23,25,27,31]},{"type":"forest","id":"Vaupes","pop":0,"adjacent":[15,16,24,26,27]},{"type":"forest","id":"Amazonas","pop":0,"adjacent":[16,25,27,30]},{"type":"foreign","id":"Brasil","pop":0,"adjacent":[24,25,26]},{"type":"foreign","id":"Ecuador","pop":0,"adjacent":[9,13,16,36]},{"type":"foreign","id":"Panama","pop":0,"adjacent":[12]},{"type":"foreign","id":"Peru","pop":0,"adjacent":[16,26]},{"type":"foreign","id":"Venezuela","pop":0,"adjacent":[7,17,19,21,23,24,44,49]},{"type":"road","name":"Santa Marta / Cartagena","id":"Santa Marta / Cartagena","econ":1,"adjacent":[5,6,11]},{"type":"road","name":"Cali / Pasto","id":"Cali / Pasto","econ":1,"adjacent":[1,9,13,20]},{"type":"road","name":"Neiva / Pasto","id":"Neiva / Pasto","econ":1,"adjacent":[8,9,16,20]},{"type":"road","name":"Bogotá / San José","id":"Bogota / San Jose","econ":1,"adjacent":[14,15,22]},{"type":"road","name":"Pasto / Tumaco","id":"Pasto / Tumaco","econ":1,"adjacent":[9,13,28]},{"type":"pipeline","name":"Cali / Buenaventura","id":"Cali / Buenaventura","econ":1,"adjacent":[1,12,13]},{"type":"pipeline","name":"Cartagena / Sincelejo","id":"Cartagena / Sincelejo","econ":1,"adjacent":[6,10,11]},{"type":"pipeline","name":"Sincelejo / Medellín","id":"Sincelejo / Medellin","econ":2,"adjacent":[2,10,12,18]},{"type":"pipeline","name":"Medellín / Ibagué","id":"Medellin / Ibague","econ":1,"adjacent":[2,4,12,18]},{"type":"pipeline","name":"Ibagué / Cali","id":"Ibague / Cali","econ":1,"adjacent":[1,4,12,20]},{"type":"pipeline","name":"Bucaramanga / Ibagué / Bogotá","id":"Bucaramanga / Ibague / Bogota","econ":2,"adjacent":[3,4,18,20]},{"type":"pipeline","name":"Bogotá / Neiva","id":"Bogota / Neiva","econ":2,"adjacent":[8,14,20]},{"type":"pipeline","name":"Cúcuta / Arauca","id":"Cucuta / Arauca","econ":3,"adjacent":[7,21,21,31]},{"type":"pipeline","name":"Bogotá / Yopal","id":"Bogota / Yopal","econ":2,"adjacent":[19,21,22]},{"type":"pipeline","name":"Santa Marta / Ayacucho","id":"Santa Marta / Ayacucho","econ":2,"adjacent":[5,11,17,18,19,47,48,49]},{"type":"pipeline","name":"Bucaramanga / Ayacucho","id":"Bucaramanga / Ayacucho","econ":2,"adjacent":[3,11,17,18,19,46,48,49]},{"type":"pipeline","name":"Sincelejo / Ayacucho","id":"Sincelejo / Ayacucho","econ":3,"adjacent":[10,11,17,18,19,46,47,49]},{"type":"pipeline","name":"Cúcuta / Ayacucho","id":"Cucuta / Ayacucho","econ":3,"adjacent":[7,11,17,18,19,31,46,47,48]}],"first_piece":[[0,3,3,33],[63,72],[102,108],[126,141]],"last_piece":[[2,2,32,62],[71,101],[107,125],[140,152]]}
+const data = {"first_space":0,"first_pop":1,"first_city":0,"last_city":10,"first_dept":11,"last_pop":22,"last_dept":26,"first_foreign":27,"last_foreign":31,"first_loc":32,"last_loc":49,"last_space":49,"coastal_spaces":[11,12,13,17],"card_name":[null,"1st Division","Ospina & Mora","Tapias","Caño Limón - Coveñas","Occidental & Ecopetrol","Oil Spill","7th Special Forces","Fuerza Aérea Colombiana","High Mountain Battalions","Blackhawks","National Defense & Security Council","Plan Colombia","Plan Meteoro","Tres Esquinas","War Tax","Coffee Prices","Madrid Donors","NSPD-18","General Offensive","Mono Jojoy","Raúl Reyes","Alfonso Cano","DoD Contractors","Operación Jaque","Ejército de Liberación Nacional","Gramaje","Misil Antiaéreo","Hugo Chávez","Kill Zone","Peace Commission","Betancourt","Secuestrados","Sucumbíos","Airdropped AKs","Crop Substitution","Zona de Convivencia","Former Military","National Coordination Center","Soldados Campesinos","Demobilization","Mancuso","Senado & Cámara","Calima Front","Colombia Nueva","Los Derechos Humanos","Limpieza","Pinto & del Rosario","Unión Sindical Obrera","Bloques","Carabineros","Pipeline Repairs","Castaño","Criminal Air Force","Deserters & Defectors","DEA Agents","Drogas La Rebaja","Op Millennium","General Serrano","Salcedo","The Chess Player","Air Bridge","Amazonía","Narco-War","Cocaine Labs","Poppies","Tingo María","Mexican Traffickers","Narco-Subs","Riverines & Fast Boats","Ayahuasca Tourism","Darién","Sicarios","Propaganda!","Propaganda!","Propaganda!","Propaganda!"],"card_order":[null,[0,1,2,3],[0,1,2,3],[0,1,2,3],[0,1,3,2],[0,1,3,2],[0,1,3,2],[0,2,1,3],[0,2,1,3],[0,2,1,3],[0,2,3,1],[0,2,3,1],[0,2,3,1],[0,3,1,2],[0,3,1,2],[0,3,1,2],[0,3,2,1],[0,3,2,1],[0,3,2,1],[1,0,2,3],[1,0,2,3],[1,0,2,3],[1,0,3,2],[1,0,3,2],[1,0,3,2],[1,2,0,3],[1,2,0,3],[1,2,0,3],[1,2,3,0],[1,2,3,0],[1,2,3,0],[1,3,0,2],[1,3,0,2],[1,3,0,2],[1,3,2,0],[1,3,2,0],[1,3,2,0],[2,0,1,3],[2,0,1,3],[2,0,1,3],[2,0,3,1],[2,0,3,1],[2,0,3,1],[2,1,0,3],[2,1,0,3],[2,1,0,3],[2,1,3,0],[2,1,3,0],[2,1,3,0],[2,3,0,1],[2,3,0,1],[2,3,0,1],[2,3,1,0],[2,3,1,0],[2,3,1,0],[3,0,1,2],[3,0,1,2],[3,0,1,2],[3,0,2,1],[3,0,2,1],[3,0,2,1],[3,1,0,2],[3,1,0,2],[3,1,0,2],[3,1,2,0],[3,1,2,0],[3,1,2,0],[3,2,0,1],[3,2,0,1],[3,2,0,1],[3,2,1,0],[3,2,1,0],[3,2,1,0],[0,1,2,3],[0,1,2,3],[0,1,2,3],[0,1,2,3]],"space_name":["Bogotá","Cali","Medellín","Bucaramanga","Ibagué","Santa Marta","Cartagena","Cúcuta","Neiva","Pasto","Sincelejo","Atlántico","Chocó","Nariño","Meta West","Guaviare","Putumayo","Cesar","Antioquia","Santander","Huila","Arauca","Meta East","Vichada","Guainía","Vaupés","Amazonas","Panamá","Ecuador","Brasil","Perú","Venezuela","Santa Marta / Cartagena","Cali / Pasto","Neiva / Pasto","Bogotá / San José","Pasto / Tumaco","Cali / Buenaventura","Cartagena / Sincelejo","Sincelejo / Medellín","Medellín / Ibagué","Ibagué / Cali","Bucaramanga / Ibagué / Bogotá","Bogotá / Neiva","Cúcuta / Arauca","Bogotá / Yopal","Santa Marta / Ayacucho","Bucaramanga / Ayacucho","Sincelejo / Ayacucho","Cúcuta / Ayacucho"],"spaces":[{"type":"city","id":"Bogota","pop":8,"adjacent":[14,19,20,22,35,42,43,45]},{"type":"city","id":"Cali","pop":3,"adjacent":[12,13,20,33,37,41]},{"type":"city","id":"Medellin","pop":3,"adjacent":[12,18,39,40]},{"type":"city","id":"Bucaramanga","pop":2,"adjacent":[18,19,42,47]},{"type":"city","id":"Ibague","pop":2,"adjacent":[12,18,20,40,41,42]},{"type":"city","id":"Santa Marta","pop":2,"adjacent":[11,17,32,46]},{"type":"city","id":"Cartagena","pop":1,"adjacent":[11,32,38]},{"type":"city","id":"Cucuta","pop":1,"adjacent":[19,31,44,49]},{"type":"city","id":"Neiva","pop":1,"adjacent":[14,16,20,34,43]},{"type":"city","id":"Pasto","pop":1,"adjacent":[13,16,20,28,33,34,36]},{"type":"city","id":"Sincelejo","pop":1,"adjacent":[11,12,18,38,39,48]},{"type":"forest","id":"Atlantico","pop":1,"adjacent":[5,6,10,17,18,19,32,38,46,47,48,49]},{"type":"forest","id":"Choco","pop":1,"adjacent":[1,2,4,10,13,18,20,27,37,39,40,41]},{"type":"forest","id":"Narino","pop":1,"adjacent":[1,9,12,20,28,33,36,37]},{"type":"forest","id":"Meta West","pop":1,"adjacent":[0,8,15,16,20,22,35,43]},{"type":"forest","id":"Guaviare","pop":1,"adjacent":[14,16,22,23,24,25,35]},{"type":"forest","id":"Putumayo","pop":1,"adjacent":[8,9,14,15,20,25,26,28,30,34]},{"type":"mountain","id":"Cesar","pop":1,"adjacent":[5,11,18,19,31,46,47,48,49]},{"type":"mountain","id":"Antioquia","pop":2,"adjacent":[2,3,4,10,11,12,17,19,20,39,40,42,46,47,48,49]},{"type":"mountain","id":"Santander","pop":2,"adjacent":[0,3,7,11,17,18,20,21,22,31,42,44,45,46,47,48,49]},{"type":"mountain","id":"Huila","pop":2,"adjacent":[0,1,4,8,9,12,13,14,16,18,19,33,34,41,42,43]},{"type":"grassland","id":"Arauca","pop":1,"adjacent":[19,22,23,31,44,45]},{"type":"grassland","id":"Meta East","pop":1,"adjacent":[0,14,15,19,21,23,35,45]},{"type":"grassland","id":"Vichada","pop":0,"adjacent":[15,21,22,24,31]},{"type":"forest","id":"Guainia","pop":0,"adjacent":[15,23,25,29,31]},{"type":"forest","id":"Vaupes","pop":0,"adjacent":[15,16,24,26,29]},{"type":"forest","id":"Amazonas","pop":0,"adjacent":[16,25,29,30]},{"type":"foreign","id":"Panama","pop":0,"adjacent":[12]},{"type":"foreign","id":"Ecuador","pop":0,"adjacent":[9,13,16,36]},{"type":"foreign","id":"Brasil","pop":0,"adjacent":[24,25,26]},{"type":"foreign","id":"Peru","pop":0,"adjacent":[16,26]},{"type":"foreign","id":"Venezuela","pop":0,"adjacent":[7,17,19,21,23,24,44,49]},{"type":"road","id":"Santa Marta / Cartagena","econ":1,"adjacent":[5,6,11]},{"type":"road","id":"Cali / Pasto","econ":1,"adjacent":[1,9,13,20]},{"type":"road","id":"Neiva / Pasto","econ":1,"adjacent":[8,9,16,20]},{"type":"road","id":"Bogota / San Jose","econ":1,"adjacent":[14,15,22]},{"type":"road","id":"Pasto / Tumaco","econ":1,"adjacent":[9,13,28]},{"type":"pipeline","id":"Cali / Buenaventura","econ":1,"adjacent":[1,12,13]},{"type":"pipeline","id":"Cartagena / Sincelejo","econ":1,"adjacent":[6,10,11]},{"type":"pipeline","id":"Sincelejo / Medellin","econ":2,"adjacent":[2,10,12,18]},{"type":"pipeline","id":"Medellin / Ibague","econ":1,"adjacent":[2,4,12,18]},{"type":"pipeline","id":"Ibague / Cali","econ":1,"adjacent":[1,4,12,20]},{"type":"pipeline","id":"Bucaramanga / Ibague / Bogota","econ":2,"adjacent":[3,4,18,19,20]},{"type":"pipeline","id":"Bogota / Neiva","econ":2,"adjacent":[8,14,20]},{"type":"pipeline","id":"Cucuta / Arauca","econ":3,"adjacent":[7,19,21,21,31]},{"type":"pipeline","id":"Bogota / Yopal","econ":2,"adjacent":[19,21,22]},{"type":"pipeline","id":"Santa Marta / Ayacucho","econ":2,"adjacent":[5,11,17,18,19,47,48,49]},{"type":"pipeline","id":"Bucaramanga / Ayacucho","econ":2,"adjacent":[3,11,17,18,19,46,48,49]},{"type":"pipeline","id":"Sincelejo / Ayacucho","econ":3,"adjacent":[10,11,17,18,19,46,47,49]},{"type":"pipeline","id":"Cucuta / Ayacucho","econ":3,"adjacent":[7,11,17,18,19,31,46,47,48]}],"first_piece":[[0,3,3,33,0],[63,72],[102,108],[126,141]],"last_piece":[[2,2,32,62,-1],[71,101],[107,125],[140,152]]}
if (typeof module !== 'undefined') module.exports = data
diff --git a/images/back-forth.svg b/images/back-forth.svg
new file mode 100644
index 0000000..e76e9b1
--- /dev/null
+++ b/images/back-forth.svg
@@ -0,0 +1 @@
+<svg style="height: 512px; width: 512px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M0 0h512v512H0z" fill="#ffffff" fill-opacity="0"></path><g class="" style="" transform="translate(0,0)"><path d="M241.844 28.625l-21.188 5.063L33.25 78.53l-9.594 2.282 2.813 9.47 54.718 184.03 6.156 20.782 10.875-18.75 36.624-63.125 39.344 22.655 9.375-16.188-47.47-27.312L128 187.72l-4.656 8.06-30.406 52.47-45.75-153.844 156.625-37.47-30.344 52.345-4.69 8.126 8.126 4.656L332.75 211.75l-17.594 30.344 16.22 9.312 22.25-38.375 4.687-8.124-8.125-4.656-155.844-89.688 36.594-63.093 10.906-18.845zm-28.25 176.47l-57.438 99.31 155.22 89.5 8.093 4.658-4.69 8.093-44.06 76.25 218.81-52.5-63.874-215.47-44.094 76.25-4.656 8.064-8.094-4.656-155.218-89.5z" fill="#000000" fill-opacity="1"></path></g></svg> \ No newline at end of file
diff --git a/images/shaking-hands.svg b/images/shaking-hands.svg
new file mode 100644
index 0000000..f3b76cd
--- /dev/null
+++ b/images/shaking-hands.svg
@@ -0,0 +1 @@
+<svg style="height: 512px; width: 512px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M0 0h512v512H0z" fill="#ffffff" fill-opacity="0"></path><g class="" style="" transform="translate(0,0)"><path d="M494 61.363l-82.58 77.934 78.994 132.96 3.586-4.458V61.362zM18 62.5v225.893c4.48.582 9.863.903 15.295.96 11.87.125 21.654-.65 27.15-1.144L113.1 154.974 18 62.5zm389.154 104.86l-7.04 4.556c-.15.097-5.362 3.336-6.893 4.29l-10.605 6.42.15.09c-4.914 3.057-6.28 3.917-11.857 7.38-2.83 1.757-2.9 1.798-5.584 3.465-20.29-10.907-42.306-19.29-67.998-25.882-32.312 9.762-66.542 23.888-100.722 37.142 14.19 17.087 29.96 22.651 45.845 22.85 18.42.23 37.25-7.78 50.218-16.754l7.4-5.12 7.426 10.73 115.453 83.33 45.112-29.987-60.906-102.51zM126.477 170.1L81.11 284.887 97.76 297.69l30.795-34.905 2.467-2.795 3.72-.232c1.5-.094 2.98-.138 4.44-.13 10.212.066 19.342 2.716 26.19 8.76 5.072 4.472 8.444 10.426 10.4 17.32l2.28-.142c11.995-.75 22.802 1.725 30.63 8.63 7.827 6.907 11.63 17.323 12.38 29.32l.07 1.08c6.44 1.216 12.205 3.752 16.893 7.888 7.828 6.906 11.63 17.32 12.38 29.317l.197 3.12c.642.202 1.275.424 1.9.658l2.033-2.853 5.47-7.678 2.813-3.95 7.33 5.223 59.428 42.336c6.464-1.594 10.317-4.075 12.46-7.086 2.147-3.012 3.233-7.47 2.624-14.107l-71.258-51.03-7.318-5.24 5.19-7.246 6.67-9.365 7.33 5.223 80.335 57.226c6.464-1.593 10.32-4.074 12.463-7.085 2.144-3.01 3.23-7.457 2.625-14.082l-92.398-65.55-7.34-5.21 10.414-14.68 7.343 5.208 92.414 65.565c6.47-1.594 10.327-4.075 12.473-7.088 2.148-3.015 3.233-7.476 2.62-14.125l-110.44-79.71c-14.655 8.688-33.402 15.648-53.557 15.396-23.587-.295-48.817-11.566-67.377-40.05a9 9 0 0 1 4.343-13.327c13.014-4.945 26.163-10.17 39.343-15.354l-92.056-6.834zm12.902 107.62l-47.564 53.91c.927 6.746 3.04 10.942 5.887 13.454 2.847 2.512 7.275 4.085 14.084 4.164l47.563-53.908c-.927-6.747-3.04-10.945-5.887-13.457-2.847-2.512-7.274-4.084-14.084-4.162zm43.308 25.81l-53.713 60.88c.926 6.747 3.04 10.945 5.886 13.457 2.85 2.51 7.275 4.083 14.085 4.16l53.713-60.878c-.926-6.748-3.04-10.944-5.887-13.457-2.846-2.512-7.273-4.085-14.083-4.164zm29.34 38.286l-47.56 53.91c.927 6.746 3.04 10.943 5.887 13.456 2.848 2.512 7.275 4.083 14.084 4.162L232 359.44c-.927-6.75-3.04-10.947-5.887-13.46-2.847-2.512-7.274-4.083-14.084-4.162zm24.702 39.137l-38.794 44.28c.925 6.76 3.038 10.962 5.888 13.476 2.845 2.51 7.267 4.082 14.067 4.163l38.796-44.28c-.926-6.758-3.04-10.96-5.89-13.476-2.844-2.51-7.266-4.08-14.066-4.162zm35.342 4.79c1.694 4.62 2.673 9.74 3.014 15.192l.232 3.704-8.277 9.448 26.724 19.037c6.464-1.594 10.316-4.075 12.46-7.086 2.145-3.01 3.233-7.464 2.628-14.093l-36.78-26.2z" fill="#000000" fill-opacity="1"></path></g></svg> \ No newline at end of file
diff --git a/play.css b/play.css
index 5958485..2cf2615 100644
--- a/play.css
+++ b/play.css
@@ -57,31 +57,36 @@ main { background-color: #777; }
#cities { position: absolute; }
#boxes { position: absolute; }
-path { fill: transparent; }
-path.action { fill: white; fill-opacity: 0.3; stroke: white; stroke-width: 4; }
+path { fill: transparent; stroke-width: 4; }
+path.action { fill: white; fill-opacity: 0.3; stroke: white; }
path.action.foreign { fill: gold; stroke: lemonchiffon; }
path.action.forest { fill: limegreen; stroke: limegreen; }
path.action.grassland { fill: greenyellow; stroke: greenyellow; }
path.action.mountain { fill: chocolate; stroke: chocolate; }
+path.selected { stroke: yellow; }
-.box{position:absolute;box-sizing:border-box;}
-.box.action{z-index:100; border:4px solid lemonchiffon;}
+.box{position:absolute;box-sizing:border-box;border:4px solid transparent}
.box.city{border-radius:50%}
-.box.city.action{background-color:#6495ed55;border:4px solid cornflowerblue;}
.box.loc{border-radius:50%}
-.box.loc.action{background-color:#fff5;border:4px solid white;}
+.box.action{border-color:lemonchiffon;}
+.box.city.action{background-color:#6495ed55;border-color:deepskyblue;}
+.box.loc.action{background-color:#fff5;border-color:white;}
+.box.selected{border-color:yellow;}
+
-.piece { position: absolute; }
-.piece { background-position: center; background-repeat: no-repeat; background-size: contain; }
.piece {
+ position: absolute;
+ pointer-events: none;
+ background-position: center;
+ background-repeat: no-repeat;
+ background-size: contain;
transition-property: top, left;
transition-duration: 700ms;
transition-timing-function: ease;
-}
-.piece {
filter: drop-shadow(0 1px 2px #000a);
}
.piece.action {
+ pointer-events: auto;
filter:
drop-shadow(0 -2px 0 white)
drop-shadow(0 2px 0 white)
@@ -91,6 +96,7 @@ path.action.mountain { fill: chocolate; stroke: chocolate; }
}
.token {
+ pointer-events: none;
position: absolute;
transition-property: top, left;
transition-duration: 700ms;
@@ -103,6 +109,10 @@ path.action.mountain { fill: chocolate; stroke: chocolate; }
height: 45px;
}
+.token.action {
+ pointer-events: auto;
+}
+
.token.terror,
.token.sabotage,
.token.active_opposition,
diff --git a/play.html b/play.html
index 33d1584..46a5f2d 100644
--- a/play.html
+++ b/play.html
@@ -37,6 +37,16 @@
<div class="icon_button" onclick="toggle_pieces()"><img src="/images/earth-america.svg"></div>
<div class="icon_button" onclick="toggle_zoom()"><img src="/images/magnifying-glass.svg"></div>
<div class="icon_button" onclick="toggle_log()"><img src="/images/scroll-quill.svg"></div>
+ <div id="trade_menu" class="menu">
+ <div class="menu_title"><img src="images/shaking-hands.svg"></div>
+ <div class="menu_popup">
+ <div id="trade_govt" class="menu_item" onclick="send_action('trade',0)">Government</div>
+ <div id="trade_farc" class="menu_item" onclick="send_action('trade',1)">FARC</div>
+ <div id="trade_auc" class="menu_item" onclick="send_action('trade',2)">AUC</div>
+ <div id="trade_cartels" class="menu_item" onclick="send_action('trade',3)">Cartels</div>
+ </div>
+ </div>
+ <div id="remove_button" class="icon_button" onclick="send_action('remove')"><img src="images/back-forth.svg"></div>
</div>
<div id="prompt"></div>
<div id="actions"></div>
diff --git a/play.js b/play.js
index b13418c..c4a3733 100644
--- a/play.js
+++ b/play.js
@@ -726,6 +726,7 @@ function on_update() {
console.log("NO SPACE", s, data.space_name[s])
ui.spaces[s].classList.toggle("action", is_action("space", s))
+ ui.spaces[s].classList.toggle("selected", view.where === s)
}
list.length = 0
@@ -779,11 +780,21 @@ function on_update() {
action_button("kidnap", "Process")
action_button("kidnap", "Bribe")
+ // Train/Rally sub-actions
+ action_button("move", "Move")
+ action_button("flip", "Flip")
+ action_button("base", "Base")
+ action_button("civic", "Civic Action")
+
action_button("unshaded", "Unshaded")
action_button("shaded", "Shaded")
action_button("event", "Event")
action_button("limop", "LimOp")
+
+ action_button("next", "Next")
+ action_button("done", "Done")
+ action_button("remove", "Remove")
action_button("undo", "Undo")
}
diff --git a/rules.js b/rules.js
index d8e921d..831377d 100644
--- a/rules.js
+++ b/rules.js
@@ -28,22 +28,25 @@ const BASE = 0
const GUERRILLA = 1
const TROOPS = 2
const POLICE = 3
+const CUBE = 4
const space_name = data.space_name
const first_piece = data.first_piece
const last_piece = data.last_piece
+const first_space = data.first_space
+const last_space = data.last_space
const first_pop = data.first_pop
const first_city = data.first_city
const last_city = data.last_city
const first_dept = data.first_dept
const last_pop = data.last_pop
const last_dept = data.last_dept
-const first_foreign = data.first_foreign
-const last_foreign = data.last_foreign
const first_loc = data.first_loc
const last_loc = data.last_loc
+const first_foreign = data.first_foreign
+const last_foreign = data.last_foreign
// Sequence of Play options
const ELIGIBLE = 0
@@ -179,8 +182,8 @@ exports.setup = function (seed, scenario, options) {
current: 0,
state: null,
- op_spaces: null,
- sa_spaces: null,
+ op: null,
+ sa: null,
deck: [],
president: 0,
@@ -293,6 +296,9 @@ function setup_standard() {
place_piece(CARTELS, BASE, 1, META_WEST)
place_piece(CARTELS, BASE, 1, GUAVIARE)
place_piece(CARTELS, BASE, 2, PUTUMAYO)
+
+ // XXX
+ set_support(BOGOTA, ACTIVE_OPPOSITION)
}
function setup_quick() {
@@ -386,7 +392,7 @@ function remove_piece(faction, type, count, where) {
}
}
-function count_pieces_imp(s, faction, type) {
+function count_pieces(s, faction, type) {
let first = first_piece[faction][type]
let last = last_piece[faction][type]
let n = 0
@@ -396,19 +402,53 @@ function count_pieces_imp(s, faction, type) {
return n
}
+function has_piece(s, faction, type) {
+ let first = first_piece[faction][type]
+ let last = last_piece[faction][type]
+ for (let p = first; p <= last; ++p)
+ if (game.pieces[p] === s)
+ return true
+ return false
+}
+
+function has_active_guerrilla(s, faction) {
+ let first = first_piece[faction][GUERRILLA]
+ let last = last_piece[faction][GUERRILLA]
+ for (let p = first; p <= last; ++p)
+ if (game.pieces[p] === s && !is_underground(p))
+ return true
+ return false
+}
+
+function count_bases(s) {
+ return (
+ count_pieces(s, GOVT, BASE) +
+ count_pieces(s, FARC, BASE) +
+ count_pieces(s, AUC, BASE) +
+ count_pieces(s, CARTELS, BASE)
+ )
+}
+
+function count_cubes(s) {
+ return (
+ count_pieces(s, GOVT, TROOPS) +
+ count_pieces(s, GOVT, POLICE)
+ )
+}
+
function update_control() {
game.govt_control = 0
game.farc_control = 0
- for (let s = 0; s <= last_dept; ++s) {
- let g = count_pieces_imp(s, GOVT, BASE) +
- count_pieces_imp(s, GOVT, TROOPS) +
- count_pieces_imp(s, GOVT, POLICE)
- let f = count_pieces_imp(s, FARC, BASE) +
- count_pieces_imp(s, FARC, GUERRILLA)
- let a = count_pieces_imp(s, AUC, BASE) +
- count_pieces_imp(s, AUC, GUERRILLA)
- let c = count_pieces_imp(s, CARTELS, BASE) +
- count_pieces_imp(s, CARTELS, GUERRILLA)
+ for (let s = first_space; s <= last_dept; ++s) {
+ let g = count_pieces(s, GOVT, BASE) +
+ count_pieces(s, GOVT, TROOPS) +
+ count_pieces(s, GOVT, POLICE)
+ let f = count_pieces(s, FARC, BASE) +
+ count_pieces(s, FARC, GUERRILLA)
+ let a = count_pieces(s, AUC, BASE) +
+ count_pieces(s, AUC, GUERRILLA)
+ let c = count_pieces(s, CARTELS, BASE) +
+ count_pieces(s, CARTELS, GUERRILLA)
if (g > a + c + f)
game.govt_control |= (1 << s)
else if (f > g + a + c)
@@ -417,11 +457,19 @@ function update_control() {
}
function is_city(s) {
- return s <= last_city
+ return s >= first_city && s <= last_city
}
-function has_govt_base(s) {
- return set_has(game.govt.bases, s)
+function is_dept(s) {
+ return s >= first_dept && s <= last_dept
+}
+
+function is_loc(s) {
+ return s >= first_loc && s <= last_loc
+}
+
+function is_adjacent(a, b) {
+ return set_has(data.spaces[a].adjacent, b)
}
function is_underground(p) {
@@ -451,12 +499,42 @@ function set_active(p) {
}
}
+function has_govt_control(s) {
+ return game.govt_control & (1 << s)
+}
+
+function has_farc_control(s) {
+ return game.farc_control & (1 << s)
+}
+
+function can_govt_civic_action(s) {
+ return game.support[s] < 2 && has_govt_control(s) && has_piece(s, GOVT, TROOPS) && has_piece(s, GOVT, POLICE)
+}
+
+function for_each_piece(faction, type, f) {
+ let p0 = first_piece[faction][type]
+ let p1 = last_piece[faction][type]
+ for (let p = p0; p <= p1; ++p)
+ f(p)
+}
+
+function gen_piece_in_space(faction, type, space) {
+ for_each_piece(faction, type, p => {
+ if (game.pieces[p] === space)
+ gen_action("piece", p)
+ })
+}
+
// === SEQUENCE OF PLAY ===
function this_card() {
return game.deck[0]
}
+function is_final_card() {
+ return false
+}
+
function goto_card() {
if (this_card() > 72)
goto_propaganda_card()
@@ -587,7 +665,7 @@ states.eligible2 = {
goto_pass()
break
case SOP_2ND_LIMOP:
- goto_op_only()
+ goto_limop()
break
case SOP_2ND_LIMOP_OR_EVENT:
goto_limop_or_event()
@@ -635,43 +713,100 @@ function goto_event() {
function goto_op_only() {
log_h2(faction_name[game.current] + " - Op Only")
- game.state = "op"
- game.op_spaces = []
- game.sa_spaces = null
+ goto_operation()
}
function goto_op_and_sa() {
log_h2(faction_name[game.current] + " - Op + Special")
- game.state = "op"
- game.op_spaces = []
- game.sa_spaces = []
+ goto_operation()
}
function goto_limop() {
log_h2(faction_name[game.current] + " - LimOp")
+ goto_operation()
+}
+
+function goto_operation() {
game.state = "op"
- game.op_spaces = []
- game.sa_spaces = null
+ game.op = {
+ spaces: [],
+ pieces: [],
+ count: 0,
+ }
+}
+
+function end_operation() {
+ game.op = null
+ resume_event_card()
}
function can_use_special_activity() {
- let faction = current_faction()
- if (faction.cylinder === SOP_1ST_OP_AND_SA || faction.cylinder === SOP_2ND_OP_AND_SA)
+ if (game.cylinder[game.current] === SOP_1ST_OP_AND_SA || game.cylinder[game.current] === SOP_2ND_OP_AND_SA)
+ return true
+ return false
+}
+
+function is_limop() {
+ if (game.cylinder[game.current] === SOP_2ND_LIMOP || game.cylinder[game.current] === SOP_2ND_LIMOP_OR_EVENT)
return true
return false
}
+function action_remove() {
+ push_undo()
+ game.save_state = game.state
+ game.state = "remove"
+}
+
+states.remove = {
+ prompt() {
+ view.prompt = "Remove pieces to Available Forces."
+ for (let p = first_piece[game.current][BASE]; p <= last_piece[game.current][BASE]; ++p)
+ if (game.pieces[p] !== AVAILABLE)
+ gen_action("piece", p)
+ if (game.current === GOVT) {
+ for (let p = first_piece[game.current][TROOPS]; p <= last_piece[game.current][TROOPS]; ++p)
+ if (game.pieces[p] !== AVAILABLE)
+ gen_action("piece", p)
+ for (let p = first_piece[game.current][POLICE]; p <= last_piece[game.current][POLICE]; ++p)
+ if (game.pieces[p] !== AVAILABLE)
+ gen_action("piece", p)
+ } else {
+ for (let p = first_piece[game.current][GUERRILLA]; p <= last_piece[game.current][GUERRILLA]; ++p)
+ if (game.pieces[p] !== AVAILABLE)
+ gen_action("piece", p)
+ }
+ view.actions.done = 1
+ },
+ piece(p) {
+ game.pieces[p] = AVAILABLE
+ update_control()
+ },
+ done() {
+ game.state = game.save_state
+ game.save_state = 0
+ },
+}
+
+// === OPERATIONS ===
+
states.op = {
prompt() {
view.prompt = "Choose an Operation."
if (game.current === GOVT) {
view.actions.train = 1
view.actions.patrol = 1
- view.actions.sweep = 1
+ if (is_final_card())
+ view.actions.sweep = 0
+ else
+ view.actions.sweep = 1
view.actions.assault = 1
} else {
view.actions.rally = 1
- view.actions.march = 1
+ if (is_final_card())
+ view.actions.march = 0
+ else
+ view.actions.march = 1
view.actions.attack = 1
view.actions.terror = 1
}
@@ -708,6 +843,7 @@ states.op = {
push_undo()
log_h3("March")
game.state = "march"
+ game.op.pieces = []
},
attack() {
push_undo()
@@ -719,101 +855,545 @@ states.op = {
log_h3("Terror")
game.state = "terror"
},
+}
+
+function gen_operation_common() {
+ if (game.op.spaces.length > 0)
+ view.actions.done = 1
+ else
+ view.actions.done = 0
+ view.actions.remove = 1
+}
+
+// OPERATION: TRAIN
+
+states.train = {
+ prompt() {
+ view.prompt = "Train: Place cubes first, then replace with Base or buy Civic Action."
+
+ if (can_use_special_activity()) {
+ view.actions.air_lift = 1
+ view.actions.eradicate = 1
+ }
+
+ // Any Departments or Cities
+ if (game.resources[game.current] >= 3) {
+ for (let s = first_space; s <= last_dept; ++s) {
+ if (is_city(s) || has_piece(s, GOVT, BASE))
+ if (!set_has(game.op.spaces, s))
+ gen_action("space", s)
+ }
+ }
+
+ // place base
+ view.actions.base = 1
+
+ // buy civic action
+ view.actions.civic = 1
- remove() {
+ gen_operation_common()
+ },
+ space(s) {
+ push_undo()
+
+ logi(`S${s}.`)
+
+ game.resources[game.current] -= 3
+ set_add(game.op.spaces, s)
+
+ game.state = "train_place"
+ game.op.where = s
+ game.op.count = 6
+ },
+ base() {
+ push_undo()
+ game.state = "train_base"
+ game.op.where = -1
+ },
+ civic() {
push_undo()
- game.save_state = game.state
- game.state = "remove"
+ game.state = "train_civic"
+ game.op.where = -1
},
+ done() {
+ end_operation()
+ },
+ air_lift: goto_air_lift,
+ eradicate: goto_eradicate,
}
-function for_each_own_piece(f) {
- f(p)
+states.train_place = {
+ prompt() {
+ view.prompt = `Train in ${space_name[game.op.where]}: Place up to ${game.op.count} cubes.`
+ view.where = game.op.where
+
+ if (game.op.count > 0) {
+ gen_piece_in_space(GOVT, POLICE, AVAILABLE)
+ gen_piece_in_space(GOVT, TROOPS, AVAILABLE)
+ }
+
+ view.actions.next = 1
+ view.actions.remove = 1
+ },
+ piece(p) {
+ push_undo()
+ game.pieces[p] = game.op.where
+ if (--game.op.count == 0)
+ game.state = "train"
+ update_control()
+ },
+ next() {
+ game.op.count = 0
+ game.state = "train"
+ },
}
-states.remove = {
+states.train_base = {
prompt() {
- view.prompt = "Remove pieces to Available Forces."
- for_each_own_piece(p => {
- if (game.pieces[p] !== AVAILABLE)
- gen_action("piece", p)
- })
- view.actions.done = 1
+ if (game.op.where < 0) {
+ view.prompt = `Train: Replace 3 cubes with a Base.`
+ for (let s = first_space; s <= last_dept; ++s) {
+ if (set_has(game.op.spaces, s) || game.resources[game.current] >= 3)
+ if (count_bases(s) < 2 && count_cubes(s) >= 3)
+ gen_action("space", s)
+ }
+ } else {
+ if (game.op.count < 0) {
+ view.prompt = `Train: All done.`
+ view.actions.done = 1
+ } else if (game.op.count > 0) {
+ view.prompt = `Train: Replace ${game.op.count} cubes with a Base.`
+ gen_piece_in_space(GOVT, POLICE, game.op.where)
+ gen_piece_in_space(GOVT, TROOPS, game.op.where)
+ } else {
+ view.prompt = `Train: Place Base.`
+ gen_piece_in_space(GOVT, BASE, AVAILABLE)
+ }
+ }
+ },
+ space(s) {
+ push_undo()
+ if (!set_has(game.op.spaces, s)) {
+ game.resources[game.current] -= 3
+ set_add(game.op.spaces, s)
+ }
+ game.op.where = s
+ game.op.count = 3
},
piece(p) {
push_undo()
- game.pieces[p] = AVAILABLE
+ if (game.op.count > 0)
+ game.pieces[p] = AVAILABLE
+ else
+ game.pieces[p] = game.op.where
+ --game.op.count
+ update_control()
},
- done() {
- game.state = game.save_state
- game.save_state = 0
- }
+ done: end_operation,
}
-states.train = {
+states.train_civic = {
prompt() {
- let faction = current_faction()
+ let res = game.resources[game.current]
+ if (game.op.where < 0) {
+ view.prompt = `Train: Buy Civic Action.`
+ if (res >= 3) {
+ for (let s = first_space; s <= last_dept; ++s) {
+ if (set_has(game.op.spaces, s) || res >= 6)
+ if (can_govt_civic_action(s))
+ gen_action("space", s)
+ }
+ }
+ } else {
+ view.prompt = `Train: Buy Civic Action in ${space_name[game.op.where]}.`
+ view.where = game.op.where
+ if (res >= 3 && can_govt_civic_action(game.op.where))
+ gen_action("space", game.op.where)
+ else
+ view.prompt = `Train: All done.`
+ view.actions.done = 1
+ }
+ },
+ space(s) {
+ push_undo()
+ if (!set_has(game.op.spaces, s)) {
+ game.resources[game.current] -= 3
+ set_add(game.op.spaces, s)
+ }
+ game.op.where = s
+ game.resources[game.current] -= 3
+ game.support[game.op.where] += 1
+ },
+ done: end_operation,
+}
- view.prompt = "Train: Select spaces."
+// OPERATION: RALLY
+
+function rally_count() {
+ if (has_piece(game.op.where, game.current, BASE))
+ return data.spaces[game.op.where].pop + count_pieces(game.op.where, game.current, BASE)
+ return 1
+}
+
+states.rally = {
+ prompt() {
+ if (game.current === FARC)
+ view.prompt = "Rally: Select City or Department without Support."
+ else if (game.current === AUC)
+ view.prompt = "Rally: Select City or Department without Opposition."
+ else
+ view.prompt = "Rally: Select City or Department."
if (can_use_special_activity()) {
- view.actions.air_lift = 1
- view.actions.eradicate = 1
+ if (game.current === FARC)
+ view.actions.extort = 1
+ if (game.current === AUC)
+ view.actions.extort = 1
+ if (game.current === CARTELS) {
+ view.actions.cultivate = 1
+ view.actions.process = 1
+ view.actions.bribe = 1
+ }
}
- // Any Departments or Cities
- if (faction.resources >= 3) {
- for (let s = 0; s <= last_dept; ++s) {
- if (!set_has(game.op_spaces, s))
- gen_action("space", s)
+ // Departments or Cities
+ if (game.resources[game.current] >= 1) {
+ for (let s = first_space; s <= last_dept; ++s) {
+ if (set_has(game.op.spaces, s))
+ continue
+
+ // FARC: without Support
+ if (game.current === FARC)
+ if (game.support[s] > 0)
+ continue
+
+ // AUC: without Opposition
+ if (game.current === AUC)
+ if (game.support[s] < 0)
+ continue
+
+ gen_action("space", s)
}
}
+
+ gen_operation_common()
},
- air_lift() {
+ space(s) {
push_undo()
- game.state = "air_lift"
+
+ logi(`S${s}.`)
+ game.resources[game.current] -= 1
+ set_add(game.op.spaces, s)
+
+ game.state = "rally_place"
+ game.op.where = s
+ game.op.count = rally_count()
},
- eradicate() {
+ done: end_operation,
+ extort: goto_extort,
+ cultivate: goto_cultivate,
+ process: goto_process,
+ bribe: goto_bribe,
+}
+
+states.rally_place = {
+ prompt() {
+ view.prompt = `Rally: Place up to ${game.op.count} Guerrillas.`
+ view.where = game.op.where
+ view.actions.remove = 1
+
+ if (game.op.count === rally_count()) {
+ view.actions.base = 0
+ view.actions.flip = 0
+ view.actions.move = 0
+ if (count_pieces(game.op.where, game.current, GUERRILLA) >= 2) {
+ if (count_bases(game.op.where) < 2)
+ view.actions.base = 1
+ }
+ if (has_piece(game.op.where, game.current, BASE)) {
+ if (has_active_guerrilla(game.op.where, game.current))
+ view.actions.flip = 1
+ view.actions.move = 1
+ }
+ }
+
+ view.actions.next = 1
+
+ gen_piece_in_space(game.current, GUERRILLA, AVAILABLE)
+ },
+ piece(p) {
push_undo()
- game.state = "eradicate"
+ set_underground(p)
+ game.pieces[p] = game.op.where
+ if (--game.op.count === 0)
+ game.state = "rally"
+ update_control()
},
- space(s) {
+ base() {
push_undo()
- logi(`S${s}.`)
- let faction = current_faction()
- faction.resources -= 3
- set_add(game.op_spaces, s)
- game.where = s
- if (is_city(s) || has_govt_base(s)) {
- game.state = "train_place_cubes"
- game.selected = -1
- game.count = 6
+ logi("Base.")
+ game.state = "rally_base"
+ game.op.count = 2
+ },
+ flip() {
+ push_undo()
+ logi("Flipped.")
+ for_each_piece(game.current, GUERRILLA, p => {
+ if (game.pieces[p] === game.op.where)
+ set_underground(p)
+ })
+ game.state = "rally"
+ },
+ move() {
+ push_undo()
+ logi("Moved.")
+ game.state = "rally_move"
+ game.op.count = 0
+ },
+ next() {
+ push_undo()
+ game.state = "rally"
+ },
+}
+
+states.rally_base = {
+ prompt() {
+ view.prompt = `Rally: Replace ${game.op.count} Guerrillas with a Base.`
+ if (game.op.count > 0)
+ gen_piece_in_space(game.current, GUERRILLA, game.op.where)
+ else
+ gen_piece_in_space(game.current, BASE, AVAILABLE)
+ },
+ piece(p) {
+ push_undo()
+ if (game.op.count > 0) {
+ set_underground(p)
+ game.pieces[p] = AVAILABLE
+ --game.op.count
} else {
- game.state = "train_base_or_civic"
+ game.pieces[p] = game.op.where
+ game.state = "rally"
}
+ update_control()
},
}
-function gen_select_available(action, list) {
- for (let i = list.length; i-- > 0; ) {
- if (list[i] === AVAILABLE) {
- gen_action(action, i)
- return
- }
+states.rally_move = {
+ prompt() {
+ view.where = game.op.where
+ view.actions.remove = 1
+
+ view.prompt = `Rally: Move any Guerrillas to ${space_name[game.op.where]}.`
+
+ for_each_piece(game.current, GUERRILLA, p => {
+ if (game.pieces[p] !== game.op.where && game.pieces[p] !== AVAILABLE)
+ gen_action("piece", p)
+ })
+
+ view.actions.next = 1
+ },
+ piece(p) {
+ push_undo()
+ game.pieces[p] = game.op.where
+ game.op.count++
+ update_control()
+ },
+ next() {
+ push_undo()
+ game.op.count = 0
+ game.state = "rally"
}
- for (let i = 0; i < list.length; ++i)
- gen_action(action, i)
}
-states.train_place_cubes = {
+// OPERATION: MARCH
+
+function can_march_to(to) {
+ for (let from of data.spaces[to].adjacent)
+ if (has_piece(from, game.current, GUERRILLA))
+ return true
+ return false
+}
+
+states.march = {
prompt() {
- view.prompt = `Train in ${space_name[game.where]}: Place up to ${game.count} cubes.`
+ view.prompt = `March: Move Guerrillas to Cities/Departments/LoCs.`
+
+ if (can_use_special_activity()) {
+ if (game.current === FARC)
+ view.actions.extort = 1
+ if (game.current === AUC)
+ view.actions.extort = 1
+ if (game.current === CARTELS) {
+ view.actions.cultivate = 1
+ view.actions.process = 1
+ view.actions.bribe = 1
+ }
+ }
+
+ // TODO: check if move is possible
- if (game.selected < 0) {
- gen_select_available("govt_police", game.govt.police)
- gen_select_available("govt_troops", game.govt.troops)
+ if (game.resources[game.current] >= 1) {
+ for (let s = first_space; s <= last_dept; ++s) {
+ if (set_has(game.op.spaces, s))
+ continue
+ if (can_march_to(s))
+ gen_action("space", s)
+ }
}
+
+ for (let s = first_loc; s <= last_loc; ++s) {
+ if (set_has(game.op.spaces, s))
+ continue
+ if (can_march_to(s))
+ gen_action("space", s)
+ }
+
+ gen_operation_common()
},
+ space(s) {
+ push_undo()
+
+ logi(`S${s}.`)
+
+ if (s <= last_dept)
+ game.resources[game.current] -= 1
+
+ set_add(game.op.spaces, s)
+
+ game.state = "march_move"
+ game.op.where = s
+ game.op.march = []
+ },
+ done: end_operation,
+}
+
+function may_activate_marching_guerrillas() {
+ if (is_loc(game.op.where))
+ return true
+ if (game.support[game.op.where] > 0)
+ return true
+ if (game.current === AUC && game.support[game.op.where] < 0)
+ return true
+ return false
+}
+
+function activate_marching_guerrillas(group) {
+ console.log("MARCH", group)
+ if (may_activate_marching_guerrillas()) {
+ let count = group.length
+ count += count_pieces(game.op.where, GOVT, TROOPS)
+ count += count_pieces(game.op.where, GOVT, POLICE)
+ if (game.current === AUC)
+ count += count_pieces(game.op.where, FARC, GUERRILLA)
+ if (count > 3)
+ for (let p of group)
+ set_active(p)
+ }
+}
+
+states.march_move = {
+ prompt() {
+ view.prompt = `March: Move Guerrillas to ${space_name[game.op.where]}.`
+ view.where = game.op.where
+
+ for_each_piece(game.current, GUERRILLA, p => {
+ // May not move more than once
+ if (set_has(game.op.pieces, p))
+ return // continue
+
+ let s = game.pieces[p]
+ if (is_adjacent(game.op.where, s))
+ gen_action("piece", p)
+ })
+
+ if (game.op.march.length > 0)
+ view.actions.next = 1
+ },
+ piece(p) {
+ push_undo()
+
+ let from = game.pieces[p]
+ let group = map_get(game.op.march, from, null)
+ if (!group)
+ group = []
+ set_add(group, p)
+ map_set(game.op.march, from, group)
+
+ activate_marching_guerrillas(group)
+
+ set_add(game.op.pieces, p)
+
+ game.pieces[p] = game.op.where
+
+ update_control()
+ },
+ next() {
+ push_undo()
+ game.op.march = 0
+ game.state = "march"
+ },
+}
+
+// === SPECIAL ACTIVITIES ===
+
+function goto_air_lift() {
+ push_undo()
+ game.save_state = game.state
+ game.state = "air_lift"
+}
+
+function goto_air_strike() {
+ push_undo()
+ game.save_state = game.state
+ game.state = "air_strike"
+}
+
+function goto_eradicate() {
+ push_undo()
+ game.save_state = game.state
+ game.state = "eradicate"
+}
+
+function goto_extort() {
+ push_undo()
+ game.save_state = game.state
+ game.state = "extort"
+}
+
+function goto_ambush() {
+ push_undo()
+ game.save_state = game.state
+ game.state = "ambush"
+}
+
+function goto_kidnap() {
+ push_undo()
+ game.save_state = game.state
+ game.state = "kidnap"
+}
+
+function goto_assassinate() {
+ push_undo()
+ game.save_state = game.state
+ game.state = "assassinate"
+}
+
+function goto_cultivate() {
+ push_undo()
+ game.save_state = game.state
+ game.state = "cultivate"
+}
+
+function goto_process() {
+ push_undo()
+ game.save_state = game.state
+ game.state = "process"
+}
+
+function goto_bribe() {
+ push_undo()
+ game.save_state = game.state
+ game.state = "bribe"
}
// === GAME OVER ===
@@ -958,13 +1538,18 @@ exports.view = function (state, role) {
exports.action = function (state, role, action, arg) {
load_game(state)
- // Object.seal(game) // XXX: don't allow adding properties
+
+ // XXX - don't allow adding properties
+ Object.seal(game) // XXX: don't allow adding properties
+
let S = states[game.state]
if (S && action in S) {
S[action](arg)
} else {
if (action === "undo" && game.undo && game.undo.length > 0)
pop_undo()
+ else if (action === "remove")
+ action_remove()
else
throw new Error("Invalid action: " + action)
}
@@ -977,36 +1562,6 @@ exports.is_checkpoint = function (a, b) {
// === COMMON LIBRARY ===
-// Packed array of small numbers in one word
-
-function pack1_get(word, n) {
- return (word >>> n) & 1
-}
-
-function pack2_get(word, n) {
- n = n << 1
- return (word >>> n) & 3
-}
-
-function pack4_get(word, n) {
- n = n << 2
- return (word >>> n) & 15
-}
-
-function pack1_set(word, n, x) {
- return (word & ~(1 << n)) | (x << n)
-}
-
-function pack2_set(word, n, x) {
- n = n << 1
- return (word & ~(3 << n)) | (x << n)
-}
-
-function pack4_set(word, n, x) {
- n = n << 2
- return (word & ~(15 << n)) | (x << n)
-}
-
function clear_undo() {
if (game.undo.length > 0)
game.undo = []
@@ -1254,3 +1809,4 @@ function map_delete(map, item) {
}
}
}
+
diff --git a/tools/gendata.js b/tools/gendata.js
index e7f5df6..4fd79a2 100644
--- a/tools/gendata.js
+++ b/tools/gendata.js
@@ -151,9 +151,12 @@ function def_town(name) {
function def_loc(type, econ, cities, depts) {
let name = cities.join(" / ")
let loc_names = cities.concat(depts)
+ for (let n of loc_names)
+ if (space_name.indexOf(n) < 0)
+ console.log("not a space: " + n)
let loc_spaces = loc_names.map(n => space_name.indexOf(n))
let ix = spaces.length
- spaces.push({ type, name, id: to_ascii(name), econ, adjacent: loc_spaces.filter(x=>x>0) })
+ spaces.push({ type, id: to_ascii(name), econ, adjacent: loc_spaces.filter(x=>x>0) })
for (let loc of loc_spaces)
if (loc >= 0)
add(spaces[loc].adjacent, ix)
@@ -169,7 +172,11 @@ function adjacent(an, bn) {
}
// Cities
+
+data.first_space = 0
+
data.first_pop = 1
+
data.first_city = spaces.length
def_space("city", 8, "Bogotá")
@@ -186,9 +193,10 @@ def_space("city", 1, "Sincelejo")
data.last_city = spaces.length-1
+// Departments
+
data.first_dept = spaces.length
-// Departments
def_space("forest", 1, "Atlántico")
def_space("forest", 1, "Chocó")
def_space("forest", 1, "Nariño")
@@ -211,12 +219,13 @@ def_space("forest", 0, "Amazonas")
data.last_dept = spaces.length-1
+// Foreign Countries
+
data.first_foreign = spaces.length
-// Foreign Countries
-def_space("foreign", 0, "Brasil")
-def_space("foreign", 0, "Ecuador")
def_space("foreign", 0, "Panamá")
+def_space("foreign", 0, "Ecuador")
+def_space("foreign", 0, "Brasil")
def_space("foreign", 0, "Perú")
def_space("foreign", 0, "Venezuela")
@@ -239,9 +248,9 @@ def_loc("pipeline", 2, [ "Sincelejo", "Medellín"], [ "Chocó", "Antioquia" ])
def_loc("pipeline", 1, [ "Medellín", "Ibagué"], [ "Chocó", "Antioquia" ])
def_loc("pipeline", 1, [ "Ibagué", "Cali"], [ "Chocó", "Huila" ])
-def_loc("pipeline", 2, [ "Bucaramanga", "Ibagué", "Bogotá" ], [ "Antioquia", "Santa", "Huila" ])
+def_loc("pipeline", 2, [ "Bucaramanga", "Ibagué", "Bogotá" ], [ "Antioquia", "Santander", "Huila" ])
def_loc("pipeline", 2, [ "Bogotá", "Neiva" ], [ "Huila", "Meta West" ])
-def_loc("pipeline", 3, [ "Cúcuta", "Arauca" ], [ "Venezuela", "Arauca", "Santaner" ])
+def_loc("pipeline", 3, [ "Cúcuta", "Arauca" ], [ "Venezuela", "Arauca", "Santander" ])
def_loc("pipeline", 2, [ "Bogotá", "Yopal" ], [ "Santander", "Arauca", "Meta East"])
let ayacucho = [
@@ -259,6 +268,8 @@ for (let a of ayacucho) {
data.last_loc = spaces.length-1
+data.last_space = spaces.length-1
+
// City in Dept
adjacent("Santa Marta", "Cesar")
@@ -394,9 +405,9 @@ adjacent("Amazonas", "Putumayo")
data.coastal_spaces = [ "Cesar", "Atlántico", "Chocó", "Nariño" ].map(n=>space_name.indexOf(n)).sort((a,b)=>a-b)
-for (let i = 1; i < spaces.length; ++i)
+for (let i = 0; i < spaces.length; ++i) {
spaces[i].adjacent.sort((a,b)=>a-b)
-
+}
data.card_name = card_name
data.card_order = card_order
@@ -404,8 +415,8 @@ data.space_name = space_name
data.spaces = spaces
let pc_index = 0
-let pc_first = data.first_piece = [ [ 0, 0, 0, 0 ], [ 0, 0 ], [ 0, 0 ], [ 0, 0 ] ]
-let pc_last = data.last_piece = [ [ -1, -1, -1, -1 ], [ -1, -1 ], [ -1, -1 ], [ -1, -1 ] ]
+let pc_first = data.first_piece = [ [ 0, 0, 0, 0, 0 ], [ 0, 0 ], [ 0, 0 ], [ 0, 0 ] ]
+let pc_last = data.last_piece = [ [ -1, -1, -1, -1, -1 ], [ -1, -1 ], [ -1, -1 ], [ -1, -1 ] ]
function def_piece(faction, type, count) {
pc_first[faction][type] = pc_index
pc_last[faction][type] = pc_index + count - 1