diff options
-rw-r--r-- | play.css | 18 | ||||
-rw-r--r-- | play.html | 1 | ||||
-rw-r--r-- | play.js | 249 | ||||
-rw-r--r-- | rules.js | 109 | ||||
-rw-r--r-- | tools/genroads.js | 53 | ||||
-rw-r--r-- | tools/makeroads.js | 30 | ||||
-rw-r--r-- | tools/roads.svg | 693 |
7 files changed, 1135 insertions, 18 deletions
@@ -195,6 +195,23 @@ body.Saracens header.your_turn { background-color: hsl(120, 50%, 70%); } /* 50% #sea { visibility: hidden; } #sea.highlight { visibility: visible; cursor: pointer; } +.road { + position: absolute; + width: 24px; + height: 24px; + border-radius: 50%; + text-align: center; + line-height: 24px; + font-size: 16px; + font-weight: bold; + background-color: hsl(35, 15%, 50%); + color: hsl(35, 15%, 30%); + box-shadow: 0px 0px 4px 1px #0004; +} + +.road.Franks { background-color: khaki; color: hsl(54, 27%, 35%); } +.road.Saracens { background-color: seagreen; color: honeydew; } + /* BLOCKS */ body.shift .block.known:hover { @@ -382,6 +399,7 @@ body.shift .block.known:hover { body.shift #padmap #map > .block.known:hover { transform: rotate(90deg) scale(2) !important; } + .road { transform: rotate(90deg); } #blocks > .block { transform: rotate(90deg); } #blocks > .block.r1 { transform: rotate(0deg); } #blocks > .block.r2 { transform: rotate(-90deg); } @@ -78,6 +78,7 @@ <svg id="svgmap" width="1275px" height="2475px" viewBox="0 0 1275 2475"> <path id="sea" d="M30 1201v1170l2-1c1 0 4-1 5-3 2-1 4-2 5-2 2 0 5-1 7-2 3-2 7-4 11-6 3-2 7-4 8-6 1-1 3-2 3-2 2 0 47-23 54-27 4-2 10-6 14-8 7-3 13-7 27-19 4-3 5-4 5-8l1-4h5c4 0 7-1 12-5 6-4 27-24 41-41 7-8 20-21 23-24 1-1 4-3 5-6l6-6c1-1 3-3 4-5 2-3 3-4 10-12 3-3 6-7 6-9 1-1 2-3 3-4 2-1 5-5 7-9 2-3 4-6 5-6 2 0 5-6 5-9 0-2 2-6 5-10s5-7 5-8 0-1 1-1 1-1 2-3c1-4 3-9 8-14 4-5 7-12 7-14 0-1 2-6 4-10 3-4 5-9 6-10 0-1 1-4 3-5 1-2 2-4 2-5 0 0 1-3 3-5 2-3 3-5 3-10 0-6 1-9 4-14 2-3 3-7 3-8 0-3 4-20 6-22 2-3 4-11 4-17 0-1 0-3 1-3 0 0 2-2 2-5 1-2 3-7 4-9 1-3 2-9 3-15 1-11 1-10 0-18 0-5 0-7 1-9s2-5 2-7c1-3 1-5 2-7 0-1 1-4 1-6v-8c4-22 4-23 3-26-1-2-1-4 0-6s1-8 1-13 1-9 1-10v-5c-1-3-1-8-1-12v-10-36c0-8-1-21-2-23 0-1-1-5-1-10 0-14-1-27-2-35-1-3-1-8-1-11s-2-10-5-17c-1-1-1-2 0-3 1-2 1-2 0-3 0-1-1-2 0-2 0-1 0-2-1-3 0-1 0-4-1-7 0-3-1-6-2-9-1-2-2-4-1-4 0-1-1-2-2-2-1-2-1-2 1-3s2-1 3-8c0-5 0-8-2-11-1-2-1-7-1-11-1-5-1-8-3-12-4-6-3-10 0-13 5-4 18-4 26 0l5 3 3-2c1-2 4-3 6-4 3-1 10-9 15-16 2-3 2-3 1-12-2-13-4-18-9-16-2 0-3 0-2-3 0-2-1-4-3-7-2-4-2-5-3-16 0-22-1-28-2-29s-1-2 0-3c2-1-1-9-3-12-2-1-2-2-1-5 0-4 2-7 8-9 9-5 10-5 10-6s1-3 3-5c1-2 2-5 2-6-1-1-1-2 0-3 1 0 1-2 1-2-1-2 0-2 3-2 2 0 4-1 6-3 3-3 4-9 3-10s0-2 1-3c4-3 3-5 0-16-2-5-3-6-6-8-2-1-6-2-8-2-5 0-5-2-1-2 6-2 7-2 8-4 0-2 1-5 2-8 0-3 1-6 2-7s1-3 1-3c-1-1 0-2 0-3 1 0 1-3 1-6v-13c0-4-1-8-1-9-1-2-1-3 1-4 2-2 2-2 1-5 0-2-1-4 0-4 0-1 1-3 2-5 1-3 1-4-1-5-1-1-1-1 2-3 2-2 3-3 3-4s1-2 3-2c4-2 12-10 16-16 2-2 3-4 4-4 0 0 1-1 2-3 1-1 2-4 4-6s2-3 2-6c-1-2-1-5-1-6s0-3-1-4-1-1 1-3c4-3 6-11 4-14-1-2-1-2 1-4 1-2 1-2 0-6-1-2-2-5-1-6 0 0 0-2-1-2-1-2 0-2 3-4l4-3-2-6c-1-3-2-7-3-7-2-1 1-4 4-4s7-4 7-8c0-1-1-4-2-5-2-3-3-6-2-7 1 0 1-1 1-2s2-6 5-11c4-7 4-9 4-13-1-11-12-33-16-33-2 0-7-3-7-4s5-4 8-5c1 0 7 0 12-1s10-1 11-2c3-2 6-1 7 1 1 3 3 4 3 2 0-3 3-5 7-6 7-2 11-4 13-9 1-2 2-4 2-5v-7c-1-4 0-6 1-7 1 0 2-2 1-4 0-2 0-3 3-4 1 0 3-2 3-3 1-1 2-2 5-2 5-1 8-5 6-8-1-2-6-4-11-4-1 0-3-1-4-1-2-2-1-13 0-16 1-1 2-5 2-9 1-8 1-9-2-12-2-2-4-4-5-4 0 0-2-1-2-3-2-2-3-3-6-4-6-1-7-1-7-3 0-1-1-3-2-5-1-3-2-5-2-7 0-1-1-3-2-4-1-3-1-5 3-7 3-3 6-9 4-11 0-1-1-3 0-4 0-1-1-2-3-4-3-3-3-3-2-4 1-2 1-2-2-3-4-3-4-4 1-8 3-2 3-3 3-6-1-4-1-4 4-6 4-3 5-3 8-2 7 1 14-2 15-6v-5c1-1 0-4 0-7-2-3-2-5-1-5 0-1 1-3 1-4 0-3 0-3 3-5 2 0 5-1 7-1 1 0 3-1 5-2 0-1 2-2 3-2l4-2c1-2 3-3 4-3 0 0 2-1 3-3 1-1 5-3 7-4 4-2 4-3 4-6 0-1-1-3-2-4-2-1-4-3-5-4-1-2-4-3-5-4-5-1-3-2 4-2 6 0 13-1 14-3 1-1 11-5 16-6 2-1 4-2 4-3 0-2 5-4 10-3 3 0 4 0 9-5 3-3 6-6 6-7 0-2 13-13 15-14 2 0 5-1 8-2l5-2v-6c0-5-1-7-4-14-2-4-6-10-8-12-5-6-28-25-36-28-2-1-6-5-9-8-4-3-7-6-8-7-4-1-8-6-11-12-2-5-3-7-7-9-2-2-5-3-5-3-1 0-9-5-10-6-5-5-19-15-22-16-1-1-2-2-2-3l-5-5c-7-5-9-9-9-15-1-4-2-7-5-9 0-1-1-2-1-4s0-3-1-3-1-2-2-4c0-3 0-4 4-8 2-2 4-5 4-5 0-2-12-14-15-16-4-2-5-6-3-9s1-5-3-7c-3-1-4-4-2-5 1-1 1-2 1-3s1-3 3-4c2-2 3-3 3-5 0-1 0-4 2-6 2-3 3-5 1-8s0-5 5-8c5-2 9-6 9-8 0-4-20-18-30-22-8-3-10-6-10-12-1-6-1-7-6-13-2-3-4-6-4-8 0-1-2-5-6-9-7-9-8-11-7-15 0-3 0-3-3-4-1-1-6-2-11-2-11-2-10-2-27-9-4-2-8-3-11-3s-10-1-15-2c-5 0-10-1-11-1-7 0-15 2-19 4s-8 2-9-1c0-1 0-3 1-4 2-3 1-6-1-8-1-1-6-2-13-3-14-2-15-2-13-5 1-2 1-3 0-4s-2-1-6 1c-2 1-4 2-5 2 0-1 4-4 11-8 5-2 7-3 7-5 0-1 1-2 3-3 1 0 2-1 2-3s0-3 1-4c1 0 1-1 1-3s-1-3-4-5c-2-2-5-3-6-4-4-1-5-3-2-7 1-2 3-3 7-4 5-1 5-1 8-7 1-3 3-6 5-7 1-1 3-4 3-7 1-3 3-7 3-8 1-1 1-3 1-4-1-3-11-13-17-16-3-2-7-5-10-8-2-2-5-4-7-4-1 0-2-1-3-2s-2-2-4-3l-3-1 7-2c3 0 9-1 12-2 7-1 12-3 18-7 5-4 7-8 7-15 0-5 1-7 10-14 1-1 2-3 2-4 0-3 5-14 9-18s5-6 4-9c0-2 0-3 4-5 6-2 6-4-1-9-6-4-9-7-14-13-2-1-5-4-7-6-3-2-7-5-8-7-2-1-6-3-9-4-2-1-5-2-6-3-2-1-13-6-15-6s-11-4-11-5c-1-1-9-5-18-10-18-8-30-15-35-18-2-2-12-5-31-10-7-3-12-7-12-11 0-2 1-4 5-7 4-4 6-7 7-10l2-5 9-5c10-4 12-6 12-12s1-7 8-10c10-5 11-7 11-12-1-4-1-5 4-9 4-5 9-8 21-12 4-1 11-4 15-7 9-4 10-5 11-9 2-8 8-13 18-16 8-2 11-6 12-13v-5H30v1171z"/> </svg> +<div id="roads"></div> <div id="blocks"></div> <div id="offmap" style="visibility:hidden"></div> <div id="towns"></div> @@ -16,6 +16,22 @@ function set_has(set, item) { return false } +function map_get(map, key, missing) { + let a = 0 + let b = (map.length >> 1) - 1 + while (a <= b) { + let m = (a + b) >> 1 + let x = map[m<<1] + if (key < x) + b = m - 1 + else if (key > x) + a = m + 1 + else + return map[(m<<1)+1] + } + return missing +} + const FRANKS = "Franks" const SARACENS = "Saracens" @@ -83,6 +99,193 @@ const VICTORY_TOWNS = [ town_index["Jerusalem"] ] +// :r !node tools/genroads.js +const ROADS_XY = { + "Germania / Aleppo": [975,64], + "Germania / St. Simeon": [297,100], + "Germania / Antioch": [512,54], + "Aleppo / Artah": [939,147], + "Aleppo / Zerdana": [1045,218], + "Artah / Zerdana": [941,227], + "Artah / Harim": [761,127], + "Zerdana / Hama": [1028,388], + "Zerdana / Albara": [942,342], + "Hama / Homs": [1133,571], + "Hama / Albara": [950,450], + "Hama / Monterrand": [964,554], + "Homs / Lacum": [980,780], + "Homs / Qaddas": [1156,780], + "Homs / Monterrand": [986,643], + "Homs / Krak": [927,726], + "Lacum / Qaddas": [1063,906], + "Lacum / Baalbek": [889,953], + "Lacum / Krak": [867,794], + "Qaddas / Damascus": [1170,1038], + "Baalbek / Anjar": [812,1082], + "Baalbek / Tripoli": [776,953], + "Anjar / Damascus": [869,1177], + "Anjar / Beirut": [635,1137], + "Anjar / Beaufort": [718,1198], + "Damascus / Banyas": [936,1267], + "Damascus / Ashtera": [1051,1339], + "Banyas / Ashtera": [907,1411], + "Banyas / Beaufort": [666,1389], + "Banyas / Tiberias": [730,1455], + "Ashtera / Ajlun": [1046,1491], + "Ajlun / Tiberias": [911,1554], + "Ajlun / Amman": [1035,1711], + "St. Simeon / Antioch": [412,208], + "Antioch / Harim": [600,127], + "Antioch / Kassab": [477,260], + "Harim / Shughur": [684,214], + "Kassab / Latakia": [406,388], + "Shughur / Saône": [635,374], + "Shughur / Albara": [704,345], + "Latakia / Saône": [524,432], + "Latakia / Margat": [492,488], + "Saône / Albara": [735,421], + "Margat / Tartus": [547,654], + "Monterrand / Krak": [851,664], + "Tartus / Krak": [689,722], + "Tartus / Tripoli": [669,786], + "Krak / Tripoli": [757,807], + "Tripoli / Botron": [571,933], + "Botron / Beirut": [567,1052], + "Beirut / Sidon": [503,1219], + "Sidon / Tyre": [460,1330], + "Sidon / Beaufort": [542,1324], + "Tyre / Beaufort": [546,1388], + "Tyre / Acre": [427,1492], + "Acre / Tiberias": [601,1572], + "Acre / Legio": [518,1653], + "Acre / Caesarea": [390,1658], + "Tiberias / Baisan": [759,1632], + "Legio / Baisan": [653,1685], + "Legio / Nablus": [619,1724], + "Baisan / Nablus": [675,1736], + "Baisan / Damiya": [811,1723], + "Caesarea / Nablus": [520,1777], + "Caesarea / Jaffa": [409,1837], + "Nablus / Damiya": [745,1797], + "Nablus / Jerusalem": [653,1883], + "Damiya / Amman": [934,1781], + "Damiya / Jericho": [845,1883], + "Amman / Jericho": [958,1892], + "Amman / Kerak": [1130,1973], + "Jaffa / Ramallah": [463,1932], + "Jaffa / Ascalon": [391,2004], + "Ramallah / Jerusalem": [586,1972], + "Ramallah / Ascalon": [448,2014], + "Jerusalem / Jericho": [789,1954], + "Jerusalem / Hebron": [677,2050], + "Jericho / Kerak": [952,1976], + "Ascalon / Lachish": [439,2128], + "Ascalon / Gaza": [330,2133], + "Lachish / Hebron": [556,2151], + "Lachish / Gaza": [430,2172], + "Hebron / Dimona": [658,2242], + "Hebron / Zoar": [779,2195], + "Kerak / Zoar": [998,2140], + "Gaza / Beersheba": [369,2265], + "Gaza / Egypt": [225,2266], + "Beersheba / Dimona": [539,2293], + "Beersheba / Egypt": [415,2312], + "Dimona / Zoar": [788,2291], +} + +const ROADS_BG = { + "Germania / Aleppo": "hsl(36, 72%, 76%)", + "Germania / St. Simeon": "hsl(37, 79%, 77%)", + "Germania / Antioch": "hsl(61, 30%, 58%)", + "Aleppo / Artah": "hsl(37, 80%, 78%)", + "Aleppo / Zerdana": "hsl(36, 81%, 81%)", + "Artah / Zerdana": "hsl(37, 82%, 78%)", + "Artah / Harim": "hsl(35, 68%, 70%)", + "Zerdana / Hama": "hsl(36, 78%, 77%)", + "Zerdana / Albara": "hsl(36, 76%, 77%)", + "Hama / Homs": "hsl(36, 78%, 77%)", + "Hama / Albara": "hsl(39, 69%, 74%)", + "Hama / Monterrand": "hsl(34, 76%, 67%)", + "Homs / Lacum": "hsl(42, 57%, 70%)", + "Homs / Qaddas": "hsl(36, 82%, 78%)", + "Homs / Monterrand": "hsl(34, 78%, 70%)", + "Homs / Krak": "hsl(36, 74%, 72%)", + "Lacum / Qaddas": "hsl(36, 60%, 75%)", + "Lacum / Baalbek": "hsl(45, 45%, 68%)", + "Lacum / Krak": "hsl(38, 59%, 65%)", + "Qaddas / Damascus": "hsl(36, 80%, 78%)", + "Baalbek / Anjar": "hsl(48, 43%, 68%)", + "Baalbek / Tripoli": "hsl(56, 30%, 55%)", + "Anjar / Damascus": "hsl(42, 59%, 73%)", + "Anjar / Beirut": "hsl(51, 39%, 61%)", + "Anjar / Beaufort": "hsl(47, 43%, 67%)", + "Damascus / Banyas": "hsl(38, 58%, 78%)", + "Damascus / Ashtera": "hsl(38, 65%, 74%)", + "Banyas / Ashtera": "hsl(38, 56%, 75%)", + "Banyas / Beaufort": "hsl(53, 36%, 59%)", + "Banyas / Tiberias": "hsl(54, 35%, 62%)", + "Ashtera / Ajlun": "hsl(37, 70%, 75%)", + "Ajlun / Tiberias": "hsl(46, 48%, 68%)", + "Ajlun / Amman": "hsl(37, 85%, 80%)", + "St. Simeon / Antioch": "hsl(66, 32%, 58%)", + "Antioch / Harim": "hsl(46, 48%, 69%)", + "Antioch / Kassab": "hsl(66, 32%, 58%)", + "Harim / Shughur": "hsl(41, 61%, 74%)", + "Kassab / Latakia": "hsl(66, 32%, 58%)", + "Shughur / Saône": "hsl(47, 48%, 69%)", + "Shughur / Albara": "hsl(41, 53%, 72%)", + "Latakia / Saône": "hsl(60, 31%, 61%)", + "Latakia / Margat": "hsl(63, 32%, 59%)", + "Saône / Albara": "hsl(41, 60%, 74%)", + "Margat / Tartus": "hsl(50, 39%, 61%)", + "Monterrand / Krak": "hsl(40, 57%, 68%)", + "Tartus / Krak": "hsl(62, 31%, 59%)", + "Tartus / Tripoli": "hsl(55, 35%, 65%)", + "Krak / Tripoli": "hsl(52, 39%, 64%)", + "Tripoli / Botron": "hsl(72, 33%, 57%)", + "Botron / Beirut": "hsl(44, 43%, 57%)", + "Beirut / Sidon": "hsl(42, 57%, 79%)", + "Sidon / Tyre": "hsl(45, 42%, 71%)", + "Sidon / Beaufort": "hsl(70, 33%, 57%)", + "Tyre / Beaufort": "hsl(69, 32%, 56%)", + "Tyre / Acre": "hsl(58, 33%, 63%)", + "Acre / Tiberias": "hsl(56, 27%, 60%)", + "Acre / Legio": "hsl(61, 31%, 57%)", + "Acre / Caesarea": "hsl(52, 40%, 70%)", + "Tiberias / Baisan": "hsl(49, 44%, 67%)", + "Legio / Baisan": "hsl(56, 38%, 64%)", + "Legio / Nablus": "hsl(58, 32%, 60%)", + "Baisan / Nablus": "hsl(48, 50%, 71%)", + "Baisan / Damiya": "hsl(51, 40%, 65%)", + "Caesarea / Nablus": "hsl(59, 32%, 60%)", + "Caesarea / Jaffa": "hsl(51, 40%, 70%)", + "Nablus / Damiya": "hsl(39, 64%, 77%)", + "Nablus / Jerusalem": "hsl(45, 51%, 71%)", + "Damiya / Amman": "hsl(46, 47%, 69%)", + "Damiya / Jericho": "hsl(48, 48%, 69%)", + "Amman / Jericho": "hsl(40, 66%, 75%)", + "Amman / Kerak": "hsl(36, 72%, 79%)", + "Jaffa / Ramallah": "hsl(58, 34%, 61%)", + "Jaffa / Ascalon": "hsl(46, 44%, 69%)", + "Ramallah / Jerusalem": "hsl(47, 46%, 68%)", + "Ramallah / Ascalon": "hsl(49, 43%, 66%)", + "Jerusalem / Jericho": "hsl(41, 52%, 70%)", + "Jerusalem / Hebron": "hsl(41, 62%, 75%)", + "Jericho / Kerak": "hsl(40, 58%, 72%)", + "Ascalon / Lachish": "hsl(44, 57%, 72%)", + "Ascalon / Gaza": "hsl(45, 51%, 74%)", + "Lachish / Hebron": "hsl(42, 56%, 71%)", + "Lachish / Gaza": "hsl(41, 65%, 74%)", + "Hebron / Dimona": "hsl(40, 60%, 73%)", + "Hebron / Zoar": "hsl(38, 59%, 72%)", + "Kerak / Zoar": "hsl(38, 78%, 77%)", + "Gaza / Beersheba": "hsl(36, 82%, 76%)", + "Gaza / Egypt": "hsl(50, 44%, 63%)", + "Beersheba / Dimona": "hsl(36, 71%, 78%)", + "Beersheba / Egypt": "hsl(34, 80%, 71%)", + "Dimona / Zoar": "hsl(36, 78%, 77%)", +} + let label_layout = window.localStorage['crusader-rex/label-layout'] || 'spread' function set_spread_layout() { @@ -105,6 +308,7 @@ let ui = { cards: [], card_backs: [], towns: [], + roads: [], blocks: [], battle_menu: [], battle_block: [], @@ -478,6 +682,21 @@ function build_map() { ui.blocks[b] = build_map_block(b, block) build_battle_block(b, block) } + + for (let name in ROADS_XY) { + let [x, y] = ROADS_XY[name] + let [a, b] = name.split(" / ") + let id = town_index[a] * 100 + town_index[b] + let e = document.createElement("div") + e.my_id = id + e.my_bgnd = ROADS_BG[name] + e.my_show = "road " + ROADS[id] + e.className = "hide" + e.style.left = (x - 12) + "px" + e.style.top = (y - 12) + "px" + ui.roads.push(e) + document.getElementById("roads").appendChild(e) + } } function update_steps(b, steps, element) { @@ -736,6 +955,36 @@ function update_map() { ui.blocks[view.who].classList.add('selected') for (let b of view.castle) ui.blocks[b].classList.add('castle') + + for (let e of ui.roads) { + let u = map_get(view.last_used, e.my_id, 0) + let n = map_get(view.road_limit, e.my_id, "") + if (view.main_road && set_has(view.main_road, e.my_id)) + n += "*" + switch (u) { + case 1: + e.style.backgroundColor = null + e.className = "road Franks" + e.textContent = n + break + case 2: + e.style.backgroundColor = null + e.className = "road Saracens" + e.textContent = n + break + case 0: + if (n) { + e.style.backgroundColor = e.my_bgnd + e.className = e.my_show + e.textContent = n + } else { + e.style.backgroundColor = null + e.className = "hide" + e.textContent = "" + } + break + } + } } function update_card_display(element, card, prior_card) { @@ -199,6 +199,14 @@ function print_summary(text, skip_if_empty = false) { logi("nothing.") } +const id_player = [ null, FRANKS, SARACENS ] + +function player_id(p) { + if (p === FRANKS) return 1 + if (p === SARACENS) return 2 + return 0 +} + function enemy(p) { if (p === FRANKS) return SARACENS if (p === SARACENS) return FRANKS @@ -365,11 +373,11 @@ function road_id(a, b) { } function road_was_last_used_by_enemy(from, to) { - return game.last_used[road_id(from, to)] === enemy(game.active) + return map_get(game.last_used, road_id(from, to)) === player_id(enemy(game.active)) } function road_was_last_used_by_friendly(from, to) { - return game.last_used[road_id(from, to)] === game.active + return map_get(game.last_used, road_id(from, to)) === player_id(game.active) } function road_type(a, b) { @@ -377,11 +385,15 @@ function road_type(a, b) { } function road_limit(a, b) { - return game.road_limit[road_id(a,b)] || 0 + return map_get(game.road_limit, road_id(a,b), 0) +} + +function set_road_limit(a, b, n) { + map_set(game.road_limit, road_id(a,b), n) } function reset_road_limits() { - game.road_limit = {} + game.road_limit.length = 0 } function count_player(p, where) { @@ -749,7 +761,7 @@ function can_use_richards_sea_legs(who, to) { if (!game.attacker[to]) return true if (game.attacker[to] === FRANKS) - return (game.main_road[to] === "England") + return (get_main_road(to) === ENGLAND) } } return false @@ -1165,7 +1177,7 @@ function start_game_turn() { // Reset movement and attack tracking state reset_road_limits() - game.last_used = {} + game.last_used = [] game.attacker = {} set_clear(game.reserves1) set_clear(game.reserves2) @@ -1313,7 +1325,7 @@ function start_player_turn() { log("") log(".h2 " + game.active) reset_road_limits() - game.main_road = {} + game.main_road = [] let card = CARDS[game.active === FRANKS ? game.f_card : game.s_card] if (card.event) goto_event_card(card.event) @@ -1533,12 +1545,20 @@ function queue_attack(who, round) { } } +function get_main_road(to) { + return map_get(game.main_road, to, NOWHERE) +} + +function set_main_road(to, x) { + map_set(game.main_road, to, x) +} + function move_block(who, from, to) { game.location[who] = to - game.road_limit[road_id(from, to)] = road_limit(from, to) + 1 + set_road_limit(from, to, road_limit(from, to) + 1) game.distance ++ if (is_contested_field(to)) { - game.last_used[road_id(from, to)] = game.active + map_set(game.last_used, road_id(from, to), player_id(game.active)) // 6.56 Main Attack relief force by Player 2 arrives one round later than normal let relief_delay = 0 @@ -1548,21 +1568,21 @@ function move_block(who, from, to) { if (!game.attacker[to]) { game.attacker[to] = game.active - game.main_road[to] = from + set_main_road(to, from) return queue_attack(who, 1 + relief_delay) } else { // Attacker main attack or reinforcements if (game.attacker[to] === game.active) { - if (game.main_road[to] !== from) + if (get_main_road(to) !== from) return queue_attack(who, 2 + relief_delay) return queue_attack(who, 1 + relief_delay) } // Defender reinforcements - if (!game.main_road[to]) - game.main_road[to] = from + if (!get_main_road(to)) + set_main_road(to, from) - if (game.main_road[to] === from) { + if (get_main_road(to) === from) { return queue_attack(who, 2) } else { return queue_attack(who, 3) @@ -1936,7 +1956,7 @@ states.sea_move_to = { } else if (!is_friendly_port(to)) { // English Crusaders attack! game.attacker[to] = FRANKS - game.main_road[to] = "England" + set_main_road(to, ENGLAND) log(game.active + " sea moved:") logi("#" + from + " \u2192 #" + to + ATTACK_MARK + ".") @@ -2324,6 +2344,7 @@ states.regroup = { }, end_regroup: function () { clear_undo() + reset_road_limits() print_summary(game.active + " regrouped:", true) if (game.assassins) { delete game.assassins @@ -3833,11 +3854,11 @@ exports.setup = function (seed, scenario, options) { steps: [], attacker: {}, - road_limit: {}, - last_used: {}, + road_limit: [], + last_used: [], castle: [], - main_road: {}, + main_road: [], moved: [], reserves1: [], reserves2: [], @@ -3923,6 +3944,8 @@ exports.view = function(state, current) { steps: game.steps, moved: game.moved, sieges: make_siege_view(), + last_used: game.last_used, + road_limit: game.road_limit, battle: null, prompt: null, } @@ -3932,6 +3955,13 @@ exports.view = function(state, current) { if (game.winter_campaign && game.winter_campaign !== game.p1 && game.winter_campaign !== game.p2) view.winter_campaign = game.winter_campaign + if (game.main_road && game.main_road.length > 0) { + view.main_road = [] + for (let i = 0; i < game.main_road.length; i += 2) + if (game.main_road[i+1] !== ENGLAND) + set_add(view.main_road, road_id(game.main_road[i+0], game.main_road[i+1])) + } + states[game.state].prompt(view, current) if (states[game.state].show_battle) @@ -3960,6 +3990,15 @@ function array_insert(array, index, item) { return array } +function array_insert_pair(array, index, key, value) { + for (let i = array.length; i > index; i -= 2) { + array[i] = array[i-2] + array[i+1] = array[i-1] + } + array[index] = key + array[index+1] = value +} + function set_clear(set) { set.length = 0 } @@ -4028,6 +4067,40 @@ function set_toggle(set, item) { return array_insert(set, a, item) } +function map_get(map, key, missing) { + let a = 0 + let b = (map.length >> 1) - 1 + while (a <= b) { + let m = (a + b) >> 1 + let x = map[m<<1] + if (key < x) + b = m - 1 + else if (key > x) + a = m + 1 + else + return map[(m<<1)+1] + } + return missing +} + +function map_set(map, key, value) { + let a = 0 + let b = (map.length >> 1) - 1 + while (a <= b) { + let m = (a + b) >> 1 + let x = map[m<<1] + if (key < x) + b = m - 1 + else if (key > x) + a = m + 1 + else { + map[(m<<1)+1] = value + return + } + } + array_insert_pair(map, a<<1, key, value) +} + // Fast deep copy for objects without cycles function object_copy(original) { if (Array.isArray(original)) { diff --git a/tools/genroads.js b/tools/genroads.js new file mode 100644 index 0000000..71b1c45 --- /dev/null +++ b/tools/genroads.js @@ -0,0 +1,53 @@ +const fs = require("fs") + +const { round, floor, ceil } = Math + +let output = {} +let mode, name, x, y, w, h, cx, cy, rx, ry + +function flush() { + if (mode === 'circle') { + output[name] = [ cx, cy ] + } + x = y = w = h = cx = cy = rx = ry = 0 + name = null +} + +for (let line of fs.readFileSync("tools/roads.svg", "utf-8").split("\n")) { + line = line.trim() + if (line.startsWith("<rect")) { + flush() + mode = "rect" + x = y = w = h = 0 + } else if (line.startsWith("<ellipse") || line.startsWith("<circle")) { + flush() + mode = "circle" + cx = cy = rx = ry = 0 + } else if (line.startsWith('x="')) + x = round(Number(line.split('"')[1])) + else if (line.startsWith('y="')) + y = round(Number(line.split('"')[1])) + else if (line.startsWith('width="')) + w = round(Number(line.split('"')[1])) + else if (line.startsWith('height="')) + h = round(Number(line.split('"')[1])) + else if (line.startsWith('cx="')) + cx = round(Number(line.split('"')[1])) + else if (line.startsWith('cy="')) + cy = round(Number(line.split('"')[1])) + else if (line.startsWith('r="')) + rx = ry = round(Number(line.split('"')[1])) + else if (line.startsWith('rx="')) + rx = round(Number(line.split('"')[1])) + else if (line.startsWith('ry="')) + ry = round(Number(line.split('"')[1])) + else if (line.startsWith('inkscape:label="')) + name = line.split('"')[1] +} + +flush() + +console.log("const ROADS_XY = {") +for (let key in output) + console.log("\t\"" + key + "\": " + JSON.stringify(output[key]) + ",") +console.log("}") diff --git a/tools/makeroads.js b/tools/makeroads.js new file mode 100644 index 0000000..edff0b9 --- /dev/null +++ b/tools/makeroads.js @@ -0,0 +1,30 @@ +const print = console.log + +const data = require("../data.js") + +var w = 1275 +var h = 2475 +var m = "../map75.png" + +print(`<?xml version="1.0" encoding="UTF-8"?> +<svg + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="${w}" + height="${h}" +> +<image xlink:href="${m}" x="0" y="0" width="${w}" height="${h}" image-rendering="pixelated" sodipodi:insensitive="true" />`) + +for (let id in data.ROADS) { + id = id | 0 + let a = (id / 100) | 0 + let b = id % 100 + let x = (data.TOWNS[a].layout.x + data.TOWNS[b].layout.x) >> 1 + let y = (data.TOWNS[a].layout.y + data.TOWNS[b].layout.y) >> 1 + let label = data.TOWNS[a].name + " / " + data.TOWNS[b].name + print(`<circle inkscape:label="${label}" cx="${x}" cy="${y}" r="12"/>`) +} + +print(`</svg>`) diff --git a/tools/roads.svg b/tools/roads.svg new file mode 100644 index 0000000..0797f57 --- /dev/null +++ b/tools/roads.svg @@ -0,0 +1,693 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="1275" + height="2475" + version="1.1" + id="svg184" + sodipodi:docname="roads.svg" + inkscape:version="1.0.2 (e86c870879, 2021-01-15)"> + <metadata + id="metadata190"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs188" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="640" + inkscape:window-height="480" + id="namedview186" + showgrid="false" + inkscape:snap-object-midpoints="true" + inkscape:zoom="1.2821617" + inkscape:cx="130.6621" + inkscape:cy="2153.467" + inkscape:current-layer="svg184" + inkscape:document-rotation="0"> + <inkscape:grid + type="xygrid" + id="grid192" /> + </sodipodi:namedview> + <image + sodipodi:absref="/home/tor/src/rally/public/crusader-rex/map75.png" + xlink:href="../map75.png" + id="image2" + sodipodi:insensitive="true" + image-rendering="pixelated" + height="2475" + width="1275" + y="0" + x="0" /> + <circle + inkscape:label="Germania / Aleppo" + cx="974.99615" + cy="63.622677" + r="12" + id="circle4" + style="opacity:0.494432" /> + <circle + inkscape:label="Germania / St. Simeon" + cx="296.58557" + cy="99.73616" + r="12" + id="circle6" + style="opacity:0.494432" /> + <circle + inkscape:label="Germania / Antioch" + cx="511.75214" + cy="54.400223" + r="12" + id="circle8" + style="opacity:0.494432" /> + <circle + inkscape:label="Aleppo / Artah" + cx="938.5" + cy="147" + r="12" + id="circle10" + style="opacity:0.49443174" /> + <circle + inkscape:label="Aleppo / Zerdana" + cx="1044.5436" + cy="217.55034" + r="12" + id="circle12" + style="opacity:0.494432" /> + <circle + inkscape:label="Artah / Zerdana" + cx="940.5" + cy="226.5" + r="12" + id="circle14" + style="opacity:0.49443174" /> + <circle + inkscape:label="Artah / Harim" + cx="760.87262" + cy="126.64205" + r="12" + id="circle16" + style="opacity:0.494432" /> + <circle + inkscape:label="Zerdana / Hama" + cx="1028" + cy="388" + r="12" + id="circle18" + style="opacity:0.49443174" /> + <circle + inkscape:label="Zerdana / Albara" + cx="941.80188" + cy="342.2558" + r="12" + id="circle20" + style="opacity:0.494432" /> + <circle + inkscape:label="Hama / Homs" + cx="1132.5" + cy="571" + r="12" + id="circle22" + style="opacity:0.49443174" /> + <circle + inkscape:label="Hama / Albara" + cx="950.42859" + cy="450.0629" + r="12" + id="circle24" + style="opacity:0.494432" /> + <circle + inkscape:label="Hama / Monterrand" + cx="963.85614" + cy="554.27655" + r="12" + id="circle26" + style="opacity:0.494432" /> + <circle + inkscape:label="Homs / Lacum" + cx="979.66449" + cy="779.88367" + r="12" + id="circle28" + style="opacity:0.494432" /> + <circle + inkscape:label="Homs / Qaddas" + cx="1155.5" + cy="780" + r="12" + id="circle30" + style="opacity:0.49443174" /> + <circle + inkscape:label="Homs / Monterrand" + cx="986" + cy="643" + r="12" + id="circle32" + style="opacity:0.49443174" /> + <circle + inkscape:label="Homs / Krak" + cx="926.5" + cy="726" + r="12" + id="circle34" + style="opacity:0.49443174" /> + <circle + inkscape:label="Lacum / Qaddas" + cx="1062.8297" + cy="905.73419" + r="12" + id="circle36" + style="opacity:0.494432" /> + <circle + inkscape:label="Lacum / Baalbek" + cx="888.5" + cy="953" + r="12" + id="circle38" + style="opacity:0.49443174" /> + <circle + inkscape:label="Lacum / Krak" + cx="867.27191" + cy="793.57495" + r="12" + id="circle40" + style="opacity:0.494432" /> + <circle + inkscape:label="Qaddas / Damascus" + cx="1170" + cy="1038" + r="12" + id="circle42" + style="opacity:0.49443174" /> + <circle + inkscape:label="Baalbek / Anjar" + cx="811.5" + cy="1081.5" + r="12" + id="circle44" + style="opacity:0.49443174" /> + <circle + inkscape:label="Baalbek / Tripoli" + cx="775.97717" + cy="953.27899" + r="12" + id="circle46" + style="opacity:0.494432" /> + <circle + inkscape:label="Anjar / Damascus" + cx="868.5" + cy="1177" + r="12" + id="circle48" + style="opacity:0.49443174" /> + <circle + inkscape:label="Anjar / Beirut" + cx="635.32697" + cy="1136.762" + r="12" + id="circle50" + style="opacity:0.494432" /> + <circle + inkscape:label="Anjar / Beaufort" + cx="717.70734" + cy="1197.6344" + r="12" + id="circle52" + style="opacity:0.494432" /> + <circle + inkscape:label="Damascus / Banyas" + cx="936.24713" + cy="1267.1222" + r="12" + id="circle54" + style="opacity:0.494432" /> + <circle + inkscape:label="Damascus / Ashtera" + cx="1050.5" + cy="1339" + r="12" + id="circle56" + style="opacity:0.49443174" /> + <circle + inkscape:label="Banyas / Ashtera" + cx="907.11737" + cy="1411.2574" + r="12" + id="circle58" + style="opacity:0.494432" /> + <circle + inkscape:label="Banyas / Beaufort" + cx="666.4845" + cy="1389.4496" + r="12" + id="circle60" + style="opacity:0.494432" /> + <circle + inkscape:label="Banyas / Tiberias" + cx="729.61981" + cy="1455.0599" + r="12" + id="circle62" + style="opacity:0.49443174" /> + <circle + inkscape:label="Ashtera / Ajlun" + cx="1046.3632" + cy="1490.8855" + r="12" + id="circle64" + style="opacity:0.494432" /> + <circle + inkscape:label="Ajlun / Tiberias" + cx="910.80432" + cy="1553.918" + r="12" + id="circle66" + style="opacity:0.494432" /> + <circle + inkscape:label="Ajlun / Amman" + cx="1034.8445" + cy="1711.1224" + r="12" + id="circle68" + style="opacity:0.494432" /> + <circle + inkscape:label="St. Simeon / Antioch" + cx="412" + cy="208" + r="12" + id="circle70" + style="opacity:0.49443174" /> + <circle + inkscape:label="Antioch / Harim" + cx="600" + cy="127.38018" + r="12" + id="circle72" + style="opacity:0.49443174" /> + <circle + inkscape:label="Antioch / Kassab" + cx="477.3251" + cy="259.52649" + r="12" + id="circle74" + style="opacity:0.494432" /> + <circle + inkscape:label="Harim / Shughur" + cx="684.38556" + cy="214.05885" + r="12" + id="circle76" + style="opacity:0.494432" /> + <circle + inkscape:label="Kassab / Latakia" + cx="405.5" + cy="387.5" + r="12" + id="circle78" + style="opacity:0.49443174" /> + <circle + inkscape:label="Shughur / Saône" + cx="635.02734" + cy="373.70758" + r="12" + id="circle80" + style="opacity:0.494432" /> + <circle + inkscape:label="Shughur / Albara" + cx="704.05487" + cy="344.73975" + r="12" + id="circle82" + style="opacity:0.494432" /> + <circle + inkscape:label="Latakia / Saône" + cx="523.5" + cy="432" + r="12" + id="circle84" + style="opacity:0.49443174" /> + <circle + inkscape:label="Latakia / Margat" + cx="491.5" + cy="487.5" + r="12" + id="circle86" + style="opacity:0.49443174" /> + <circle + inkscape:label="Saône / Albara" + cx="735" + cy="420.5" + r="12" + id="circle88" + style="opacity:0.49443174" /> + <circle + inkscape:label="Margat / Tartus" + cx="546.5" + cy="653.5" + r="12" + id="circle90" + style="opacity:0.49443174" /> + <circle + inkscape:label="Monterrand / Krak" + cx="850.96875" + cy="664.35345" + r="12" + id="circle92" + style="opacity:0.494432" /> + <circle + inkscape:label="Tartus / Krak" + cx="689" + cy="722" + r="12" + id="circle94" + style="opacity:0.49443174" /> + <circle + inkscape:label="Tartus / Tripoli" + cx="669" + cy="785.5" + r="12" + id="circle96" + style="opacity:0.49443174" /> + <circle + inkscape:label="Krak / Tripoli" + cx="756.5" + cy="806.5" + r="12" + id="circle98" + style="opacity:0.49443174" /> + <circle + inkscape:label="Tripoli / Botron" + cx="571.41937" + cy="932.94006" + r="12" + id="circle100" + style="opacity:0.49443174" /> + <circle + inkscape:label="Botron / Beirut" + cx="566.5" + cy="1052" + r="12" + id="circle102" + style="opacity:0.49443174" /> + <circle + inkscape:label="Beirut / Sidon" + cx="503" + cy="1219" + r="12" + id="circle104" + style="opacity:0.49443174" /> + <circle + inkscape:label="Sidon / Tyre" + cx="460" + cy="1329.5" + r="12" + id="circle106" + style="opacity:0.49443174" /> + <circle + inkscape:label="Sidon / Beaufort" + cx="542" + cy="1323.5" + r="12" + id="circle108" + style="opacity:0.49443174" /> + <circle + inkscape:label="Tyre / Beaufort" + cx="546" + cy="1388" + r="12" + id="circle110" + style="opacity:0.49443174" /> + <circle + inkscape:label="Tyre / Acre" + cx="427" + cy="1492" + r="12" + id="circle112" + style="opacity:0.49443174" /> + <circle + inkscape:label="Acre / Tiberias" + cx="600.5" + cy="1571.5" + r="12" + id="circle114" + style="opacity:0.49443174" /> + <circle + inkscape:label="Acre / Legio" + cx="518" + cy="1652.5" + r="12" + id="circle116" + style="opacity:0.49443174" /> + <circle + inkscape:label="Acre / Caesarea" + cx="389.65146" + cy="1657.533" + r="12" + id="circle118" + style="opacity:0.494432" /> + <circle + inkscape:label="Tiberias / Baisan" + cx="758.84827" + cy="1631.7234" + r="12" + id="circle120" + style="opacity:0.494432" /> + <circle + inkscape:label="Legio / Baisan" + cx="653.27991" + cy="1684.589" + r="12" + id="circle122" + style="opacity:0.494432" /> + <circle + inkscape:label="Legio / Nablus" + cx="619" + cy="1723.5" + r="12" + id="circle124" + style="opacity:0.49443174" /> + <circle + inkscape:label="Baisan / Nablus" + cx="675" + cy="1736" + r="12" + id="circle126" + style="opacity:0.49443174" /> + <circle + inkscape:label="Baisan / Damiya" + cx="810.5" + cy="1723" + r="12" + id="circle128" + style="opacity:0.49443174" /> + <circle + inkscape:label="Caesarea / Nablus" + cx="519.5" + cy="1776.5" + r="12" + id="circle130" + style="opacity:0.49443174" /> + <circle + inkscape:label="Caesarea / Jaffa" + cx="409" + cy="1836.5" + r="12" + id="circle132" + style="opacity:0.49443174" /> + <circle + inkscape:label="Nablus / Damiya" + cx="745" + cy="1797" + r="12" + id="circle134" + style="opacity:0.49443174" /> + <circle + inkscape:label="Nablus / Jerusalem" + cx="653" + cy="1883" + r="12" + id="circle136" + style="opacity:0.49443174" /> + <circle + inkscape:label="Damiya / Amman" + cx="934.28229" + cy="1781.4043" + r="12" + id="circle138" + style="opacity:0.494432" /> + <circle + inkscape:label="Damiya / Jericho" + cx="845.08759" + cy="1883.2872" + r="12" + id="circle140" + style="opacity:0.494432" /> + <circle + inkscape:label="Amman / Jericho" + cx="958.41956" + cy="1891.8888" + r="12" + id="circle142" + style="opacity:0.494432" /> + <circle + inkscape:label="Amman / Kerak" + cx="1129.8142" + cy="1972.9695" + r="12" + id="circle144" + style="opacity:0.494432" /> + <circle + inkscape:label="Jaffa / Ramallah" + cx="463" + cy="1932" + r="12" + id="circle146" + style="opacity:0.49443174" /> + <circle + inkscape:label="Jaffa / Ascalon" + cx="390.88397" + cy="2003.7799" + r="12" + id="circle148" + style="opacity:0.494432" /> + <circle + inkscape:label="Ramallah / Jerusalem" + cx="586" + cy="1971.5" + r="12" + id="circle150" + style="opacity:0.49443174" /> + <circle + inkscape:label="Ramallah / Ascalon" + cx="448" + cy="2014" + r="12" + id="circle152" + style="opacity:0.49443174" /> + <circle + inkscape:label="Jerusalem / Jericho" + cx="789.3551" + cy="1954.4401" + r="12" + id="circle154" + style="opacity:0.49443174" /> + <circle + inkscape:label="Jerusalem / Hebron" + cx="677.30878" + cy="2050.4141" + r="12" + id="circle156" + style="opacity:0.494432" /> + <circle + inkscape:label="Jericho / Kerak" + cx="951.5" + cy="1975.5" + r="12" + id="circle158" + style="opacity:0.49443174" /> + <circle + inkscape:label="Ascalon / Lachish" + cx="439.29956" + cy="2128.4792" + r="12" + id="circle160" + style="opacity:0.49443174" /> + <circle + inkscape:label="Ascalon / Gaza" + cx="330.43219" + cy="2133.1641" + r="12" + id="circle162" + style="opacity:0.494432" /> + <circle + inkscape:label="Lachish / Hebron" + cx="556.32245" + cy="2150.7788" + r="12" + id="circle164" + style="opacity:0.49443174" /> + <circle + inkscape:label="Lachish / Gaza" + cx="429.73749" + cy="2172.2603" + r="12" + id="circle166" + style="opacity:0.49443174" /> + <circle + inkscape:label="Hebron / Dimona" + cx="658.28107" + cy="2241.6177" + r="12" + id="circle168" + style="opacity:0.49443174" /> + <circle + inkscape:label="Hebron / Zoar" + cx="779.01019" + cy="2195.3918" + r="12" + id="circle170" + style="opacity:0.494432" /> + <circle + inkscape:label="Kerak / Zoar" + cx="998.22845" + cy="2139.8015" + r="12" + id="circle172" + style="opacity:0.494432" /> + <circle + inkscape:label="Gaza / Beersheba" + cx="369" + cy="2265" + r="12" + id="circle174" + style="opacity:0.49443174" /> + <circle + inkscape:label="Gaza / Egypt" + cx="225.37711" + cy="2266.1133" + r="12" + id="circle176" + style="opacity:0.494432" /> + <circle + inkscape:label="Beersheba / Dimona" + cx="538.5" + cy="2293" + r="12" + id="circle178" + style="opacity:0.49443174" /> + <circle + inkscape:label="Beersheba / Egypt" + cx="414.74786" + cy="2311.9475" + r="12" + id="circle180" + style="opacity:0.494432" /> + <circle + inkscape:label="Dimona / Zoar" + cx="788" + cy="2290.5" + r="12" + id="circle182" + style="opacity:0.49443174" /> +</svg> |