summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data.js106
-rw-r--r--play.html10
-rw-r--r--play.js3
-rw-r--r--rules.js639
-rw-r--r--tools/gendata.js20
5 files changed, 565 insertions, 213 deletions
diff --git a/data.js b/data.js
index c714cba..a226d25 100644
--- a/data.js
+++ b/data.js
@@ -6,59 +6,59 @@ strongholds:[0,1,7,8,9,10,11,12,13,24,25,26,27,32,33,34,35,36],
steppe_warriors:[18,19,22,23],
summer_crusaders:[2,13],
locales:[
-{"name":"Reval","type":"bishopric","stronghold":3,"walls":4,"vp":2,"region":"Danish Estonia","ways":[[5,25],[3,32]],"box":{"x":601,"y":3564,"w":206,"h":91},"adjacent":[3,5],"adjacent_by_trackway":[3,5],"adjacent_by_waterway":[]},
-{"name":"Wesenberg","type":"castle","stronghold":2,"walls":4,"vp":1,"region":"Danish Estonia","ways":[[5,26],[17,30],[6,31]],"box":{"x":1448,"y":3625,"w":304,"h":60},"adjacent":[5,6,17],"adjacent_by_trackway":[5,6,17],"adjacent_by_waterway":[]},
-{"name":"Narwia","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Danish Estonia","ways":[[7,2],[38,2],[49,2],[46,17],[6,27],[33,28]],"box":{"x":2371,"y":3549,"w":123,"h":31},"adjacent":[6,7,33,38,46,49],"adjacent_by_trackway":[6,33],"adjacent_by_waterway":[7,38,46,49]},
-{"name":"Warbola","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Danish Estonia","ways":[[0,32],[4,33],[8,35]],"box":{"x":292,"y":3797,"w":142,"h":31},"adjacent":[0,4,8],"adjacent_by_trackway":[0,4,8],"adjacent_by_waterway":[]},
-{"name":"Harrien","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Danish Estonia","ways":[[3,33],[17,34]],"box":{"x":567,"y":3983,"w":200,"h":100},"adjacent":[3,17],"adjacent_by_trackway":[3,17],"adjacent_by_waterway":[]},
-{"name":"Revala","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Danish Estonia","ways":[[0,25],[1,26]],"box":{"x":1030,"y":3410,"w":200,"h":100},"adjacent":[0,1],"adjacent_by_trackway":[0,1],"adjacent_by_waterway":[]},
-{"name":"Wierland","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Danish Estonia","ways":[[2,27],[23,29],[1,31]],"box":{"x":1999,"y":3680,"w":200,"h":100},"adjacent":[1,2,23],"adjacent_by_trackway":[1,2,23],"adjacent_by_waterway":[]},
-{"name":"Dorpat","type":"bishopric","stronghold":3,"walls":4,"vp":2,"region":"Crusader Livonia","ways":[[12,0,3],[22,0],[2,2],[38,2],[49,2],[11,3],[23,36]],"box":{"x":1625,"y":4589,"w":253,"h":91},"adjacent":[2,11,12,22,23,38,49],"adjacent_by_trackway":[12,22,23],"adjacent_by_waterway":[2,11,12,38,49]},
-{"name":"Leal","type":"bishopric","stronghold":3,"walls":4,"vp":2,"region":"Crusader Livonia","ways":[[3,35],[15,37]],"box":{"x":108,"y":4266,"w":205,"h":91},"adjacent":[3,15],"adjacent_by_trackway":[3,15],"adjacent_by_waterway":[]},
-{"name":"Riga","type":"bishopric","stronghold":3,"walls":4,"vp":2,"region":"Crusader Livonia","ways":[[13,8]],"box":{"x":273,"y":6231,"w":205,"h":91},"adjacent":[13],"adjacent_by_trackway":[],"adjacent_by_waterway":[13]},
-{"name":"Adsel","type":"castle","stronghold":2,"walls":4,"vp":1,"region":"Crusader Livonia","ways":[[13,9],[18,44],[21,44],[14,45]],"box":{"x":1504,"y":5612,"w":185,"h":60},"adjacent":[13,14,18,21],"adjacent_by_trackway":[14,18,21],"adjacent_by_waterway":[13]},
-{"name":"Fellin","type":"castle","stronghold":2,"walls":4,"vp":1,"region":"Crusader Livonia","ways":[[7,3],[12,3],[15,7],[17,7],[20,38]],"box":{"x":1013,"y":4583,"w":184,"h":61},"adjacent":[7,12,15,17,20],"adjacent_by_trackway":[20],"adjacent_by_waterway":[7,12,15,17]},
-{"name":"Odenpäh","type":"castle","stronghold":2,"walls":4,"vp":1,"region":"Crusader Livonia","ways":[[7,0,3],[22,0],[11,3],[14,46]],"box":{"x":1378,"y":5103,"w":250,"h":61},"adjacent":[7,11,14,22],"adjacent_by_trackway":[7,14,22],"adjacent_by_waterway":[7,11]},
-{"name":"Wenden","type":"castle","stronghold":2,"walls":4,"vp":1,"region":"Crusader Livonia","ways":[[9,8],[10,9],[19,40],[21,41]],"box":{"x":909,"y":5759,"w":232,"h":60},"adjacent":[9,10,19,21],"adjacent_by_trackway":[19,21],"adjacent_by_waterway":[9,10]},
-{"name":"Kirrumpäh","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[10,45],[12,46],[32,47]],"box":{"x":1877,"y":5389,"w":175,"h":30},"adjacent":[10,12,32],"adjacent_by_trackway":[10,12,32],"adjacent_by_waterway":[]},
-{"name":"Pernau","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[11,7],[17,7],[8,37]],"box":{"x":517,"y":4580,"w":118,"h":30},"adjacent":[8,11,17],"adjacent_by_trackway":[8],"adjacent_by_waterway":[11,17]},
-{"name":"Rositten","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[21,42],[18,43]],"box":{"x":2046,"y":6307,"w":146,"h":30},"adjacent":[18,21],"adjacent_by_trackway":[18,21],"adjacent_by_waterway":[]},
-{"name":"Jerwen","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[11,7],[15,7],[1,30],[4,34]],"box":{"x":1064,"y":3946,"w":200,"h":100},"adjacent":[1,4,11,15],"adjacent_by_trackway":[1,4],"adjacent_by_waterway":[11,15]},
-{"name":"Lettgallia","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[16,43],[10,44],[21,44],[39,50],[32,51]],"box":{"x":2048,"y":5777,"w":200,"h":100},"adjacent":[10,16,21,32,39],"adjacent_by_trackway":[10,16,21,32,39],"adjacent_by_waterway":[]},
-{"name":"Metsepole","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[20,39],[13,40]],"box":{"x":509,"y":5226,"w":200,"h":100},"adjacent":[13,20],"adjacent_by_trackway":[13,20],"adjacent_by_waterway":[]},
-{"name":"Sackala","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[11,38],[19,39]],"box":{"x":617,"y":4769,"w":200,"h":100},"adjacent":[11,19],"adjacent_by_trackway":[11,19],"adjacent_by_waterway":[]},
-{"name":"Tolowa","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[13,41],[16,42],[10,44],[18,44]],"box":{"x":1541,"y":5933,"w":200,"h":100},"adjacent":[10,13,16,18],"adjacent_by_trackway":[10,13,16,18],"adjacent_by_waterway":[]},
-{"name":"Ugaunia","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[7,0],[12,0],[49,48],[32,49]],"box":{"x":1957,"y":4940,"w":200,"h":100},"adjacent":[7,12,32,49],"adjacent_by_trackway":[7,12,32,49],"adjacent_by_waterway":[]},
-{"name":"Waiga","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[6,29],[7,36]],"box":{"x":1535,"y":4113,"w":200,"h":100},"adjacent":[6,7],"adjacent_by_trackway":[6,7],"adjacent_by_waterway":[]},
-{"name":"Novgorod","type":"novgorod","stronghold":3,"walls":3,"vp":3,"region":"Novgorodan Rus","ways":[[27,6],[47,6],[31,23],[41,62],[40,63]],"box":{"x":4318,"y":4315,"w":333,"h":112},"adjacent":[27,31,40,41,47],"adjacent_by_trackway":[40,41],"adjacent_by_waterway":[27,31,47]},
-{"name":"Ladoga","type":"city","stronghold":3,"walls":3,"vp":2,"region":"Novgorodan Rus","ways":[[31,22],[30,24],[44,59]],"box":{"x":4619,"y":2817,"w":238,"h":90},"adjacent":[30,31,44],"adjacent_by_trackway":[44],"adjacent_by_waterway":[30,31]},
-{"name":"Pskov","type":"city","stronghold":3,"walls":3,"vp":2,"region":"Novgorodan Rus","ways":[[49,4],[39,10],[32,52],[52,68],[37,69]],"box":{"x":2680,"y":5263,"w":205,"h":91},"adjacent":[32,37,39,49,52],"adjacent_by_trackway":[32,37,52],"adjacent_by_waterway":[39,49]},
-{"name":"Rusa","type":"city","stronghold":3,"walls":3,"vp":2,"region":"Novgorodan Rus","ways":[[24,6],[47,6],[28,13]],"box":{"x":4329,"y":5166,"w":205,"h":92},"adjacent":[24,28,47],"adjacent_by_trackway":[],"adjacent_by_waterway":[24,28,47]},
-{"name":"Lovat","type":"traderoute","stronghold":0,"walls":0,"vp":1,"region":"Novgorodan Rus","ways":[[27,13],[36,14]],"box":{"x":4243,"y":5581,"w":187,"h":63},"adjacent":[27,36],"adjacent_by_trackway":[],"adjacent_by_waterway":[27,36]},
-{"name":"Luga","type":"traderoute","stronghold":0,"walls":0,"vp":1,"region":"Novgorodan Rus","ways":[[33,18]],"box":{"x":2667,"y":3295,"w":148,"h":62},"adjacent":[33],"adjacent_by_trackway":[],"adjacent_by_waterway":[33]},
-{"name":"Neva","type":"traderoute","stronghold":0,"walls":0,"vp":1,"region":"Novgorodan Rus","ways":[[25,24],[51,55],[45,57],[44,58]],"box":{"x":3924,"y":2934,"w":148,"h":62},"adjacent":[25,44,45,51],"adjacent_by_trackway":[44,45,51],"adjacent_by_waterway":[25]},
-{"name":"Volkhov","type":"traderoute","stronghold":0,"walls":0,"vp":1,"region":"Novgorodan Rus","ways":[[25,22],[24,23]],"box":{"x":4591,"y":3783,"w":231,"h":63},"adjacent":[24,25],"adjacent_by_trackway":[],"adjacent_by_waterway":[24,25]},
-{"name":"Izborsk","type":"fort","stronghold":1,"walls":3,"vp":1,"region":"Novgorodan Rus","ways":[[14,47],[22,49],[18,51],[26,52]],"box":{"x":2240,"y":5431,"w":241,"h":62},"adjacent":[14,18,22,26],"adjacent_by_trackway":[14,18,22,26],"adjacent_by_waterway":[]},
-{"name":"Kaibolovo","type":"fort","stronghold":1,"walls":3,"vp":1,"region":"Novgorodan Rus","ways":[[29,18],[42,19],[2,28],[34,53]],"box":{"x":2904,"y":3522,"w":285,"h":62},"adjacent":[2,29,34,42],"adjacent_by_trackway":[2,34],"adjacent_by_waterway":[29,42]},
-{"name":"Koporye","type":"fort","stronghold":1,"walls":3,"vp":1,"region":"Novgorodan Rus","ways":[[33,53],[51,54]],"box":{"x":3133,"y":3160,"w":241,"h":62},"adjacent":[33,51],"adjacent_by_trackway":[33,51],"adjacent_by_waterway":[]},
-{"name":"Porkhov","type":"fort","stronghold":1,"walls":3,"vp":1,"region":"Novgorodan Rus","ways":[[47,16],[37,70],[48,71]],"box":{"x":3515,"y":5467,"w":241,"h":63},"adjacent":[37,47,48],"adjacent_by_trackway":[37,48],"adjacent_by_waterway":[47]},
-{"name":"Velikiye Luki","type":"fort","stronghold":1,"walls":3,"vp":1,"region":"Novgorodan Rus","ways":[[28,14],[50,72]],"box":{"x":3706,"y":6347,"w":351,"h":61},"adjacent":[28,50],"adjacent_by_trackway":[50],"adjacent_by_waterway":[28]},
-{"name":"Dubrovno","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[47,15],[26,69],[35,70]],"box":{"x":3153,"y":5214,"w":161,"h":31},"adjacent":[26,35,47],"adjacent_by_trackway":[26,35],"adjacent_by_waterway":[47]},
-{"name":"Gdov","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[49,1,2],[2,2],[7,2],[46,65]],"box":{"x":2427,"y":4149,"w":88,"h":30},"adjacent":[2,7,46,49],"adjacent_by_trackway":[46],"adjacent_by_waterway":[2,7,49]},
-{"name":"Ostrov","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[26,10],[50,11],[18,50]],"box":{"x":2746,"y":5717,"w":115,"h":30},"adjacent":[18,26,50],"adjacent_by_trackway":[18],"adjacent_by_waterway":[26,50]},
-{"name":"Sablia","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[42,20],[24,63],[47,64]],"box":{"x":3788,"y":4541,"w":104,"h":31},"adjacent":[24,42,47],"adjacent_by_trackway":[24,47],"adjacent_by_waterway":[42]},
-{"name":"Tesovo","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[42,21],[43,61],[24,62]],"box":{"x":3936,"y":4102,"w":121,"h":32},"adjacent":[24,42,43],"adjacent_by_trackway":[24,43],"adjacent_by_waterway":[42]},
-{"name":"Zheltsy","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[33,19],[40,20],[41,21],[46,66]],"box":{"x":3501,"y":4176,"w":128,"h":30},"adjacent":[33,40,41,46],"adjacent_by_trackway":[46],"adjacent_by_waterway":[33,40,41]},
-{"name":"Ingria","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[51,56],[44,60],[41,61]],"box":{"x":3820,"y":3639,"w":200,"h":100},"adjacent":[41,44,51],"adjacent_by_trackway":[41,44,51],"adjacent_by_waterway":[]},
-{"name":"Izhora","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[30,58],[25,59],[43,60]],"box":{"x":4074,"y":3323,"w":200,"h":100},"adjacent":[25,30,43],"adjacent_by_trackway":[25,30,43],"adjacent_by_waterway":[]},
-{"name":"Karelia","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[30,57]],"box":{"x":3833,"y":2408,"w":200,"h":100},"adjacent":[30],"adjacent_by_trackway":[30],"adjacent_by_waterway":[]},
-{"name":"Plyussa River","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[2,17],[38,65],[42,66],[52,67]],"box":{"x":2829,"y":4234,"w":200,"h":100},"adjacent":[2,38,42,52],"adjacent_by_trackway":[38,42,52],"adjacent_by_waterway":[2]},
-{"name":"Shelon River","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[24,6],[27,6],[37,15],[35,16],[40,64]],"box":{"x":3654,"y":4864,"w":200,"h":100},"adjacent":[24,27,35,37,40],"adjacent_by_trackway":[40],"adjacent_by_waterway":[24,27,35,37]},
-{"name":"Sorot River","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[50,12],[35,71]],"box":{"x":3299,"y":5781,"w":200,"h":100},"adjacent":[35,50],"adjacent_by_trackway":[35],"adjacent_by_waterway":[50]},
-{"name":"Uzmen","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[38,1,2],[2,2],[7,2],[26,4],[52,5],[22,48]],"box":{"x":2112,"y":4692,"w":200,"h":100},"adjacent":[2,7,22,26,38,52],"adjacent_by_trackway":[22],"adjacent_by_waterway":[2,7,26,38,52]},
-{"name":"Velikaya River","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[39,11],[48,12],[36,72]],"box":{"x":3029,"y":6090,"w":200,"h":100},"adjacent":[36,39,48],"adjacent_by_trackway":[36],"adjacent_by_waterway":[39,48]},
-{"name":"Vod","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[34,54],[30,55],[43,56]],"box":{"x":3488,"y":3345,"w":200,"h":100},"adjacent":[30,34,43],"adjacent_by_trackway":[30,34,43],"adjacent_by_waterway":[]},
-{"name":"Zhelcha River","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[49,5],[46,67],[26,68]],"box":{"x":2782,"y":4586,"w":200,"h":100},"adjacent":[26,46,49],"adjacent_by_trackway":[26,46],"adjacent_by_waterway":[49]},
+{"name":"Reval","type":"bishopric","stronghold":3,"walls":4,"vp":2,"region":"Danish Estonia","ways":[[5,25],[3,32]],"box":{"x":601,"y":3564,"w":206,"h":91},"adjacent":[3,5],"adjacent_by_trackway":[3,5],"adjacent_by_waterway":[],"trackways":[[3,32],[5,25]],"waterways":[]},
+{"name":"Wesenberg","type":"castle","stronghold":2,"walls":4,"vp":1,"region":"Danish Estonia","ways":[[5,26],[17,30],[6,31]],"box":{"x":1448,"y":3625,"w":304,"h":60},"adjacent":[5,6,17],"adjacent_by_trackway":[5,6,17],"adjacent_by_waterway":[],"trackways":[[5,26],[6,31],[17,30]],"waterways":[]},
+{"name":"Narwia","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Danish Estonia","ways":[[7,2],[38,2],[49,2],[46,17],[6,27],[33,28]],"box":{"x":2371,"y":3549,"w":123,"h":31},"adjacent":[6,7,33,38,46,49],"adjacent_by_trackway":[6,33],"adjacent_by_waterway":[7,38,46,49],"trackways":[[6,27],[33,28]],"waterways":[[7,2],[38,2],[49,2],[46,17]]},
+{"name":"Warbola","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Danish Estonia","ways":[[0,32],[4,33],[8,35]],"box":{"x":292,"y":3797,"w":142,"h":31},"adjacent":[0,4,8],"adjacent_by_trackway":[0,4,8],"adjacent_by_waterway":[],"trackways":[[0,32],[4,33],[8,35]],"waterways":[]},
+{"name":"Harrien","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Danish Estonia","ways":[[3,33],[17,34]],"box":{"x":567,"y":3983,"w":200,"h":100},"adjacent":[3,17],"adjacent_by_trackway":[3,17],"adjacent_by_waterway":[],"trackways":[[3,33],[17,34]],"waterways":[]},
+{"name":"Revala","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Danish Estonia","ways":[[0,25],[1,26]],"box":{"x":1030,"y":3410,"w":200,"h":100},"adjacent":[0,1],"adjacent_by_trackway":[0,1],"adjacent_by_waterway":[],"trackways":[[0,25],[1,26]],"waterways":[]},
+{"name":"Wierland","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Danish Estonia","ways":[[2,27],[23,29],[1,31]],"box":{"x":1999,"y":3680,"w":200,"h":100},"adjacent":[1,2,23],"adjacent_by_trackway":[1,2,23],"adjacent_by_waterway":[],"trackways":[[1,31],[2,27],[23,29]],"waterways":[]},
+{"name":"Dorpat","type":"bishopric","stronghold":3,"walls":4,"vp":2,"region":"Crusader Livonia","ways":[[12,0,3],[22,0],[2,2],[38,2],[49,2],[11,3],[23,36]],"box":{"x":1625,"y":4589,"w":253,"h":91},"adjacent":[2,11,12,22,23,38,49],"adjacent_by_trackway":[12,22,23],"adjacent_by_waterway":[2,11,12,38,49],"trackways":[[12,0],[22,0],[23,36]],"waterways":[[12,3],[2,2],[38,2],[49,2],[11,3]]},
+{"name":"Leal","type":"bishopric","stronghold":3,"walls":4,"vp":2,"region":"Crusader Livonia","ways":[[3,35],[15,37]],"box":{"x":108,"y":4266,"w":205,"h":91},"adjacent":[3,15],"adjacent_by_trackway":[3,15],"adjacent_by_waterway":[],"trackways":[[3,35],[15,37]],"waterways":[]},
+{"name":"Riga","type":"bishopric","stronghold":3,"walls":4,"vp":2,"region":"Crusader Livonia","ways":[[13,8]],"box":{"x":273,"y":6231,"w":205,"h":91},"adjacent":[13],"adjacent_by_trackway":[],"adjacent_by_waterway":[13],"trackways":[],"waterways":[[13,8]]},
+{"name":"Adsel","type":"castle","stronghold":2,"walls":4,"vp":1,"region":"Crusader Livonia","ways":[[13,9],[18,44],[21,44],[14,45]],"box":{"x":1504,"y":5612,"w":185,"h":60},"adjacent":[13,14,18,21],"adjacent_by_trackway":[14,18,21],"adjacent_by_waterway":[13],"trackways":[[14,45],[18,44],[21,44]],"waterways":[[13,9]]},
+{"name":"Fellin","type":"castle","stronghold":2,"walls":4,"vp":1,"region":"Crusader Livonia","ways":[[7,3],[12,3],[15,7],[17,7],[20,38]],"box":{"x":1013,"y":4583,"w":184,"h":61},"adjacent":[7,12,15,17,20],"adjacent_by_trackway":[20],"adjacent_by_waterway":[7,12,15,17],"trackways":[[20,38]],"waterways":[[7,3],[12,3],[15,7],[17,7]]},
+{"name":"Odenpäh","type":"castle","stronghold":2,"walls":4,"vp":1,"region":"Crusader Livonia","ways":[[7,0,3],[22,0],[11,3],[14,46]],"box":{"x":1378,"y":5103,"w":250,"h":61},"adjacent":[7,11,14,22],"adjacent_by_trackway":[7,14,22],"adjacent_by_waterway":[7,11],"trackways":[[7,0],[14,46],[22,0]],"waterways":[[7,3],[11,3]]},
+{"name":"Wenden","type":"castle","stronghold":2,"walls":4,"vp":1,"region":"Crusader Livonia","ways":[[9,8],[10,9],[19,40],[21,41]],"box":{"x":909,"y":5759,"w":232,"h":60},"adjacent":[9,10,19,21],"adjacent_by_trackway":[19,21],"adjacent_by_waterway":[9,10],"trackways":[[19,40],[21,41]],"waterways":[[9,8],[10,9]]},
+{"name":"Kirrumpäh","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[10,45],[12,46],[32,47]],"box":{"x":1877,"y":5389,"w":175,"h":30},"adjacent":[10,12,32],"adjacent_by_trackway":[10,12,32],"adjacent_by_waterway":[],"trackways":[[10,45],[12,46],[32,47]],"waterways":[]},
+{"name":"Pernau","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[11,7],[17,7],[8,37]],"box":{"x":517,"y":4580,"w":118,"h":30},"adjacent":[8,11,17],"adjacent_by_trackway":[8],"adjacent_by_waterway":[11,17],"trackways":[[8,37]],"waterways":[[11,7],[17,7]]},
+{"name":"Rositten","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[21,42],[18,43]],"box":{"x":2046,"y":6307,"w":146,"h":30},"adjacent":[18,21],"adjacent_by_trackway":[18,21],"adjacent_by_waterway":[],"trackways":[[18,43],[21,42]],"waterways":[]},
+{"name":"Jerwen","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[11,7],[15,7],[1,30],[4,34]],"box":{"x":1064,"y":3946,"w":200,"h":100},"adjacent":[1,4,11,15],"adjacent_by_trackway":[1,4],"adjacent_by_waterway":[11,15],"trackways":[[1,30],[4,34]],"waterways":[[11,7],[15,7]]},
+{"name":"Lettgallia","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[16,43],[10,44],[21,44],[39,50],[32,51]],"box":{"x":2048,"y":5777,"w":200,"h":100},"adjacent":[10,16,21,32,39],"adjacent_by_trackway":[10,16,21,32,39],"adjacent_by_waterway":[],"trackways":[[10,44],[16,43],[21,44],[32,51],[39,50]],"waterways":[]},
+{"name":"Metsepole","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[20,39],[13,40]],"box":{"x":509,"y":5226,"w":200,"h":100},"adjacent":[13,20],"adjacent_by_trackway":[13,20],"adjacent_by_waterway":[],"trackways":[[13,40],[20,39]],"waterways":[]},
+{"name":"Sackala","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[11,38],[19,39]],"box":{"x":617,"y":4769,"w":200,"h":100},"adjacent":[11,19],"adjacent_by_trackway":[11,19],"adjacent_by_waterway":[],"trackways":[[11,38],[19,39]],"waterways":[]},
+{"name":"Tolowa","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[13,41],[16,42],[10,44],[18,44]],"box":{"x":1541,"y":5933,"w":200,"h":100},"adjacent":[10,13,16,18],"adjacent_by_trackway":[10,13,16,18],"adjacent_by_waterway":[],"trackways":[[10,44],[13,41],[16,42],[18,44]],"waterways":[]},
+{"name":"Ugaunia","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[7,0],[12,0],[49,48],[32,49]],"box":{"x":1957,"y":4940,"w":200,"h":100},"adjacent":[7,12,32,49],"adjacent_by_trackway":[7,12,32,49],"adjacent_by_waterway":[],"trackways":[[7,0],[12,0],[32,49],[49,48]],"waterways":[]},
+{"name":"Waiga","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Crusader Livonia","ways":[[6,29],[7,36]],"box":{"x":1535,"y":4113,"w":200,"h":100},"adjacent":[6,7],"adjacent_by_trackway":[6,7],"adjacent_by_waterway":[],"trackways":[[6,29],[7,36]],"waterways":[]},
+{"name":"Novgorod","type":"novgorod","stronghold":3,"walls":3,"vp":3,"region":"Novgorodan Rus","ways":[[27,6],[47,6],[31,23],[41,62],[40,63]],"box":{"x":4318,"y":4315,"w":333,"h":112},"adjacent":[27,31,40,41,47],"adjacent_by_trackway":[40,41],"adjacent_by_waterway":[27,31,47],"trackways":[[40,63],[41,62]],"waterways":[[27,6],[47,6],[31,23]]},
+{"name":"Ladoga","type":"city","stronghold":3,"walls":3,"vp":2,"region":"Novgorodan Rus","ways":[[31,22],[30,24],[44,59]],"box":{"x":4619,"y":2817,"w":238,"h":90},"adjacent":[30,31,44],"adjacent_by_trackway":[44],"adjacent_by_waterway":[30,31],"trackways":[[44,59]],"waterways":[[31,22],[30,24]]},
+{"name":"Pskov","type":"city","stronghold":3,"walls":3,"vp":2,"region":"Novgorodan Rus","ways":[[49,4],[39,10],[32,52],[52,68],[37,69]],"box":{"x":2680,"y":5263,"w":205,"h":91},"adjacent":[32,37,39,49,52],"adjacent_by_trackway":[32,37,52],"adjacent_by_waterway":[39,49],"trackways":[[32,52],[37,69],[52,68]],"waterways":[[49,4],[39,10]]},
+{"name":"Rusa","type":"city","stronghold":3,"walls":3,"vp":2,"region":"Novgorodan Rus","ways":[[24,6],[47,6],[28,13]],"box":{"x":4329,"y":5166,"w":205,"h":92},"adjacent":[24,28,47],"adjacent_by_trackway":[],"adjacent_by_waterway":[24,28,47],"trackways":[],"waterways":[[24,6],[47,6],[28,13]]},
+{"name":"Lovat","type":"traderoute","stronghold":0,"walls":0,"vp":1,"region":"Novgorodan Rus","ways":[[27,13],[36,14]],"box":{"x":4243,"y":5581,"w":187,"h":63},"adjacent":[27,36],"adjacent_by_trackway":[],"adjacent_by_waterway":[27,36],"trackways":[],"waterways":[[27,13],[36,14]]},
+{"name":"Luga","type":"traderoute","stronghold":0,"walls":0,"vp":1,"region":"Novgorodan Rus","ways":[[33,18]],"box":{"x":2667,"y":3295,"w":148,"h":62},"adjacent":[33],"adjacent_by_trackway":[],"adjacent_by_waterway":[33],"trackways":[],"waterways":[[33,18]]},
+{"name":"Neva","type":"traderoute","stronghold":0,"walls":0,"vp":1,"region":"Novgorodan Rus","ways":[[25,24],[51,55],[45,57],[44,58]],"box":{"x":3924,"y":2934,"w":148,"h":62},"adjacent":[25,44,45,51],"adjacent_by_trackway":[44,45,51],"adjacent_by_waterway":[25],"trackways":[[44,58],[45,57],[51,55]],"waterways":[[25,24]]},
+{"name":"Volkhov","type":"traderoute","stronghold":0,"walls":0,"vp":1,"region":"Novgorodan Rus","ways":[[25,22],[24,23]],"box":{"x":4591,"y":3783,"w":231,"h":63},"adjacent":[24,25],"adjacent_by_trackway":[],"adjacent_by_waterway":[24,25],"trackways":[],"waterways":[[25,22],[24,23]]},
+{"name":"Izborsk","type":"fort","stronghold":1,"walls":3,"vp":1,"region":"Novgorodan Rus","ways":[[14,47],[22,49],[18,51],[26,52]],"box":{"x":2240,"y":5431,"w":241,"h":62},"adjacent":[14,18,22,26],"adjacent_by_trackway":[14,18,22,26],"adjacent_by_waterway":[],"trackways":[[14,47],[18,51],[22,49],[26,52]],"waterways":[]},
+{"name":"Kaibolovo","type":"fort","stronghold":1,"walls":3,"vp":1,"region":"Novgorodan Rus","ways":[[29,18],[42,19],[2,28],[34,53]],"box":{"x":2904,"y":3522,"w":285,"h":62},"adjacent":[2,29,34,42],"adjacent_by_trackway":[2,34],"adjacent_by_waterway":[29,42],"trackways":[[2,28],[34,53]],"waterways":[[29,18],[42,19]]},
+{"name":"Koporye","type":"fort","stronghold":1,"walls":3,"vp":1,"region":"Novgorodan Rus","ways":[[33,53],[51,54]],"box":{"x":3133,"y":3160,"w":241,"h":62},"adjacent":[33,51],"adjacent_by_trackway":[33,51],"adjacent_by_waterway":[],"trackways":[[33,53],[51,54]],"waterways":[]},
+{"name":"Porkhov","type":"fort","stronghold":1,"walls":3,"vp":1,"region":"Novgorodan Rus","ways":[[47,16],[37,70],[48,71]],"box":{"x":3515,"y":5467,"w":241,"h":63},"adjacent":[37,47,48],"adjacent_by_trackway":[37,48],"adjacent_by_waterway":[47],"trackways":[[37,70],[48,71]],"waterways":[[47,16]]},
+{"name":"Velikiye Luki","type":"fort","stronghold":1,"walls":3,"vp":1,"region":"Novgorodan Rus","ways":[[28,14],[50,72]],"box":{"x":3706,"y":6347,"w":351,"h":61},"adjacent":[28,50],"adjacent_by_trackway":[50],"adjacent_by_waterway":[28],"trackways":[[50,72]],"waterways":[[28,14]]},
+{"name":"Dubrovno","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[47,15],[26,69],[35,70]],"box":{"x":3153,"y":5214,"w":161,"h":31},"adjacent":[26,35,47],"adjacent_by_trackway":[26,35],"adjacent_by_waterway":[47],"trackways":[[26,69],[35,70]],"waterways":[[47,15]]},
+{"name":"Gdov","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[49,1,2],[2,2],[7,2],[46,65]],"box":{"x":2427,"y":4149,"w":88,"h":30},"adjacent":[2,7,46,49],"adjacent_by_trackway":[46],"adjacent_by_waterway":[2,7,49],"trackways":[[46,65]],"waterways":[[49,1],[2,2],[7,2]]},
+{"name":"Ostrov","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[26,10],[50,11],[18,50]],"box":{"x":2746,"y":5717,"w":115,"h":30},"adjacent":[18,26,50],"adjacent_by_trackway":[18],"adjacent_by_waterway":[26,50],"trackways":[[18,50]],"waterways":[[26,10],[50,11]]},
+{"name":"Sablia","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[42,20],[24,63],[47,64]],"box":{"x":3788,"y":4541,"w":104,"h":31},"adjacent":[24,42,47],"adjacent_by_trackway":[24,47],"adjacent_by_waterway":[42],"trackways":[[24,63],[47,64]],"waterways":[[42,20]]},
+{"name":"Tesovo","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[42,21],[43,61],[24,62]],"box":{"x":3936,"y":4102,"w":121,"h":32},"adjacent":[24,42,43],"adjacent_by_trackway":[24,43],"adjacent_by_waterway":[42],"trackways":[[24,62],[43,61]],"waterways":[[42,21]]},
+{"name":"Zheltsy","type":"town","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[33,19],[40,20],[41,21],[46,66]],"box":{"x":3501,"y":4176,"w":128,"h":30},"adjacent":[33,40,41,46],"adjacent_by_trackway":[46],"adjacent_by_waterway":[33,40,41],"trackways":[[46,66]],"waterways":[[33,19],[40,20],[41,21]]},
+{"name":"Ingria","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[51,56],[44,60],[41,61]],"box":{"x":3820,"y":3639,"w":200,"h":100},"adjacent":[41,44,51],"adjacent_by_trackway":[41,44,51],"adjacent_by_waterway":[],"trackways":[[41,61],[44,60],[51,56]],"waterways":[]},
+{"name":"Izhora","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[30,58],[25,59],[43,60]],"box":{"x":4074,"y":3323,"w":200,"h":100},"adjacent":[25,30,43],"adjacent_by_trackway":[25,30,43],"adjacent_by_waterway":[],"trackways":[[25,59],[30,58],[43,60]],"waterways":[]},
+{"name":"Karelia","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[30,57]],"box":{"x":3833,"y":2408,"w":200,"h":100},"adjacent":[30],"adjacent_by_trackway":[30],"adjacent_by_waterway":[],"trackways":[[30,57]],"waterways":[]},
+{"name":"Plyussa River","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[2,17],[38,65],[42,66],[52,67]],"box":{"x":2829,"y":4234,"w":200,"h":100},"adjacent":[2,38,42,52],"adjacent_by_trackway":[38,42,52],"adjacent_by_waterway":[2],"trackways":[[38,65],[42,66],[52,67]],"waterways":[[2,17]]},
+{"name":"Shelon River","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[24,6],[27,6],[37,15],[35,16],[40,64]],"box":{"x":3654,"y":4864,"w":200,"h":100},"adjacent":[24,27,35,37,40],"adjacent_by_trackway":[40],"adjacent_by_waterway":[24,27,35,37],"trackways":[[40,64]],"waterways":[[24,6],[27,6],[37,15],[35,16]]},
+{"name":"Sorot River","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[50,12],[35,71]],"box":{"x":3299,"y":5781,"w":200,"h":100},"adjacent":[35,50],"adjacent_by_trackway":[35],"adjacent_by_waterway":[50],"trackways":[[35,71]],"waterways":[[50,12]]},
+{"name":"Uzmen","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[38,1,2],[2,2],[7,2],[26,4],[52,5],[22,48]],"box":{"x":2112,"y":4692,"w":200,"h":100},"adjacent":[2,7,22,26,38,52],"adjacent_by_trackway":[22],"adjacent_by_waterway":[2,7,26,38,52],"trackways":[[22,48]],"waterways":[[38,1],[2,2],[7,2],[26,4],[52,5]]},
+{"name":"Velikaya River","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[39,11],[48,12],[36,72]],"box":{"x":3029,"y":6090,"w":200,"h":100},"adjacent":[36,39,48],"adjacent_by_trackway":[36],"adjacent_by_waterway":[39,48],"trackways":[[36,72]],"waterways":[[39,11],[48,12]]},
+{"name":"Vod","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[34,54],[30,55],[43,56]],"box":{"x":3488,"y":3345,"w":200,"h":100},"adjacent":[30,34,43],"adjacent_by_trackway":[30,34,43],"adjacent_by_waterway":[],"trackways":[[30,55],[34,54],[43,56]],"waterways":[]},
+{"name":"Zhelcha River","type":"region","stronghold":0,"walls":0,"vp":0,"region":"Novgorodan Rus","ways":[[49,5],[46,67],[26,68]],"box":{"x":2782,"y":4586,"w":200,"h":100},"adjacent":[26,46,49],"adjacent_by_trackway":[26,46],"adjacent_by_waterway":[49],"trackways":[[26,68],[46,67]],"waterways":[[49,5]]},
],
ways:[
{"type":"trackway","locales":[7,12,22],"name":"Crossroads"},
diff --git a/play.html b/play.html
index 4096add..d2335cb 100644
--- a/play.html
+++ b/play.html
@@ -720,9 +720,6 @@ body.shift .mustered_vassals {
.locale.bishopric { border-radius: 50% 50% 0 0 }
.locale.novgorod { border-radius: 50% 50% 0 0 }
-.locale.action { border-color: white; box-shadow: 0 0 4px white; }
-.locale.action.laden { border-color: red; box-shadow: 0 0 4px red; }
-
.locale.action.region { background-color: #5A02 }
.locale.action.town { background-color: #fc02 }
.locale.action.traderoute { background-color: #0af2 }
@@ -732,6 +729,13 @@ body.shift .mustered_vassals {
.locale.action.castle { background-color: #0002 }
.locale.action.bishopric { background-color: #0002 }
+.locale.action { border-color: white; box-shadow: 0 0 4px white; }
+.locale.action.laden { border-color: white; box-shadow: 0 0 0 3px indianred; background-color: #cd5c5c66 }
+
+.locale.supply_path { border-color: indianred; background-color: #cd5c5c66; }
+.locale.supply_source { border-color: seagreen; background-color: #2e8b5766; }
+.locale.supply_source.action { border-color: white; background-color: #2e8b5766; box-shadow: 0 0 0 3px seagreen; }
+
.locale.tip, .locale_name.tip {
background-color: #ff08;
box-shadow: 0 0 8px #ff08;
diff --git a/play.js b/play.js
index fd4237e..ace1c6c 100644
--- a/play.js
+++ b/play.js
@@ -1231,9 +1231,10 @@ function update_locale(loc) {
ui.locale[loc].classList.toggle("action", is_locale_action(loc) || is_laden_march_action(loc))
ui.locale[loc].classList.toggle("laden", is_laden_march_action(loc))
+ ui.locale[loc].classList.toggle("supply_path", !!(view.supply && view.supply[0] === loc))
+ ui.locale[loc].classList.toggle("supply_source", !!(view.supply && view.supply[1] === loc))
if (ui.locale_name[loc]) {
ui.locale_name[loc].classList.toggle("action", is_locale_action(loc) || is_laden_march_action(loc))
- ui.locale_name[loc].classList.toggle("laden", is_laden_march_action(loc))
}
ui.locale_markers[loc].replaceChildren()
diff --git a/rules.js b/rules.js
index d55dd3b..9afee28 100644
--- a/rules.js
+++ b/rules.js
@@ -7,13 +7,10 @@
// FIXME: lift_sieges / besieged needs checking! (automatic after disband_lord, manual after move/sail, extra careful manual after battle)
// FIXME: remove_legate_if_endangered needs checking! (automatic after disband_lord, manual after move/sail, manual after battle)
-// GUI: Remove battle mat.
-// GUI: tucked map capabilities pop up on shift key
+// GUI: siegeworks markers for walls and siegeworks (colored by side)
// GUI: show moved/fought markers on mats during Feed phase
// GUI: show sally/relief sally as unbesieged on map
-// GUI: show siegeworks + walls on battle mat for protection indication
// GUI: show feed x2 on lord mats with > 6 units
-// GUI: battle event on lord mat (at top) - in client for Bridge and Field Organs
// WONTFIX: choose crossbow/normal hit application order
@@ -26,6 +23,8 @@
const data = require("./data.js")
+const AUTOWALK = true
+
const BOTH = "Both"
const TEUTONS = "Teutons"
const RUSSIANS = "Russians"
@@ -2839,6 +2838,7 @@ function end_heinrich_sees_the_curia() {
if (game.state === "actions" && game.command === LORD_HEINRICH) {
spend_all_actions()
resume_actions()
+ update_supply_possible()
}
// No more muster of Heinrich
@@ -4259,6 +4259,7 @@ function goto_actions() {
}
resume_actions()
+ update_supply_possible()
}
function resume_actions() {
@@ -4633,6 +4634,7 @@ function march_with_group_3() {
game.march = 0
spend_all_actions()
resume_actions()
+ update_supply_possible()
return
}
@@ -4650,6 +4652,7 @@ function march_with_group_3() {
game.march = 0
resume_actions()
+ update_supply_possible()
}
// === ACTION: MARCH - AVOID BATTLE ===
@@ -5182,6 +5185,7 @@ function end_siege() {
spend_all_actions()
resume_actions()
+ update_supply_possible()
}
// === ACTION: STORM ===
@@ -5262,14 +5266,34 @@ function init_lodya_supply() {
if (!is_winter()) {
let lord = find_lodya_lord_in_shared()
if (lord !== NOBODY) {
- // No choice if enough ships and 2x boats >= boats + extra ships
- let ships = get_lord_assets(lord, SHIP)
let boats = get_lord_assets(lord, BOAT)
- if (ships < 2 || boats < ships - 2)
- return true
- log("Lodya: Boats as 2 Boats each.")
+ let ships = get_lord_assets(lord, SHIP)
+
+ // Automatic choice if Novgorod is unavailable for seaport supply.
+ if (is_supply_forbidden(LOC_NOVGOROD)) {
+ if (boats < ships) {
+ game.flags.lodya = -ships
+ log_lodya()
+ return false
+ } else {
+ game.flags.lodya = 0
+ log_lodya()
+ return false
+ }
+ }
+
+ // Automatic choice if enough ships and 2x boats >= boats + extra ships.
+ if (ships >= 2 && boats >= ships - 2) {
+ game.flags.lodya = 0
+ log_lodya()
+ return false
+ }
+
+ // Manual choice.
+ return true
}
}
+ // No choice.
return false
}
@@ -5315,6 +5339,7 @@ function end_supply_lodya() {
push_undo()
log_lodya()
init_supply()
+ resume_supply()
game.state = "supply_source"
}
@@ -5327,7 +5352,37 @@ function log_lodya() {
log(`Lodya: ${game.flags.lodya} Ships as Boats.`)
}
-// === ACTION: SUPPLY ===
+// === ACTION: SUPPLY (SEARCHING) ===
+
+let _supply_stat = 0
+let _supply_stop = new Array(last_locale+1)
+let _supply_reached = new Array(last_locale+1)
+
+let _supply_seen = new Array(last_locale+1).fill(0)
+let _supply_cost = new Array(last_locale+1)
+let _supply_boats = new Array(last_locale+1)
+let _supply_carts = new Array(last_locale+1)
+
+function is_supply_forbidden(here) {
+ if (has_unbesieged_enemy_lord(here))
+ return true
+ if (is_unbesieged_enemy_stronghold(here))
+ return true
+ if (is_friendly_territory(here) && has_conquered_marker(here))
+ if (!has_siege_marker(here))
+ return true
+ return false
+}
+
+function init_supply_forbidden() {
+ _supply_stat = 0
+ for (let here = 0; here <= last_locale; ++here) {
+ if (is_supply_forbidden(here))
+ _supply_stop[here] = 1
+ else
+ _supply_stop[here] = 0
+ }
+}
function init_supply() {
let season = current_season()
@@ -5336,7 +5391,7 @@ function init_supply() {
let carts = 0
let sleds = 0
let ships = 0
- let seats = 2
+ let available = 2
if (season === SUMMER) {
carts = get_shared_assets(here, CART)
@@ -5353,84 +5408,184 @@ function init_supply() {
ships = 2
if (is_famine_in_play())
- seats = game.flags.famine ? 0 : 1
-
- let sources = list_supply_sources(ships)
- let reachable = filter_reachable_supply_sources(sources, boats, carts, sleds)
- let supply_seats = filter_usable_supply_seats(reachable)
- let supply_seaports = filter_usable_supply_seaports(reachable, ships)
+ available = game.flags.famine ? 0 : 1
- game.supply = { supply_seats, supply_seaports, seats, boats, carts, sleds, ships }
-}
-
-function list_supply_sources(ships) {
- let sources = []
-
- for_each_seat(game.command, seat => { set_add(sources, seat) }, false)
+ let seats = []
+ if (available > 0) {
+ for_each_seat(game.command, seat => {
+ if (!is_supply_forbidden(seat))
+ seats.push(seat)
+ }, true)
+ available = Math.min(seats.length, available)
+ }
+ let seaports = []
if (ships > 0) {
if (game.active === TEUTONS)
for (let port of data.seaports)
- set_add(sources, port)
+ if (!is_supply_forbidden(port))
+ seaports.push(port)
if (game.active === RUSSIANS)
- set_add(sources, LOC_NOVGOROD)
+ if (!is_supply_forbidden(LOC_NOVGOROD))
+ seaports.push(LOC_NOVGOROD)
}
+ if (seaports.length === 0)
+ ships = 0
- return sources
+ game.supply = { seats, seaports, available, boats, carts, sleds, ships }
}
-let _supply_stat = 0
-let _supply_stop = new Array(last_locale+1)
-let _supply_reached = new Array(last_locale+1)
+function search_supply_winter(start, sleds, exit) {
+ if (_supply_stop[start])
+ return 0
+ _supply_reached[start] = 1
+ _supply_cost[start] = 0
+ if (exit && set_has(exit, start))
+ return 1
+ if (sleds === 0)
+ return 0
+ let queue = [ start ]
+ while (queue.length > 0) {
+ let item = queue.shift()
+ let here = item & 63
+ let used = item >> 6
+ if (used + 1 <= sleds) {
+ for (let next of data.locales[here].adjacent) {
+ if (!_supply_reached[next] && !_supply_stop[next]) {
+ if (exit && set_has(exit, next))
+ return 1
+ _supply_reached[next] = 1
+ _supply_cost[next] = used + 1
+ if (used + 1 < sleds)
+ queue.push(next | ((used + 1) << 6))
+ }
+ }
+ }
+ _supply_stat++
+ }
+ return 0
+}
-let _supply_seen = new Array(last_locale+1)
-let _supply_boats = new Array(last_locale+1)
-let _supply_carts = new Array(last_locale+1)
+function search_supply_rasputitsa(start, boats, exit) {
+ if (_supply_stop[start])
+ return 0
+ _supply_reached[start] = 1
+ _supply_cost[start] = 0
+ if (exit && set_has(exit, start))
+ return 1
+ if (boats === 0)
+ return 0
+ let queue = [ start ]
+ while (queue.length > 0) {
+ let item = queue.shift()
+ let here = item & 63
+ let used = item >> 6
+ if (used + 1 <= boats) {
+ for (let next of data.locales[here].adjacent_by_waterway) {
+ if (!_supply_reached[next] && !_supply_stop[next]) {
+ if (exit && set_has(exit, next))
+ return 1
+ _supply_reached[next] = 1
+ _supply_cost[next] = used + 1
+ if (used + 1 < boats)
+ queue.push(next | ((used + 1) << 6))
+ }
+ }
+ }
+ _supply_stat++
+ }
+ return 0
+}
-function filter_reachable_supply_sources(sources, boats, carts, sleds) {
- _supply_stat = 0
- _supply_stop.fill(0)
- _supply_reached.fill(0)
+function search_supply_summer(here, boats, carts, exit) {
+ _supply_stat++
- for (let here = 0; here <= last_locale; ++here) {
- if (has_unbesieged_enemy_lord(here))
- _supply_stop[here] = 1
- else if (is_unbesieged_enemy_stronghold(here))
- _supply_stop[here] = 1
- else if (is_friendly_territory(here) && has_conquered_marker(here))
- if (!has_siege_marker(here))
- _supply_stop[here] = 1
+ // Been here before with same or more transports remaining
+ if (_supply_boats[here] >= boats && _supply_carts[here] >= carts)
+ return 0
+
+ // First time here with this many transports remaining
+ if (_supply_boats[here] <= boats && _supply_carts[here] <= carts) {
+ _supply_boats[here] = boats
+ _supply_carts[here] = carts
}
- // NOTE: Impossible situation
- if (_supply_stop[get_lord_locale(game.command)])
- return []
+ _supply_reached[here] = 1
+ if (exit && set_has(exit, here))
+ return 1
- switch (current_season()) {
- case SUMMER:
- _supply_seen.fill(0)
- _supply_boats.fill(-1)
- _supply_carts.fill(-1)
- search_supply_reachable_summer(get_lord_locale(game.command), boats, carts)
- break
- case EARLY_WINTER:
- case LATE_WINTER:
- search_supply_reachable_winter(get_lord_locale(game.command), sleds)
- break
- case RASPUTITSA:
- search_supply_reachable_rasputitsa(get_lord_locale(game.command), boats)
- break
+ _supply_seen[here] = 1
+
+ if (boats > 0) {
+ for (let next of data.locales[here].adjacent_by_waterway) {
+ if (!_supply_seen[next] && !_supply_stop[next]) {
+ if (search_supply_summer(next, boats-1, carts, exit)) {
+ _supply_seen[here] = 0
+ return 1
+ }
+ }
+ }
}
- let result = []
- for (let here of sources)
- if (_supply_reached[here])
- set_add(result, here)
- console.log("SUPPLY SEARCH", _supply_stat, sources, result, _supply_reached.join(""))
- return result
+ if (carts > 0) {
+ for (let next of data.locales[here].adjacent_by_trackway) {
+ if (!_supply_seen[next] && !_supply_stop[next]) {
+ if (search_supply_summer(next, boats, carts-1, exit)) {
+ _supply_seen[here] = 0
+ return 1
+ }
+ }
+ }
+ }
+
+ _supply_seen[here] = 0
+ return 0
}
-function search_supply_reachable_summer(here, boats, carts) {
+function init_summer_path() {
+ init_supply_forbidden()
+
+ // First pass to create best-cost-so-far for each combo of boats to carts
+ let gate = {
+ boats: new Array(game.supply.boats+1).fill(0),
+ carts: new Array(game.supply.carts+1).fill(0),
+ }
+ _supply_stat = 0
+ _supply_boats.fill(-1)
+ _supply_carts.fill(-1)
+ search_summer_path_pass1(game.supply.here, game.supply.end, game.supply.boats, game.supply.carts, gate)
+ console.log("SUMMER GATE", _supply_stat, JSON.stringify(gate))
+
+ // Second pass which lists acceptable paths
+ _supply_stat = 0
+ _supply_boats.fill(-1)
+ _supply_carts.fill(-1)
+ game.supply.path = []
+ search_summer_path_pass2([], game.supply.here, game.supply.end, game.supply.boats, game.supply.carts, gate)
+ console.log("SUMMER PATH", _supply_stat, JSON.stringify(game.supply.path).length)
+
+ // Auto-pick path if only one choice.
+ if (AUTOWALK && game.supply.path.length === 2)
+ walk_supply_path_way(game.supply.path[0] >> 8, game.supply.path[0] & 255)
+}
+
+function search_summer_path_pass1(here, end, boats, carts, gate) {
+ _supply_stat++
+
+ if (here === end) {
+ for (let c = 0; c <= carts; ++c)
+ if (boats > gate.boats[c])
+ gate.boats[c] = boats
+ for (let b = 0; b <= boats; ++b)
+ if (carts > gate.carts[b])
+ gate.carts[b] = carts
+ return
+ }
+
+ // Worse than the best path found
+ if (boats < gate.boats[carts] || carts < gate.carts[boats])
+ return
+
// Been here before with same or more transports remaining
if (_supply_boats[here] >= boats && _supply_carts[here] >= carts)
return
@@ -5441,102 +5596,171 @@ function search_supply_reachable_summer(here, boats, carts) {
_supply_carts[here] = carts
}
- _supply_reached[here] = 1
-
- _supply_stat++
_supply_seen[here] = 1
+
if (boats > 0)
for (let next of data.locales[here].adjacent_by_waterway)
- if (!_supply_seen[next] && !_supply_stop[next])
- search_supply_reachable_summer(next, boats - 1, carts)
+ if (!_supply_stop[next] && !_supply_seen[next])
+ search_summer_path_pass1(next, end, boats-1, carts, gate)
+
if (carts > 0)
for (let next of data.locales[here].adjacent_by_trackway)
- if (!_supply_seen[next] && !_supply_stop[next])
- search_supply_reachable_summer(next, boats, carts - 1)
+ if (!_supply_stop[next] && !_supply_seen[next])
+ search_summer_path_pass1(next, end, boats, carts-1, gate)
+
_supply_seen[here] = 0
}
-function search_supply_reachable_winter(start, sleds) {
- _supply_reached[start] = 1
- if (0 < sleds) {
- let queue = [ start ]
- while (queue.length > 0) {
- let item = queue.shift()
- let here = item & 63
- let used = item >> 6
- for (let next of data.locales[here].adjacent) {
- if (!_supply_reached[next] && !_supply_stop[next]) {
- _supply_reached[next] = 1
- if (used + 1 < sleds)
- queue.push(next | ((used + 1) << 6))
- }
+function search_summer_path_pass2(path, here, end, boats, carts, gate) {
+ _supply_stat++
+
+ // Worse than the best path found
+ if (boats < gate.boats[carts] || carts < gate.carts[boats])
+ return
+
+ if (here === end) {
+ console.log(" path", path.map(wl=>data.locales[wl>>8].name).join(","), boats, carts)
+ let out1 = game.supply.path
+ for (let i = 0; i < path.length; ++i) {
+ let wayloc = path[i]
+ let out2 = map_get(out1, wayloc, null)
+ if (out2 === null) {
+ if (i < path.length - 1)
+ map_set(out1, wayloc, out2 = [])
+ else
+ map_set(out1, wayloc, 0)
}
+ out1 = out2
}
+ return
+ }
+
+ // Been here before with same or more transports remaining
+ if (_supply_boats[here] >= boats && _supply_carts[here] >= carts)
+ return
+
+ // First time here with this many transports remaining
+ if (_supply_boats[here] <= boats && _supply_carts[here] <= carts) {
+ _supply_boats[here] = boats
+ _supply_carts[here] = carts
}
+
+ _supply_seen[here] = 1
+
+ if (boats > 0) {
+ for (let [next, way] of data.locales[here].waterways) {
+ if (!_supply_stop[next] && !_supply_seen[next]) {
+ path.push((next << 8) | way)
+ search_summer_path_pass2(path, next, end, boats-1, carts, gate)
+ path.pop()
+ }
+ }
+ }
+
+ if (carts > 0) {
+ for (let [next, way] of data.locales[here].trackways) {
+ if (!_supply_stop[next] && !_supply_seen[next]) {
+ path.push((next << 8) | way)
+ search_summer_path_pass2(path, next, end, boats, carts-1, gate)
+ path.pop()
+ }
+ }
+ }
+
+ _supply_seen[here] = 0
}
-function search_supply_reachable_rasputitsa(start, boats) {
- _supply_reached[start] = 1
- if (0 < boats) {
- let queue = [ start ]
- while (queue.length > 0) {
- let item = queue.shift()
- let here = item & 63
- let used = item >> 6
- for (let next of data.locales[here].adjacent_by_waterway) {
- if (!_supply_reached[next] && !_supply_stop[next]) {
- _supply_reached[next] = 1
- if (used + 1 < boats)
- queue.push(next | ((used + 1) << 6))
- }
+// === ACTION: SUPPLY ===
+
+function update_supply_possible() {
+ if (game.actions < 1) {
+ game.supply = 0
+ return
+ }
+
+ let lord = find_lodya_lord_in_shared()
+ if (lord !== NOBODY) {
+ if (!is_winter()) {
+ if (!is_supply_forbidden(LOC_NOVGOROD)) {
+ if (get_lord_assets(lord, BOAT) >= 2 && update_supply_possible_lodya(-2))
+ return
+ if (get_lord_assets(lord, BOAT) >= 1 && update_supply_possible_lodya(-1))
+ return
}
+ if (get_lord_assets(lord, SHIP) >= 2 && update_supply_possible_lodya(2))
+ return
+ if (get_lord_assets(lord, SHIP) >= 1 && update_supply_possible_lodya(1))
+ return
}
+ update_supply_possible_lodya(0)
+ } else {
+ update_supply_possible_pass()
+ console.log("POSSIBLE SEARCH", _supply_stat, game.supply)
}
}
-function filter_usable_supply_seats(reachable) {
+function update_supply_possible_lodya(x) {
+ game.flags.lodya = x
+ update_supply_possible_pass()
+ console.log("LODYA POSSIBLE SEARCH", _supply_stat, x, game.supply)
+ return game.supply
+}
+
+function update_supply_possible_pass() {
+ init_supply()
+ init_supply_forbidden()
+ _supply_reached.fill(0)
let sources = []
- for_each_seat(
- game.command,
- (seat) => {
- if (set_has(reachable, seat))
- sources.push(seat)
- },
- true
- )
- return sources
+ for (let loc of game.supply.seats)
+ set_add(sources, loc)
+ for (let loc of game.supply.seaports)
+ set_add(sources, loc)
+ switch (current_season()) {
+ case SUMMER:
+ _supply_boats.fill(-1)
+ _supply_carts.fill(-1)
+ game.supply = search_supply_summer(get_lord_locale(game.command), game.supply.boats, game.supply.carts, sources)
+ break
+ case EARLY_WINTER:
+ case LATE_WINTER:
+ game.supply = search_supply_winter(get_lord_locale(game.command), game.supply.sleds, sources)
+ break
+ case RASPUTITSA:
+ game.supply = search_supply_rasputitsa(get_lord_locale(game.command), game.supply.boats, sources)
+ break
+ }
}
-function filter_usable_supply_seaports(reachable, ships) {
- if (ships > 0) {
- let sources = []
- if (game.active === TEUTONS) {
- for (let port of data.seaports) {
- if (set_has(reachable, port)) {
- set_add(sources, port)
- }
- }
- }
- if (game.active === RUSSIANS) {
- if (set_has(reachable, LOC_NOVGOROD)) {
- set_add(sources, LOC_NOVGOROD)
- }
- }
- return sources
+function search_supply_cost() {
+ init_supply_forbidden()
+ _supply_reached.fill(0)
+ switch (current_season()) {
+ case SUMMER:
+ _supply_boats.fill(-1)
+ _supply_carts.fill(-1)
+ search_supply_summer(get_lord_locale(game.command), game.supply.boats, game.supply.carts, null)
+ break
+ case EARLY_WINTER:
+ case LATE_WINTER:
+ search_supply_winter(get_lord_locale(game.command), game.supply.sleds, null)
+ break
+ case RASPUTITSA:
+ search_supply_rasputitsa(get_lord_locale(game.command), game.supply.boats, null)
+ break
}
- return null
+ console.log("SUPPLY COST", _supply_stat)
}
function can_action_supply() {
if (game.actions < 1)
return false
- return true // TODO - Lodya & pre-search?
+ return !!game.supply
}
function can_supply() {
- if (game.supply.seats > 0 && game.supply.supply_seats.length > 0)
+ if (game.supply.available > 0 && game.supply.seats.length > 0)
return true
- if (game.supply.ships > 0 && game.supply.supply_seaports.length > 0)
+ if (game.supply.ships > 0 && game.supply.seaports.length > 0)
return true
return false
}
@@ -5547,10 +5771,27 @@ function goto_supply() {
game.state = "supply_lodya"
} else {
init_supply()
+ resume_supply()
game.state = "supply_source"
}
}
+function resume_supply() {
+ if (game.supply.available + game.supply.ships === 0) {
+ game.supply.seats = []
+ game.supply.seaports = []
+ } else {
+ search_supply_cost()
+ game.supply.seats = game.supply.seats.filter(loc => _supply_reached[loc])
+ game.supply.seaports = game.supply.seaports.filter(loc => _supply_reached[loc])
+ }
+
+ if (can_supply())
+ game.state = "supply_source"
+ else
+ end_supply()
+}
+
states.supply_source = {
prompt() {
if (!can_supply()) {
@@ -5569,25 +5810,23 @@ states.supply_source = {
if (game.supply.ships > 0)
view.prompt += ` ${game.supply.ships} ship`
- if (game.supply.seats > 0)
- for (let source of game.supply.supply_seats)
+ if (game.supply.available > 0)
+ for (let source of game.supply.seats)
gen_action_locale(source)
if (game.supply.ships > 0)
- for (let source of game.supply.supply_seaports)
+ for (let source of game.supply.seaports)
gen_action_locale(source)
view.actions.end_supply = 1
},
locale(source) {
- // TODO: 2nd ed - no reusing of transports!
-
- if (game.supply.supply_seats.includes(source)) {
+ if (game.supply.seats.includes(source)) {
log(`Supplied from seat at %${source}.`)
if (is_famine_in_play()) {
log("Famine.")
game.flags.famine = 1
}
- array_remove_item(game.supply.supply_seats, source)
- game.supply.seats--
+ game.supply.available--
+ array_remove_item(game.supply.seats, source)
} else {
log(`Supplied from seaport at %${source}.`)
game.supply.ships--
@@ -5595,16 +5834,119 @@ states.supply_source = {
add_lord_assets(game.command, PROV, 1)
- if (!can_supply())
- end_supply()
+ spend_supply_transport(source)
},
end_supply: end_supply,
}
function end_supply() {
- game.supply = 0
spend_action(1)
resume_actions()
+ game.supply = 1 // supply is possible!
+}
+
+function spend_supply_transport(source) {
+ if (source === get_lord_locale(game.command)) {
+ resume_supply()
+ return
+ }
+
+ switch (current_season()) {
+ case SUMMER:
+ game.supply.here = get_lord_locale(game.command)
+ game.supply.end = source
+ game.state = "supply_path"
+ init_summer_path()
+ break
+ case EARLY_WINTER:
+ case LATE_WINTER:
+ search_supply_cost()
+ game.supply.sleds -= _supply_cost[source]
+ resume_supply()
+ break
+ case RASPUTITSA:
+ search_supply_cost()
+ game.supply.boats -= _supply_cost[source]
+ resume_supply()
+ break
+ }
+}
+
+states.supply_path = {
+ prompt() {
+ view.prompt = "Supply: Trace path to supply source."
+ view.supply = [ game.supply.here, game.supply.end ]
+ if (game.supply.boats > 0)
+ view.prompt += ` ${game.supply.boats} boat`
+ if (game.supply.carts > 0)
+ view.prompt += ` ${game.supply.carts} cart`
+ for (let i = 0; i < game.supply.path.length; i += 2) {
+ let wayloc = game.supply.path[i]
+ gen_action_locale(wayloc >> 8)
+ }
+ },
+ locale(next) {
+ let useloc = -1
+ let useway = -1
+ let twoway = false
+ for (let i = 0; i < game.supply.path.length; i += 2) {
+ let wayloc = game.supply.path[i]
+ let way = wayloc & 255
+ let loc = wayloc >> 8
+ if (loc === next) {
+ if (useloc < 0) {
+ useloc = loc
+ useway = way
+ } else {
+ twoway = true
+ }
+ }
+ }
+ if (twoway) {
+ game.state = "supply_path_way"
+ game.supply.next = next
+ } else {
+ walk_supply_path_way(next, useway)
+ }
+ },
+}
+
+function walk_supply_path_way(next, way) {
+ let type = data.ways[way].type
+ if (type === "waterway")
+ game.supply.boats--
+ else
+ game.supply.carts--
+ game.supply.here = next
+ game.supply.path = map_get(game.supply.path, (next << 8) | way)
+ if (game.supply.path === 0)
+ resume_supply()
+ else
+ // Auto-pick path if only one choice.
+ if (AUTOWALK && game.supply.path.length === 2)
+ walk_supply_path_way(game.supply.path[0] >> 8, game.supply.path[0] & 255)
+}
+
+states.supply_path_way = {
+ prompt() {
+ view.prompt = "Supply: Trace path to supply source."
+ view.supply = [ game.supply.here, game.supply.end ]
+ if (game.supply.boats > 0)
+ view.prompt += ` ${game.supply.boats} boat`
+ if (game.supply.carts > 0)
+ view.prompt += ` ${game.supply.carts} cart`
+ for (let i = 0; i < game.supply.path.length; i += 2) {
+ let wayloc = game.supply.path[i]
+ let way = wayloc & 255
+ let loc = wayloc >> 8
+ if (loc === game.supply.next)
+ gen_action_way(way)
+ }
+ },
+ way(way) {
+ game.state = "supply_path"
+ walk_supply_path_way(game.supply.next, way)
+ },
}
// === ACTION: FORAGE ===
@@ -5930,6 +6272,7 @@ states.sail = {
spend_all_actions()
resume_actions()
+ update_supply_possible()
},
}
@@ -6424,7 +6767,6 @@ states.array_attacker = {
}
},
array: action_array_place,
- array: action_array_place,
lord: action_select_lord,
end_array: end_array_attacker,
}
@@ -7481,10 +7823,6 @@ for each battle step:
*/
-function format_group(g) {
- return g.map(p=>lord_name[game.battle.array[p]]).join(", ")
-}
-
function format_strike_step() {
// TODO: format strike group and target groups too?
if (game.battle.storm)
@@ -7494,7 +7832,6 @@ function format_strike_step() {
function format_hits() {
if (game.battle.xhits > 0 && game.battle.hits > 0) {
- return `${game.battle.xhits} crossbow hits and ${game.battle.hits} hits`
if (game.battle.xhits > 1 && game.battle.hits > 1)
return `${game.battle.xhits} crossbow hits and ${game.battle.hits} hits`
else if (game.battle.xhits > 1)
diff --git a/tools/gendata.js b/tools/gendata.js
index c671cf6..05ac486 100644
--- a/tools/gendata.js
+++ b/tools/gendata.js
@@ -5,6 +5,7 @@
const fs = require('fs')
function cmpnum(a,b) { return a - b }
+function cmpnum2(a,b) { return a[0] - b[0] }
// :r !python3 genboxes.py
const boxes = {
@@ -307,24 +308,33 @@ locales.forEach(loc => {
loc.adjacent = []
loc.adjacent_by_trackway = []
loc.adjacent_by_waterway = []
+ loc.trackways = []
+ loc.waterways = []
for (let data of loc.ways) {
let to = data[0]
for (let i = 1; i < data.length; ++i) {
let way = data[i]
- console.log("WAY", loc.name, to, way)
if (!loc.adjacent.includes(to))
loc.adjacent.push(to)
- if (ways[way].type === "trackway")
- if (!loc.adjacent_by_trackway.includes(to))
+ if (ways[way].type === "trackway") {
+ if (!loc.adjacent_by_trackway.includes(to)) {
loc.adjacent_by_trackway.push(to)
- if (ways[way].type === "waterway")
- if (!loc.adjacent_by_waterway.includes(to))
+ loc.trackways.push([to,way])
+ }
+ }
+ if (ways[way].type === "waterway") {
+ if (!loc.adjacent_by_waterway.includes(to)) {
loc.adjacent_by_waterway.push(to)
+ loc.waterways.push([to,way])
+ }
+ }
}
}
loc.adjacent.sort(cmpnum)
loc.adjacent_by_trackway.sort(cmpnum)
loc.adjacent_by_waterway.sort(cmpnum)
+ loc.trackways.sort(cmpnum2)
+ loc.trackways.sort(cmpnum2)
})
function seats(list) {