diff options
-rw-r--r-- | data.js | 2 | ||||
-rw-r--r-- | images/back-forth.svg | 1 | ||||
-rw-r--r-- | images/shaking-hands.svg | 1 | ||||
-rw-r--r-- | play.css | 30 | ||||
-rw-r--r-- | play.html | 10 | ||||
-rw-r--r-- | play.js | 11 | ||||
-rw-r--r-- | rules.js | 790 | ||||
-rw-r--r-- | tools/gendata.js | 33 |
8 files changed, 739 insertions, 139 deletions
@@ -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 @@ -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, @@ -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> @@ -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") } @@ -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 |