diff options
-rw-r--r-- | play.css | 436 | ||||
-rw-r--r-- | play.html | 115 | ||||
-rw-r--r-- | play.js | 1247 | ||||
-rw-r--r-- | rules.js | 75 | ||||
-rw-r--r-- | tools/layout.svg | 378 | ||||
-rw-r--r-- | tools/parse-layout.js | 187 |
6 files changed, 2435 insertions, 3 deletions
diff --git a/play.css b/play.css new file mode 100644 index 0000000..f33c156 --- /dev/null +++ b/play.css @@ -0,0 +1,436 @@ +main { background-color: #777; } + +#role_DS { background-color: silver } +#role_BK { background-color: turquoise } +#role_VK { background-color: gold } +#role_Solo { background-image: linear-gradient(120deg, gray, turquoise, gold) } + +.role.active span { text-decoration: underline; } + +/* LAYOUT */ + +#card_tip { + position: fixed; + z-index: 100; + right: 240px; + top: 60px; +} + +#card_panel { + display: flex; + flex-wrap: wrap; + align-content: start; + justify-content: center; + gap: 16px; + padding: 16px; +} + +@media (max-width: 800px) { + #card_panel { + justify-content: start; + } +} + +#table { + display: grid; + width: 100%; + grid-template-columns: 1fr min-content min-content 1fr; + grid-template-rows: min-content max-content; +} + +#mapwrap { + grid-column: 2; + grid-row: 1; +} + +#card_panel { + grid-column: 2; + grid-row: 2; +} + +#mapwrap[data-fit="both"] + #card_panel { + max-width: 250px; + grid-column: 3; + grid-row: 1; +} + +@media (min-width: 2000px) { + #card_panel { + max-width: 250px; + grid-column: 3; + grid-row: 1; + } +} + +/* LOG */ + +#log .h1 { background-image: linear-gradient(60deg, gray, turquoise, gold); text-shadow: 0 0 4px white; } + +#log { font-variant-numeric: tabular-nums; } + +/* MAP */ + +#mapwrap { + box-shadow: 0px 1px 10px #0008; + width: 1275px; + height: 1650px; + margin-bottom: 24px; +} + +#mapwrap[data-fit="width"], #mapwrap[data-fit="both"] { + margin-bottom: 0; +} + +#map { + width: 1275px; + height: 1650px; + background-repeat: no-repeat; + background-size: cover; +} + +#map { background-image: url("map75.jpg") } +@media (min-resolution: 97dpi) { + #map { background-image: url("map150.jpg") } +} + +#svgmap { position: absolute; } +#tokens { position: absolute; } +#pieces { position: absolute; } +#boxes { position: absolute; } + +#map.hide_pieces #pieces { display: none; } +#map.hide_tokens #tokens { display: none; } + +/* SPACES */ + +path { fill: transparent; stroke-width: 4; } +path.action { fill: white; fill-opacity: 0.3; stroke: white; } +path.selected { stroke: yellow; } +path.tip { stroke: white; stroke-dasharray: 4 4; } + +.space { + position: absolute; + border: 2px solid lime; +} + +.space.province { + border-radius: 50%; +} + +.box{position:absolute;box-sizing:border-box;border:4px solid transparent} +.box.action{border-color:lemonchiffon;} +.box.selected{border-color:yellow;} +.box.tip { border-color: white; } + +/* PIECES */ + +.piece { + position: absolute; + pointer-events: none; + background-position: center; + background-repeat: no-repeat; + background-size: contain; + transition-property: top, left; + transition-duration: 700ms; + transition-timing-function: ease; + filter: drop-shadow(0 1px 1px #0006); +} +.piece.action { + pointer-events: auto; + filter: + drop-shadow(0 -2px 0 white) + drop-shadow(0 2px 0 white) + drop-shadow(-2px 0 0 white) + drop-shadow(2px 0 0 white) + ; +} +.piece.selected { + pointer-events: auto; + filter: + drop-shadow(0 -2px 0 yellow) + drop-shadow(0 2px 0 yellow) + drop-shadow(-2px 0 0 yellow) + drop-shadow(2px 0 0 yellow) + ; +} + +.cylinder { width: 44px; height: 48px; } +.disk { width: 44px; height: 38px; } +.governor, .amir, .raja { width: 29px; height: 36px; } +.cube { width: 29px; height: 36px; } + +.ds.cylinder { background-image: url(pieces/ds_cylinder.svg) } +.ds.cube { background-image: url(pieces/ds_troop.svg) } +.ds.disk { background-image: url(pieces/ds_disk.svg) } +.ds.governor { background-image: url(pieces/ds_governor.svg) } +.bk.cylinder { background-image: url(pieces/bk_cylinder.svg) } +.bk.amir { background-image: url(pieces/bk_amir.svg) } +.bk.amir.rebel { background-image: url(pieces/bk_amir_rebel.svg) } +.bk.disk { background-image: url(pieces/bk_disk.svg) } +.ve.cylinder { background-image: url(pieces/ve_cylinder.svg) } +.ve.raja { background-image: url(pieces/ve_raja.svg) } +.ve.raja.rebel { background-image: url(pieces/ve_raja_rebel.svg) } +.ve.disk { background-image: url(pieces/ve_disk.svg) } +.mongol.cube { background-image: url(pieces/mongol_invader.svg) } + +.disk { border-radius: 15px; } +.noble { border-radius: 8px; } +.cube { border-radius: 14px 14px / 10px 10px; } + +/* TOKENS */ + +.token { + pointer-events: none; + position: absolute; + transition-property: top, left; + transition-duration: 700ms; + transition-timing-function: ease; + background-repeat: no-repeat; + border-radius: 8px; + border: 2px outset gray; + box-shadow: 0 0 0px 1px #222; + background-size: 45px 45px; + width: 45px; + height: 45px; +} + +.token.action { + pointer-events: auto; +} + +.token.tributary, .token.ds_ctl, .token.bk_ctl, .token.ve_ctl { border-radius: 50% } + +/* CARDS */ + +.card { + width: 250px; + height: 350px; + border-radius: 16px; + background-color: #c6bb8d; + background-size: cover; + background-repeat: no-repeat; + box-shadow: 0 0 0 1px #223f21, 1px 1px 4px #0008; +} + +.card.card_back { + background-color: orange; +} + +.card.action { + box-shadow: 0 0 0 3px white; +} + +.card.momentum { + width: 186px; + height: 261px; + border-radius: 12px; +} + +#deck_outer { position: relative; } +#deck_size { + position: absolute; + right: 24px; + bottom: 16px; + font-size: 24px; + font-weight: bold; + color: white; +} + +#of_gods_and_kings { position: relative; } +#of_gods_and_kings_label { + position: absolute; + top: 6px; + left: 6px; + right: 6px; + border-radius: 16px; + text-align: center; + background-color: #5c4f23; + font-size: 16px; + font-weight: bold; + color: white; +} + +#this_card { position: relative; } + +#this_card.c #shaded_event { border-top-color: transparent; } +#this_card.c #unshaded_event { border-image: radial-gradient(100px 30px at bottom, transparent 65%, white) 3 } + +#unshaded_event, #shaded_event { + display: none; + position: absolute; + box-sizing: border-box; + border: 3px solid white; +} + +#shaded_event.action, #unshaded_event.action { + display: block; +} + +#unshaded_event { + left: 3px; + right: 3px; + top: 200px; + height: 70px; + border-radius: 12px; +} + +#shaded_event { + left: 3px; + right: 3px; + bottom: 4px; + height: 80px; + border-radius: 12px; +} + +/* TOKEN IMAGES */ + +.token.tributary { background-color: #2a2c26 } +.token.ds_ctl { background-color: #433e1d } +.token.bk_ctl { background-color: #15908a } +.token.ve_ctl { background-color: #fc7b0d } +#token_ds_vp { background-color: #2a2c26 } +#token_bk_vp { background-color: #15908a } +#token_ve_vp { background-color: #fc7b0d } +#token_bk_influence { background-color: #02766f } +#token_ve_influence { background-color: #fdbb47 } +#token_mongol_cavalry { background-color: #58291f } +.token.cavalry.charge { background-color: #6c4a2f } +.token.cavalry.screen { background-color: #363d29 } + +.token.tributary { border-color: #2a2c26 } +.token.ds_ctl { border-color: #433e1d } +.token.bk_ctl { border-color: #15908a } +.token.ve_ctl { border-color: #fc7b0d } +#token_ds_vp { border-color: #2a2c26 } +#token_bk_vp { border-color: #15908a } +#token_ve_vp { border-color: #fc7b0d } +#token_bk_influence { border-color: #02766f } +#token_ve_influence { border-color: #fdbb47 } +#token_mongol_cavalry { border-color: #58291f } +.token.cavalry.charge { border-color: #6c4a2f } +.token.cavalry.screen { border-color: #363d29 } + +.token.tributary { background-image: url(pieces/Tributary.png) } +.token.ds_ctl { background-image: url(pieces/Flag_DS.png) } +.token.bk_ctl { background-image: url(pieces/Flag_BK.png) } +.token.ve_ctl { background-image: url(pieces/Flag_VE.png) } +#token_ds_vp { background-image: url(pieces/Victory_DS.png) } +#token_bk_vp { background-image: url(pieces/Victory_BK.png) } +#token_ve_vp { background-image: url(pieces/Victory_VE.png) } +#token_bk_influence { background-image: url(pieces/Influence_BK.png) } +#token_ve_influence { background-image: url(pieces/Influence_VE.png) } +#token_mongol_cavalry { background-image: url(pieces/Cavalry_Mongol.png) } +.token.cavalry.charge { background-image: url(pieces/Cavalry_Charge.png) } +.token.cavalry.screen { background-image: url(pieces/Cavalry_Screen.png) } + +/* CARD IMAGES */ + +.card.card_back{background-image:url(cards100/card_back.jpg)} +.card.card_1{background-image:url(cards100/event_1.jpg)} +.card.card_2{background-image:url(cards100/event_2.jpg)} +.card.card_3{background-image:url(cards100/event_3.jpg)} +.card.card_4{background-image:url(cards100/event_4.jpg)} +.card.card_5{background-image:url(cards100/event_5.jpg)} +.card.card_6{background-image:url(cards100/event_6.jpg)} +.card.card_7{background-image:url(cards100/event_7.jpg)} +.card.card_8{background-image:url(cards100/event_8.jpg)} +.card.card_9{background-image:url(cards100/event_9.jpg)} +.card.card_10{background-image:url(cards100/event_10.jpg)} +.card.card_11{background-image:url(cards100/event_11.jpg)} +.card.card_12{background-image:url(cards100/event_12.jpg)} +.card.card_13{background-image:url(cards100/event_13.jpg)} +.card.card_14{background-image:url(cards100/event_14.jpg)} +.card.card_15{background-image:url(cards100/event_15.jpg)} +.card.card_16{background-image:url(cards100/event_16.jpg)} +.card.card_17{background-image:url(cards100/event_17.jpg)} +.card.card_18{background-image:url(cards100/event_18.jpg)} +.card.card_19{background-image:url(cards100/event_19.jpg)} +.card.card_20{background-image:url(cards100/event_20.jpg)} +.card.card_21{background-image:url(cards100/event_21.jpg)} +.card.card_22{background-image:url(cards100/event_22.jpg)} +.card.card_23{background-image:url(cards100/event_23.jpg)} +.card.card_24{background-image:url(cards100/event_24.jpg)} +.card.card_25{background-image:url(cards100/event_25.jpg)} +.card.card_26{background-image:url(cards100/event_26.jpg)} +.card.card_27{background-image:url(cards100/event_27.jpg)} +.card.card_28{background-image:url(cards100/event_28.jpg)} +.card.card_29{background-image:url(cards100/event_29.jpg)} +.card.card_30{background-image:url(cards100/event_30.jpg)} +.card.card_31{background-image:url(cards100/event_31.jpg)} +.card.card_32{background-image:url(cards100/event_32.jpg)} +.card.card_33{background-image:url(cards100/event_33.jpg)} +.card.card_34{background-image:url(cards100/event_34.jpg)} +.card.card_35{background-image:url(cards100/event_35.jpg)} +.card.card_36{background-image:url(cards100/event_36.jpg)} +.card.card_37{background-image:url(cards100/Mongol_Invasion_BK_front.jpg)} +.card.card_38{background-image:url(cards100/Mongol_Invasion_BK_front.jpg)} +.card.card_39{background-image:url(cards100/Mongol_Invasion_BK_front.jpg)} +.card.card_40{background-image:url(cards100/Mongol_Invasion_BK_front.jpg)} +.card.card_41{background-image:url(cards100/Mongol_Invasion_BK_front.jpg)} +.card.card_42{background-image:url(cards100/Mongol_Invasion_VE_front.jpg)} +.card.card_43{background-image:url(cards100/Mongol_Invasion_VE_front.jpg)} +.card.card_44{background-image:url(cards100/Mongol_Invasion_VE_front.jpg)} +.card.card_45{background-image:url(cards100/Mongol_Invasion_VE_front.jpg)} +.card.card_46{background-image:url(cards100/Timurid_Empire_front.jpg)} +.card.card_47{background-image:url(cards100/Timurid_Empire_front.jpg)} +.card.card_48{background-image:url(cards100/Succession1_front.jpg)} +.card.card_49{background-image:url(cards100/Succession2_front.jpg)} +.card.card_50{background-image:url(cards100/Succession3_front.jpg)} +.card.card_dynasty_khalji{background-image:url(cards100/Dynasty_front.jpg)} +.card.card_dynasty_khalji{background-image:url(cards100/Dynasty_back.jpg)} + +@media (min-resolution: 97dpi) { +.card.card_back{background-image:url(cards200/card_back.jpg)} +.card.card_1{background-image:url(cards200/event_1.jpg)} +.card.card_2{background-image:url(cards200/event_2.jpg)} +.card.card_3{background-image:url(cards200/event_3.jpg)} +.card.card_4{background-image:url(cards200/event_4.jpg)} +.card.card_5{background-image:url(cards200/event_5.jpg)} +.card.card_6{background-image:url(cards200/event_6.jpg)} +.card.card_7{background-image:url(cards200/event_7.jpg)} +.card.card_8{background-image:url(cards200/event_8.jpg)} +.card.card_9{background-image:url(cards200/event_9.jpg)} +.card.card_10{background-image:url(cards200/event_10.jpg)} +.card.card_11{background-image:url(cards200/event_11.jpg)} +.card.card_12{background-image:url(cards200/event_12.jpg)} +.card.card_13{background-image:url(cards200/event_13.jpg)} +.card.card_14{background-image:url(cards200/event_14.jpg)} +.card.card_15{background-image:url(cards200/event_15.jpg)} +.card.card_16{background-image:url(cards200/event_16.jpg)} +.card.card_17{background-image:url(cards200/event_17.jpg)} +.card.card_18{background-image:url(cards200/event_18.jpg)} +.card.card_19{background-image:url(cards200/event_19.jpg)} +.card.card_20{background-image:url(cards200/event_20.jpg)} +.card.card_21{background-image:url(cards200/event_21.jpg)} +.card.card_22{background-image:url(cards200/event_22.jpg)} +.card.card_23{background-image:url(cards200/event_23.jpg)} +.card.card_24{background-image:url(cards200/event_24.jpg)} +.card.card_25{background-image:url(cards200/event_25.jpg)} +.card.card_26{background-image:url(cards200/event_26.jpg)} +.card.card_27{background-image:url(cards200/event_27.jpg)} +.card.card_28{background-image:url(cards200/event_28.jpg)} +.card.card_29{background-image:url(cards200/event_29.jpg)} +.card.card_30{background-image:url(cards200/event_30.jpg)} +.card.card_31{background-image:url(cards200/event_31.jpg)} +.card.card_32{background-image:url(cards200/event_32.jpg)} +.card.card_33{background-image:url(cards200/event_33.jpg)} +.card.card_34{background-image:url(cards200/event_34.jpg)} +.card.card_35{background-image:url(cards200/event_35.jpg)} +.card.card_36{background-image:url(cards200/event_36.jpg)} +.card.card_37{background-image:url(cards200/Mongol_Invasion_BK_front.jpg)} +.card.card_38{background-image:url(cards200/Mongol_Invasion_BK_front.jpg)} +.card.card_39{background-image:url(cards200/Mongol_Invasion_BK_front.jpg)} +.card.card_40{background-image:url(cards200/Mongol_Invasion_BK_front.jpg)} +.card.card_41{background-image:url(cards200/Mongol_Invasion_BK_front.jpg)} +.card.card_42{background-image:url(cards200/Mongol_Invasion_VE_front.jpg)} +.card.card_43{background-image:url(cards200/Mongol_Invasion_VE_front.jpg)} +.card.card_44{background-image:url(cards200/Mongol_Invasion_VE_front.jpg)} +.card.card_45{background-image:url(cards200/Mongol_Invasion_VE_front.jpg)} +.card.card_46{background-image:url(cards200/Timurid_Empire_front.jpg)} +.card.card_47{background-image:url(cards200/Timurid_Empire_front.jpg)} +.card.card_48{background-image:url(cards200/Succession1_front.jpg)} +.card.card_49{background-image:url(cards200/Succession2_front.jpg)} +.card.card_50{background-image:url(cards200/Succession3_front.jpg)} +.card.card_dynasty_khalji{background-image:url(cards200/Dynasty_front.jpg)} +.card.card_dynasty_khalji{background-image:url(cards200/Dynasty_back.jpg)} +} diff --git a/play.html b/play.html new file mode 100644 index 0000000..f0b502b --- /dev/null +++ b/play.html @@ -0,0 +1,115 @@ +<!DOCTYPE html> +<!-- vim:set nowrap: --> +<html lang="en"> +<head> +<meta name="viewport" content="width=device-width, user-scalable=no, interactive-widget=resizes-content, viewport-fit=cover"> +<meta name="theme-color" content="#444"> +<meta charset="UTF-8"> +<title>VIJAYANAGARA</title> +<link id="favicon" rel="icon" href="images/Flags_VE.png"> +<link rel="stylesheet" href="/fonts/fonts.css"> +<link rel="stylesheet" href="/common/client.css"> +<link rel="stylesheet" href="play.css"> +<script defer src="/common/client.js"></script> +<script defer src="data.js"></script> +<script defer src="play.js"></script> +</head> +<body> + +<div id="card_tip" class="hide"></div> + +<header> + <div id="toolbar"> + <details> + <summary> + <img src="/images/cog.svg"> + </summary> + <menu> + <li><a href="info/learn-to-play.html" target="_blank")">Learn to Play</a> + <li><a href="info/rulebook.html" target="_blank")">Rulebook</a> + <li><a href="info/reference.html" target="_blank")">Referenece</a> + <li><a href="info/guides.html" target="_blank")">Strategy</a> + <li><a href="info/cards.html" target="_blank")">Cards</a> + </menu> + </details> + <details id="negotiate_menu"> + <summary> + <img src="images/shaking-hands.svg"> + </summary> + <menu> + <li id="transfer_resources_menu" onclick="send_action('transfer_resources')">Transfer Resources + <li id="transfer_cavalry_menu" onclick="send_action('transfer_cavalry')">Transfer Cavalry + <li id="ask_resources_menu" onclick="send_action('ask_resources')">Ask for Resources + <li id="ask_cavalry_menu" onclick="send_action('ask_cavalry')">Ask for Cavalry + </menu> + </details> + <button onclick="toggle_pieces()"><img src="/images/earth-asia-oceania.svg"></button> + </div> +</header> + +<aside> + <div id="roles"></div> + <div id="log"></div> +</aside> + +<main data-max-zoom="1" data-map-width="1275" data-map-height="1650"> + +<div id="table"> + +<div id="mapwrap"> + <div id="map"> + <svg id="svgmap" width="1650" height="2550" viewBox="0 0 1650 2550"> + <!-- TODO: region paths --> + </svg> + + <div id="spaces"> + <div id="mongol_invaders" class="box" style="top:1266px;left:684px;width:141px;height:140px"></div> + <div id="available_ds" class="box" style="top:1266px;left:684px;width:141px;height:140px"></div> + <div id="available_bk" class="box" style="top:1266px;left:684px;width:141px;height:140px"></div> + <div id="available_ve" class="box" style="top:1266px;left:684px;width:141px;height:140px"></div> + </div> + + <div id="tokens"> + <div class="token" id="token_ds_vp" style="left:1204px;top:20px"></div> + <div class="token" id="token_bk_vp" style="left:23px;top:8px"></div> + <div class="token" id="token_ve_vp" style="left:23px;top:36px"></div> + + <div class="token" id="token_bk_influence" style="left:25px;top:1195px"></div> + <div class="token" id="token_ve_influence" style="left:25px;top:1300px"></div> + <div class="token" id="token_mongol_cavalry" style="left:89px;top:220px"></div> + <div class="token cavalry charge" id="cavalry_1" style="left:700px;top:100px"></div> + <div class="token cavalry screen" id="cavalry_2" style="left:700px;top:110px"></div> + <div class="token cavalry charge" id="cavalry_3" style="left:700px;top:120px"></div> + <div class="token cavalry screen" id="cavalry_4" style="left:700px;top:130px"></div> + <div class="token cavalry charge" id="cavalry_5" style="left:700px;top:140px"></div> + <div class="token cavalry screen" id="cavalry_6" style="left:700px;top:150px"></div> + <div class="token cavalry charge" id="cavalry_7" style="left:700px;top:160px"></div> + <div class="token cavalry screen" id="cavalry_8" style="left:700px;top:170px"></div> + + </div> + + <div id="pieces"> + <div id="ds_cylinder" class="piece ds cylinder" style="left:875px;top:1480px;z-index:3"></div> + <div id="bk_cylinder" class="piece bk cylinder" style="left:875px;top:1440px;z-index:2"></div> + <div id="ve_cylinder" class="piece ve cylinder" style="left:875px;top:1400px;z-index:1"></div> + <div id="ds_resources" class="piece ds cylinder" style="left:812px;top:20px;z-index:1"></div> + <div id="bk_resources" class="piece bk cylinder" style="left:419px;top:20px;z-index:1"></div> + <div id="ve_resources" class="piece ve cylinder" style="left:484px;top:20px;z-index:1"></div> + </div> + </div> +</div> + +<div id="card_panel"> + <div id="this_card" class="card card_1"><div id="unshaded_event"></div><div id="shaded_event"></div></div> + <div id="deck_outer" class="card card_back"><div id="deck_size">35</div></div> + <div id="dynasty_card" class="card card_dynasty_khalji"></div> + <div id="of_gods_and_kings" class="card card_26"><div id="of_gods_and_kings_label">~ Of Gods and Kings ~</div></div> +</div> + +</div> + +</main> + +<footer id="status"></footer> + +</body> @@ -0,0 +1,1247 @@ +"use strict" + +/* + global view, data, player, send_action, action_button, scroll_with_middle_mouse +*/ + +/* COMMON */ + +function set_has(set, item) { + let a = 0 + let b = set.length - 1 + while (a <= b) { + let m = (a + b) >> 1 + let x = set[m] + if (item < x) + b = m - 1 + else if (item > x) + a = m + 1 + else + return true + } + return false +} + +function 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 +} + +/* DATA */ + +// Factions +const DS = 0 +const BK = 1 +const VE = 2 + +const NAME_DS = "DS" +const NAME_BK = "BK" +const NAME_VE = "VE" + +// Sequence of Play options +const ELIGIBLE = 0 +const SOP_LIMITED_COMMAND = 1 +const SOP_COMMAND_DECREE = 2 +const SOP_EVENT_OR_COMMAND = 3 +const SOP_PASS = 4 +const INELIGIBLE = 5 + +// Spaces +const S_ANDHRA = 0 +const S_BENGAL = 1 +const S_GONDWANA = 2 +const S_GUJARAT = 3 +const S_JAUNPUR = 4 +const S_KARNATAKA = 5 +const S_MADHYADESH = 6 +const S_MAHARASHTRA = 7 +const S_MALWA = 8 +const S_ORISSA = 9 +const S_RAJPUT_KINGDOMS = 10 +const S_SINDH = 11 +const S_TAMILAKAM = 12 +const S_DELHI = 13 +const S_MOUNTAIN_PASSES = 14 +const S_PUNJAB = 15 +const S_MONGOL_INVADERS = 16 +const S_DS_AVAILABLE = 17 +const S_BK_AVAILABLE = 18 +const S_VE_AVAILABLE = 19 +const S_BK_INF_2 = 20 +const S_BK_INF_4 = 21 +const S_VE_INF_1 = 22 +const S_VE_INF_2 = 23 +const S_VE_INF_3 = 24 +const S_VE_INF_4 = 25 + +const space_name = [ + "Andhra", + "Bengal", + "Gondwana", + "Gujarat", + "Jaunpur", + "Karnataka", + "Madhyadesh", + "Maharashtra", + "Malwa", + "Orissa", + "Rajput Kingdoms", + "Sindh", + "Tamilakam", + "Delhi", + "Mountain Passes", + "Punjab", + "Mongol Invaders", + "DS Available", + "BK Available", + "VE Available", +] + +/* LAYOUT DATA */ +// :r !node tools/parse-layout.js +const layout = { + "circles": { + "provinces": { + "Sindh": { + "cx": 65, + "cy": 393, + "rx": 34, + "ry": 34 + }, + "Rajput Kingdoms": { + "cx": 329, + "cy": 403, + "rx": 34, + "ry": 34 + }, + "Malwa": { + "cx": 605, + "cy": 572, + "rx": 34, + "ry": 34 + }, + "Jaunpur": { + "cx": 894, + "cy": 455, + "rx": 34, + "ry": 34 + }, + "Bengal": { + "cx": 1192, + "cy": 536, + "rx": 34, + "ry": 34 + }, + "Orissa": { + "cx": 1034, + "cy": 858, + "rx": 34, + "ry": 34 + }, + "Gondwana": { + "cx": 913, + "cy": 737, + "rx": 34, + "ry": 34 + }, + "Madhyadesh": { + "cx": 670, + "cy": 817, + "rx": 34, + "ry": 34 + }, + "Andhra": { + "cx": 743, + "cy": 1090, + "rx": 34, + "ry": 34 + }, + "Maharashtra": { + "cx": 438, + "cy": 969, + "rx": 34, + "ry": 34 + }, + "Gujarat": { + "cx": 220, + "cy": 678, + "rx": 34, + "ry": 34 + }, + "Karnataka": { + "cx": 550, + "cy": 1278, + "rx": 34, + "ry": 34 + }, + "Tamilakam": { + "cx": 704, + "cy": 1399, + "rx": 34, + "ry": 34 + } + }, + "mongol_invasion_regions": { + "Mountain Passes": { + "cx": 302, + "cy": 140, + "rx": 83, + "ry": 28 + }, + "Punjab": { + "cx": 478, + "cy": 220, + "rx": 58, + "ry": 19 + }, + "Delhi": { + "cx": 647, + "cy": 375, + "rx": 148, + "ry": 148 + } + } + }, + "rects": { + "available_boxes": { + "Mongol Invaders": { + "x": 24, + "y": 100, + "w": 177, + "h": 110 + }, + "DS Available": { + "x": 796, + "y": 91, + "w": 388, + "h": 223 + }, + "BK Available": { + "x": 21, + "y": 908, + "w": 238, + "h": 224 + }, + "VE Available": { + "x": 21, + "y": 1405, + "w": 239, + "h": 225 + } + }, + "tracks": { + "Track 24": { + "x": 1198, + "y": 403, + "w": 61, + "h": 61 + }, + "Track 18": { + "x": 1197, + "y": 14, + "w": 62, + "h": 63 + }, + "Track 0": { + "x": 15, + "y": 14, + "w": 62, + "h": 63 + }, + "BK Influence 0": { + "x": 18, + "y": 1186, + "w": 61, + "h": 63 + }, + "BK Influence 1": { + "x": 89, + "y": 1186, + "w": 61, + "h": 63 + }, + "BK Influence 2": { + "x": 160, + "y": 1186, + "w": 61, + "h": 63 + }, + "BK Influence 3": { + "x": 231, + "y": 1186, + "w": 61, + "h": 63 + }, + "BK Influence 4": { + "x": 302, + "y": 1186, + "w": 61, + "h": 63 + }, + "VE Influence 0": { + "x": 18, + "y": 1292, + "w": 61, + "h": 63 + }, + "VE Influence 1": { + "x": 89, + "y": 1292, + "w": 61, + "h": 63 + }, + "VE Influence 2": { + "x": 160, + "y": 1292, + "w": 61, + "h": 63 + }, + "VE Influence 3": { + "x": 231, + "y": 1291, + "w": 61, + "h": 63 + }, + "VE Influence 4": { + "x": 302, + "y": 1292, + "w": 61, + "h": 63 + } + }, + "sequence_of_play": { + "Limited Command": { + "x": 854, + "y": 1305, + "w": 90, + "h": 54 + }, + "Eligible Factions": { + "x": 854, + "y": 1367, + "w": 91, + "h": 201 + }, + "Pass": { + "x": 854, + "y": 1578, + "w": 90, + "h": 56 + }, + "Ineligible Factions": { + "x": 1166, + "y": 1367, + "w": 90, + "h": 201 + }, + "Command and Decree": { + "x": 1016, + "y": 1371, + "w": 77, + "h": 77 + }, + "Event or Command": { + "x": 1016, + "y": 1488, + "w": 77, + "h": 77 + } + } + } +} + +/* STATE */ + +function piece_space(p) { + return view.pieces[p] +} + +/* BUILD */ + +let ui = { + map: document.getElementById("map"), + favicon: document.getElementById("favicon"), + header: document.querySelector("header"), + status: document.getElementById("status"), + spaces: [], + control: [], + card_tip: document.getElementById("card_tip"), + this_card: document.getElementById("this_card"), + shaded_event: document.getElementById("shaded_event"), + unshaded_event: document.getElementById("unshaded_event"), + deck_outer: document.getElementById("deck_outer"), + deck_size: document.getElementById("deck_size"), + of_gods_and_kings: document.getElementById("of_gods_and_kings"), + dynasty_card: document.getElementById("dynasty_card"), + tokens: { + token_ds_vp: document.getElementById("token_ds_vp"), + token_bk_vp: document.getElementById("token_bk_vp"), + token_ve_vp: document.getElementById("token_ve_vp"), + token_bk_influence: document.getElementById("token_bk_influence"), + token_ve_influence: document.getElementById("token_ve_influence"), + token_mongol_cavalry: document.getElementById("token_mongol_cavalry"), + cavalry_1: document.getElementById("cavalry_1"), + cavalry_2: document.getElementById("cavalry_2"), + cavalry_3: document.getElementById("cavalry_3"), + cavalry_4: document.getElementById("cavalry_4"), + cavalry_5: document.getElementById("cavalry_5"), + cavalry_6: document.getElementById("cavalry_6"), + cavalry_7: document.getElementById("cavalry_7"), + cavalry_8: document.getElementById("cavalry_8"), + }, + pieces: [], + resources: [ + document.getElementById("ds_resources"), + document.getElementById("bk_resources"), + document.getElementById("ve_resources"), + ], + cylinder: [ + document.getElementById("ds_cylinder"), + document.getElementById("bk_cylinder"), + document.getElementById("ve_cylinder"), + ], +} + +function create(t, p, ...c) { + let e = document.createElement(t) + Object.assign(e, p) + e.append(c) + return e +} + +function register_action(e, action, id) { + e.my_action = action + e.my_id = id + e.onmousedown = on_click_action +} + +function is_action(action, arg) { + if (arg === undefined) + return !!(view.actions && view.actions[action] === 1) + return !!(view.actions && view.actions[action] && set_has(view.actions[action], arg)) +} + +function toggle_pieces() { + if (ui.map.classList.contains("hide_tokens")) { + ui.map.classList.remove("hide_tokens") + ui.map.classList.remove("hide_pieces") + } else if (ui.map.classList.contains("hide_pieces")) { + ui.map.classList.add("hide_tokens") + } else { + ui.map.classList.add("hide_pieces") + } +} + +function on_click_action(evt) { + if (evt.button === 0) + send_action(evt.target.my_action, evt.target.my_id) +} + +function init_ui() { + register_action(ui.this_card, "event", undefined) + register_action(ui.unshaded_event, "unshaded", undefined) + register_action(ui.shaded_event, "shaded", undefined) + register_action(ui.resources[DS], "resources", DS) + register_action(ui.resources[BK], "resources", BK) + register_action(ui.resources[VE], "resources", VE) + + ui.this_card.onmouseenter = on_focus_this_event + ui.this_card.onmouseleave = on_blur_event + ui.shaded_event.onmouseenter = on_focus_shaded_event + ui.shaded_event.onmouseleave = on_focus_this_event + ui.unshaded_event.onmouseenter = on_focus_unshaded_event + ui.unshaded_event.onmouseleave = on_focus_this_event + + for (let s = 0; s < 20; ++s) { + let e = null + + // provinces + if (s < S_DELHI) { + let { cx, cy } = layout.circles.provinces[space_name[s]] + ui.control[s] = e = create("div", { className: "token tributary" }) + e.style.left = (cx - 24) + "px" + e.style.top = (cy - 24) + "px" + document.getElementById("tokens").appendChild(e) + + ui.spaces[s] = e = create("div", { className: "space province" }) + e.style.left = (cx - 45) + "px" + e.style.top = (cy - 45) + "px" + e.style.width = (90 - 4) + "px" + e.style.height = (90 - 4) + "px" + register_action(e, "space", s) + document.getElementById("spaces").appendChild(e) + } + + // delhi & passes + if (s >= S_DELHI && s <= S_PUNJAB) { + let { cx, cy, rx, ry } = layout.circles.mongol_invasion_regions[space_name[s]] + ui.spaces[s] = e = create("div", { className: "space province" }) + e.style.left = (cx - rx) + "px" + e.style.top = (cy - ry) + "px" + e.style.width = (rx * 2 - 4) + "px" + e.style.height = (ry * 2 - 4) + "px" + register_action(e, "space", s) + document.getElementById("spaces").appendChild(e) + } + + // boxes + if (s >= S_MONGOL_INVADERS && s <= S_VE_AVAILABLE) { + let { x, y, w, h } = layout.rects.available_boxes[space_name[s]] + ui.spaces[s] = e = create("div", { className: "space" }) + e.style.left = x + "px" + e.style.top = y + "px" + e.style.width = (w - 4) + "px" + e.style.height = (h - 4) + "px" + register_action(e, "space", s) + document.getElementById("spaces").appendChild(e) + } + } + + function create_piece(c, action, id, x, y) { + let e = create("div", { + className: c, + my_action: action, + my_id: id, + my_x_offset: x, + my_y_offset: y, + onmousedown: on_click_action + }) + document.getElementById("pieces").appendChild(e) + return e + } + + function create_piece_list(faction, type, c, x, y) { + for (let p = first_piece[faction][type]; p <= last_piece[faction][type]; ++p) + ui.pieces[p] = create_piece(c, "piece", p, x, y) + } + + ui.pieces = [] + +return + + /* + <div class="piece ds governor" style="left:200px;top:380px"></div> + <div class="piece bk amir rebel" style="left:230px;top:380px"></div> + <div class="piece ve raja rebel" style="left:260px;top:380px"></div> + <div class="piece ds cube" style="left:210px;top:430px"></div> + <div class="piece mongol cube" style="left:245px;top:430px"></div> + <div class="piece ds disk" style="left:200px;top:480px"></div> + <div class="piece bk disk" style="left:250px;top:480px"></div> + <div class="piece ve disk" style="left:300px;top:480px"></div> + */ + + + create_piece_list(DS, DISK, "piece ds disk", -4, 10) + create_piece_list(DS, GOVERNOR, "piece ds governor", 0, 4) + create_piece_list(DS, CUBE, "piece ds cube", 0, 4) + + create_piece_list(BK, DISK, "piece ve disk", -4, 10) + create_piece_list(BK, AMIR, "piece ve amir", 2, 0) + + create_piece_list(VE, DISK, "piece ve disk", -4, 10) + create_piece_list(VE, RAJA, "piece ve raja", 2, 0) + + create_piece_list(MONGOLS, CUBE, "piece mongol cube", 2, 0) +} + +/* UPDATE */ + +function action_menu_item(action) { + let menu = document.getElementById(action + "_menu") + if (view.actions && action in view.actions) { + menu.classList.toggle("hide", false) + menu.classList.toggle("disabled", view.actions[action] === 0) + return 1 + } else { + menu.classList.toggle("hide", true) + return 0 + } +} + +function action_menu(menu, action_list) { + let x = 0 + for (let action of action_list) + x |= action_menu_item(action) + menu.classList.toggle("hide", !x) +} + +const LAYOUT_CACHE = { + Center: [], + Govt: [], + FARC: [], + AUC: [], + Cartels: [], + COIN: [], + INSURGENTS: [], +} + +function get_layout_xy(s, f = "Center") { + if (!LAYOUT_CACHE[f][s]) { + let id = (f !== "Center") ? data.spaces[s].id + " " + f : data.spaces[s].id + LAYOUT_CACHE[f][s] = LAYOUT[id] + } + return LAYOUT_CACHE[f][s] +} + +function filter_piece_list(list, space, faction, type) { + for (let i = first_piece[faction][type]; i <= last_piece[faction][type]; ++i) + if (view.pieces[i] === space) + list.push(ui.pieces[i]) +} + +function layout_available(faction, type, xorig, yorig) { + let list = [] + filter_piece_list(list, AVAILABLE, faction, type) + layout_pieces(list, xorig, yorig + 35, null, AVAILABLE) +} + +function layout_pieces(list, xorig, yorig, bases, shipments, s, faction) { + const dx = 17 + const dy = 11 + let off_x = 0 + let off_y = 0 + let rotate = 0 + + if (s >= 0) + rotate = (data.spaces[s].type === "mountain") ? 1 : 0 + + function layout_piece_rowcol(nrow, ncol, row, col, e, z) { + // basic piece size = 29x36 + let x = xorig - (row * dx - col * dx) - 15 + off_x + let y = yorig - (row * dy + col * dy) - 24 + off_y + let xo = e.my_x_offset + let yo = e.my_y_offset + e.style.left = (xo + x) + "px" + e.style.top = (yo + y) + "px" + e.style.zIndex = y + e.my_x = x + 15 + e.my_y = y + 24 + e.my_z = z + } + + if (list.length > 0) { + let nrow = Math.round(Math.sqrt(list.length)) + let ncol = Math.ceil(list.length / nrow) + let z = 50 + let i = 0 + if ((s >= 0 && s <= last_city) || s >= first_loc) { + off_x = (nrow - ncol) * 6 + off_y = (nrow - 1) * 8 + } + for (let row = 0; row < nrow; ++row) + for (let col = 0; col < ncol; ++col) + if (i < list.length) + layout_piece_rowcol(nrow, ncol, row, col, list[list.length-(++i)], z--) + } + + if (bases) + layout_dept_bases(bases, xorig + off_x, yorig + 12 + off_y, s) +} + +function place_piece(p, x, y, z) { + p.style.left = x + "px" + p.style.top = y + "px" + if (z) + p.style.zIndex = z + p.my_x = x + p.my_y = y + p.my_z = z +} + +function layout_dept_bases(list, xc, yc, s) { + if (list.length === 1) { + place_piece(list[0], xc - 20 + 32, yc - 10, 52) + } + if (list.length === 2) { + place_piece(list[0], xc - 20 + 18, yc - 0, 52) + place_piece(list[1], xc - 20 + 18 + 32, yc - 21, 51) + } + if (list.length === 3) { + // TODO + place_piece(list[0], xc - 20 + 18, yc - 0, 52) + place_piece(list[1], xc - 20 + 18 + 32, yc - 21, 51) + place_piece(list[2], xc - 20 + 18 + 32, yc - 21, 100) + } +} + +function layout_available_bases(list, x0, y0, cols, rows, dx, dy) { + let x = x0 + let y = y0 + for (let i = 0; i < list.length; ++i) { + place_piece(list[list.length-i-1], x - 44 - 6, y + 8) + y += dy + if (i % rows === rows - 1) { + x -= dx + y = y0 + } + } +} + +function layout_sop() { + let i, x, y, z + + // Eligible + x = 1164 - 22 + y = 480 + z = 1 + let order = data.card_order[view.deck[0]] + for (let faction of order) { + if (view.cylinder[faction] === ELIGIBLE) { + place_piece(ui.cylinder[faction], x, y, z) + y += 40 + z += 1 + } + } + + // Ineligible + x = 1510 - 22 + y = 480 + z = 1 + for (let faction = 0; faction < 4; ++faction) { + if (view.cylinder[faction] === INELIGIBLE) { + place_piece(ui.cylinder[faction], x, y, z) + y += 40 + z += 1 + } + } + + // Pass + x = 1164 - 22 - 24 + y = 688 - 28 + z = 1 + i = 0 + for (let faction = 0; faction < 4; ++faction) { + if (view.cylinder[faction] === SOP_PASS) { + place_piece(ui.cylinder[faction], x, y, z) + x += 48 + z += 1 + if (++i === 2) { x -= 72; y += 28 } + } + } + + for (let [i, x, y] of sop_xy) { + for (let faction = 0; faction < 4; ++faction) + if (view.cylinder[faction] === i) + place_piece(ui.cylinder[faction], x, y) + } +} + +function layout_score_cell(list, x, y, dx, dy) { + let z = 1 + if (list.length > 1) { + if (dy > 0) y -= 12 + if (dy < 0) y += 12 + if (dx > 0) x -= 12 + if (dx < 0) x += 12 + } + for (let p of list) { + if (p.classList.contains("token")) + place_piece(p, x - 24, y - 24, z) + else + place_piece(p, x - 22, y - 24, z) + x += dx + y += dy + z += 1 + } +} + +function layout_score() { + let list = [] + let x, y + for (let i = 0; i <= 24; ++i) { + list.length = 0 + + if (view.vp[DS] === i) list.push(ui.tokens.token_ds_vp) + if (view.vp[BK] === i) list.push(ui.tokens.token_bk_vp) + if (view.vp[VE] === i) list.push(ui.tokens.token_ve_vp) + + for (let faction = 0; faction < 3; ++faction) + if (view.resources[faction] === i) + list.push(ui.resources[faction]) + + // X: 15 -> 1198 (0-18) + // Y: 14 -> 403 (18-24) + + if (i < 18) y = 20 + else y = 20 + (i - 18) * 65 + + if (i < 18) x = 23 + (i * 65.6) + else x = 1204 + + x = Math.round(x) + 24 + y = Math.round(y) + 24 + + if (i < 17) layout_score_cell(list, x, y, 0, 28) + else if (i === 17) layout_score_cell(list, x, y, -18, 25) + else layout_score_cell(list, x, y, -41, 0) + } +} + +function update_rebels(faction, type, rebel) { + let p0 = first_piece[faction][type] + let p1 = last_piece[faction][type] + for (let i = 0, p = p0; p <= p1; ++i, ++p) { + if (underground & (1 << i)) + ui.pieces[p].classList.add("rebel") + else + ui.pieces[p].classList.remove("rebel") + } +} + +function make_card_class_name(c) { + return "card card_" + c + // TODO: + if (set_has([1,2,3,7,9,10,11,13], view.deck[0])) + return "card card_" + c + " u" + data.card_unshaded_lines[c] + " s" + data.card_shaded_lines[c] + " c" + else + return "card card_" + c + " u" + data.card_unshaded_lines[c] + " s" + data.card_shaded_lines[c] +} + +function update_player_active(name, x) { + if (roles[name]) + roles[name].element.classList.toggle("active", x) +} + +let once = true +function on_update() { + if (once) { + init_ui() + once = false + } + + switch (player) { + case "DS": ui.favicon.href = "images/Flag_DS.png"; break + case "BK": ui.favicon.href = "images/Flag_BK.png"; break + case "VE": ui.favicon.href = "images/Flag_VE.png"; break + } + + ui.header.classList.toggle("ds", view.current === DS) + ui.header.classList.toggle("bk", view.current === BK) + ui.header.classList.toggle("ve", view.current === VE) + + ui.tokens.token_bk_influence.classList.toggle("action", is_action("bk_inf")) + ui.tokens.token_ve_influence.classList.toggle("action", is_action("ve_inf")) + + ui.resources[DS].classList.toggle("action", is_action("resources", DS)) + ui.resources[BK].classList.toggle("action", is_action("resources", BK)) + ui.resources[VE].classList.toggle("action", is_action("resources", VE)) + + update_player_active(NAME_DS, view.current === DS) + update_player_active(NAME_VE, view.current === VE) + update_player_active(NAME_BK, view.current === BK) + + ui.this_card.className = make_card_class_name(view.deck[0]) + if (view.deck[1] > 0) { + ui.deck_outer.className = "card card_back" + ui.deck_size.textContent = `${view.deck[1]}` + } else { + ui.deck_outer.className = "hide" + } + + ui.this_card.classList.toggle("action", !!(view.actions && view.actions.event === 1)) + ui.shaded_event.classList.toggle("action", !!(view.actions && view.actions.shaded === 1)) + ui.unshaded_event.classList.toggle("action", !!(view.actions && view.actions.unshaded === 1)) + + layout_score() + +return + + layout_sop() + + layout_available(GOVT, TROOPS, 114, 248) + layout_available(GOVT, POLICE, 114, 448) + layout_available(FARC, GUERRILLA, 1396, 234) + layout_available(AUC, GUERRILLA, 196, 2370) + layout_available(CARTELS, GUERRILLA, 1465, 1970) + + for (let i = view.farc_zones.length; i < ui.farc_zones.length; ++i) + ui.farc_zones[i].className = "hide" + + let tix = 0 + + let list = [] + let bases = [] + let drugs = [] + for (let s = 0; s < data.spaces.length; ++s) { + let id = data.spaces[s].id + let xy + + if (s <= last_pop) { + switch (view.support[s]) { + case -2: ui.support[s].className = "token active_opposition"; break + case -1: ui.support[s].className = "token passive_opposition"; break + case 0: ui.support[s].className = "hide"; break + case 1: ui.support[s].className = "token passive_support"; break + case 2: ui.support[s].className = "token active_support"; break + } + } + + if (s >= first_loc && s <= last_loc) { + if (set_has(view.sabotage, s)) + ui.sabotage[s].className = "token sabotage" + else + ui.sabotage[s].className = "hide" + } + + if (s >= first_dept && s <= last_dept) { + ui.farc_zone[s].classList.toggle("hide", !(view.farc_zones & (1<<s))) + } + + if (s <= last_dept) { + if (view.govt_control & (1<<s)) + ui.control[s].className = "token govt_control" + else if (view.farc_control & (1<<s)) + ui.control[s].className = "token farc_control" + else + ui.control[s].className = "hide" + } + + tix = layout_terror(tix, s, map_get(view.terror, s, 0) * 1) + + update_rebels(BK, AMIR, view.rebel[BK]) + update_rebels(VE, RAJA, view.rebel[VE]) + + drugs.length = 0 + for (let i = 0; i < 4; ++i) { + let shx = view.shipments[i] + if (shx !== 0) { + if ((shx & 3) === 0 && view.pieces[(shx >> 2)] === s) + layout_shipments_push(drugs, ui.pieces[shx>>2], ui.shipments[i], piece_faction(shx>>2)) + else if ((shx & 3) !== 0 && (shx >> 2) === s) + layout_shipments_push(drugs, null, ui.shipments[i], shx & 3) + } + } + + if (s <= last_city) { + list.length = bases.length = 0 + filter_piece_list(list, s, FARC, GUERRILLA) + filter_piece_list(list, s, AUC, GUERRILLA) + filter_piece_list(list, s, CARTELS, GUERRILLA) + filter_piece_list(list, s, GOVT, TROOPS) + filter_piece_list(list, s, GOVT, POLICE) + filter_piece_list(bases, s, GOVT, BASE) + filter_piece_list(bases, s, FARC, BASE) + filter_piece_list(bases, s, AUC, BASE) + filter_piece_list(bases, s, CARTELS, BASE) + xy = get_layout_xy(s) + layout_pieces(list, xy[0], xy[1], null, null, s, 0) + layout_city_bases(bases, xy[0], xy[1] + get_layout_radius(s) - 12, s) + + layout_city_shipments(s, drugs, xy[0], xy[1]) + } else if (s <= last_dept) { + list.length = bases.length = 0 + filter_piece_list(list, s, FARC, GUERRILLA) + filter_piece_list(bases, s, FARC, BASE) + xy = get_layout_xy(s, "FARC") + layout_pieces(list, xy[0], xy[1], bases, drugs, s, FARC) + + list.length = bases.length = 0 + filter_piece_list(list, s, AUC, GUERRILLA) + filter_piece_list(bases, s, AUC, BASE) + xy = get_layout_xy(s, "AUC") + layout_pieces(list, xy[0], xy[1], bases, drugs, s, AUC) + + list.length = bases.length = 0 + filter_piece_list(list, s, CARTELS, GUERRILLA) + filter_piece_list(bases, s, CARTELS, BASE) + xy = get_layout_xy(s, "Cartels") + layout_pieces(list, xy[0], xy[1], bases, drugs, s, CARTELS) + + list.length = bases.length = 0 + filter_piece_list(list, s, GOVT, TROOPS) + filter_piece_list(list, s, GOVT, POLICE) + filter_piece_list(bases, s, GOVT, BASE) + xy = get_layout_xy(s, "Govt") + layout_pieces(list, xy[0], xy[1], bases, null, s, GOVT) + } else { + list.length = 0 + filter_piece_list(list, s, FARC, GUERRILLA) + filter_piece_list(list, s, AUC, GUERRILLA) + filter_piece_list(list, s, CARTELS, GUERRILLA) + xy = get_layout_xy(s, "INSURGENTS") + layout_pieces(list, xy[0], xy[1], null, s) + + list.length = 0 + filter_piece_list(list, s, GOVT, TROOPS) + filter_piece_list(list, s, GOVT, POLICE) + xy = get_layout_xy(s, "COIN") + layout_pieces(list, xy[0], xy[1], null, s) + + xy = get_layout_xy(s) + layout_loc_shipments(s, drugs, xy[0], xy[1]) + } + + ui.spaces[s].classList.toggle("action", is_action("space", s)) + ui.spaces[s].classList.toggle("selected", view.where === s) + } + + for (; tix < 40; ++tix) + ui.terror[tix].className = "hide" + + for (let i = first_piece[AUC][GUERRILLA]; i <= last_piece[AUC][GUERRILLA]; ++i) + ui.pieces[i].classList.toggle("hide", view.pieces[i] === OUT_OF_PLAY) + + list.length = 0 + for (let i = 0; i < 4; ++i) { + let shx = view.shipments[i] + let shf = shx & 3 + if (shx === 0) + list.push(ui.shipments[i]) + if (shf === 0) + ui.shipments[i].className = "token shipment" + else if (shf === FARC) + ui.shipments[i].className = "token shipment farc" + else if (shf === AUC) + ui.shipments[i].className = "token shipment auc" + else if (shf === CARTELS) + ui.shipments[i].className = "token shipment cartels" + if (view.actions && view.actions.shipment && set_has(view.actions.shipment, i)) + ui.shipments[i].classList.add("action") + if (view.selected_shipment === i) + ui.shipments[i].classList.add("selected") + } + layout_available_bases(list, 1532, 1722, 2, 2, 89, 69) + + list.length = 0 + filter_piece_list(list, AVAILABLE, GOVT, BASE) + layout_available_bases(list, 287 + 177, 371, 3, 1, 61, 0) + + list.length = 0 + filter_piece_list(list, AVAILABLE, FARC, BASE) + layout_available_bases(list, 446 + 543, 2295, 9, 1, 61, 0) + + list.length = 0 + filter_piece_list(list, AVAILABLE, AUC, BASE) + layout_available_bases(list, 446 + 360, 2386, 6, 1, 61, 0) + + list.length = 0 + filter_piece_list(list, AVAILABLE, CARTELS, BASE) + layout_available_bases(list, 1373 + 183, 2117, 3, 5, 63, 63) + + if (view.actions && view.actions.piece) + for (let i = 0; i < ui.pieces.length; ++i) + ui.pieces[i].classList.toggle("action", set_has(view.actions.piece, i)) + else + for (let i = 0; i < ui.pieces.length; ++i) + ui.pieces[i].classList.remove("action") + for (let i = 0; i < ui.pieces.length; ++i) + ui.pieces[i].classList.toggle("selected", view.who === i) + + action_menu(document.getElementById("negotiate_menu"), [ + "remove_pieces", + "transfer_resources", + "transfer_shipment", + "ask_resources", + "ask_shipment", + ]) + + // Select Faction + action_button("govt", "Government") + action_button("farc", "FARC") + action_button("auc", "AUC") + action_button("cartels", "Cartels") + + confirm_action_button("choose_govt", "Government", "Choose GOVERNMENT to execute this event?") + confirm_action_button("choose_farc", "FARC", "Choose FARC to execute this event?") + confirm_action_button("choose_auc", "AUC", "Choose AUC to execute this event?") + confirm_action_button("choose_cartels", "Cartels", "Choose CARTELS to execute this event?") + + // Select Operation + action_button("train", "Train") + action_button("patrol", "Patrol") + action_button("sweep", "Sweep") + action_button("assault", "Assault") + action_button("rally", "Rally") + action_button("march", "March") + action_button("attack", "Attack") + action_button("terror", "Terror") + + // Select Special Activity + action_button("air_lift", "Air Lift") + action_button("air_strike", "Air Strike") + action_button("eradicate", "Eradicate") + action_button("extort", "Extort") + action_button("ambush", "Ambush") + action_button("assassinate", "Assassinate") + action_button("kidnap", "Kidnap") + action_button("cultivate", "Cultivate") + action_button("process", "Process") + action_button("bribe", "Bribe") + + // Train/Rally sub-actions + action_button("move", "Move") + action_button("flip", "Flip") + action_button("base", "Base") + action_button("civic", "Civic Action") + + action_button("support", "Support") + action_button("opposition", "Opposition") + + action_button("remove", "Remove") + action_button("roll", "Roll") + action_button("skip", "Skip") + action_button("next", "Next") + action_button("pass", "Pass") + + action_button("end_train", "End Train") + action_button("end_patrol", "End Patrol") + action_button("end_sweep", "End Sweep") + action_button("end_assault", "End Assault") + action_button("end_rally", "End Rally") + action_button("end_march", "End March") + action_button("end_attack", "End Attack") + action_button("end_terror", "End Terror") + + action_button("end_air_lift", "End Air Lift") + action_button("end_extort", "End Extort") + action_button("end_assassinate", "End Assassinate") + action_button("end_kidnap", "End Kidnap") + action_button("end_process", "End Process") + action_button("end_bribe", "End Bribe") + + action_button("end_event", "End Event") + + action_button("deny", "Deny") + action_button("done", "Done") + action_button("undo", "Undo") +} + +/* TOOLTIPS */ + +function register_card_tip(e, c) { + e.onmouseenter = () => on_focus_card_tip(c) + e.onmouseleave = on_blur_card_tip +} + +function on_focus_this_event() { + let c = view.deck[0] + if (c > 0) + ui.status.textContent = data.card_title[c] +} + +function on_focus_unshaded_event() { + let c = view.deck[0] + if (c > 0) { + let f = data.card_flavor[c] + if (f) + ui.status.textContent = data.card_title[c] + " - " + f + else + ui.status.textContent = data.card_title[c] + } +} + +function on_focus_shaded_event() { + let c = view.deck[0] + if (c > 0) { + ui.status.textContent = data.card_title[c] + " - " + data.card_flavor_shaded[c] + } +} + +function on_blur_event() { + ui.status.textContent = "" +} + +function on_focus_card_tip(c) { + ui.card_tip.className = "card card_" + c +} + +function on_blur_card_tip() { + ui.card_tip.className = "hide" +} + +function on_focus_space_tip(s) { + ui.spaces[s].classList.add("tip") +} + +function on_blur_space_tip(s) { + ui.spaces[s].classList.remove("tip") +} + +function on_click_space_tip(s) { + scroll_into_view(ui.spaces[s]) +} + +/* LOG */ + +function sub_card(match, p1) { + let x = p1 | 0 + let n = data.card_title[x] + return `<span class="tip" onmouseenter="on_focus_card_tip(${x})" onmouseleave="on_blur_card_tip()">${n}</span>` +} + +function sub_space(match, p1) { + let x = p1 | 0 + let n = data.space_name[x] + return `<span class="tip" onmouseenter="on_focus_space_tip(${x})" onmouseleave="on_blur_space_tip(${x})" onmousedown="on_click_space_tip(${x})">${n}</span>` +} + +function on_log(text) { + let p = document.createElement("div") + + if (text.match(/^>/)) { + text = text.substring(1) + p.className = "indent" + } + + text = text.replace(/&/g, "&") + text = text.replace(/</g, "<") + text = text.replace(/>/g, ">") + + if (text.match(/^\.h1/)) { + text = text.substring(4) + p.className = "h1" + } + else if (text.match(/^\.h2 Gov/)) { + text = text.substring(3) + p.className = "h2 govt" + } + else if (text.match(/^\.h2 AUC/)) { + text = text.substring(3) + p.className = "h2 auc" + } + else if (text.match(/^\.h2 Cartels/)) { + text = text.substring(3) + p.className = "h2 cartels" + } + else if (text.match(/^\.h2 FARC/)) { + text = text.substring(3) + p.className = "h2 farc" + } + else if (text.match(/^\.h2 /)) { + text = text.substring(3) + p.className = "h2" + } + else if (text.match(/^\.h3/)) { + text = text.substring(4) + p.className = "h3" + } + else if (text.match(/^\.h4/)) { + text = text.substring(4) + p.className = "h4" + } + else if (text.match(/^\.n/)) { + text = text.substring(3) + p.className = "italic" + } + else if (text.match(/^\.i/)) { + text = text.substring(3) + p.className = "indent italic" + } + + text = text.replace(/C(\d+)/g, sub_card) + text = text.replace(/S(\d+)/g, sub_space) + + p.innerHTML = text + return p +} @@ -6,13 +6,71 @@ let view = null /* DATA */ -const data = require("./data.js") +// const data = require("./data.js") + +const space_name = [ + "Andhra", + "Bengal", + "Gondwana", + "Gujarat", + "Jaunpur", + "Karnataka", + "Madhyadesh", + "Maharashtra", + "Malwa", + "Orissa", + "Rajput Kingdoms", + "Sindh", + "Tamilakam", + "Delhi", + "Mountain Passes", + "Punjab", + "Mongol Invaders", + "DS Available", + "BK Available", + "VE Available", +] + +const S_ANDHRA = 0 +const S_BENGAL = 1 +const S_GONDWANA = 2 +const S_GUJARAT = 3 +const S_JAUNPUR = 4 +const S_KARNATAKA = 5 +const S_MADHYADESH = 6 +const S_MAHARASHTRA = 7 +const S_MALWA = 8 +const S_ORISSA = 9 +const S_RAJPUT_KINGDOMS = 10 +const S_SINDH = 11 +const S_TAMILAKAM = 12 +const S_DELHI = 13 +const S_MOUNTAIN_PASSES = 14 +const S_PUNJAB = 15 +const S_MONGOL_INVADERS = 16 +const S_DS_AVAILABLE = 17 +const S_BK_AVAILABLE = 18 +const S_VE_AVAILABLE = 19 +const S_BK_INF_2 = 20 +const S_BK_INF_4 = 21 +const S_VE_INF_1 = 22 +const S_VE_INF_2 = 23 +const S_VE_INF_3 = 24 +const S_VE_INF_4 = 25 // Factions const DS = 0 const BK = 1 const VE = 2 +// Sequence of Play options +const ELIGIBLE = 0 +const SOP_LIMITED_COMMAND = 1 +const SOP_COMMAND_DECREE = 2 +const SOP_EVENT_OR_COMMAND = 3 +const SOP_PASS = 4 +const INELIGIBLE = 5 + const faction_name = [ "Delhi Sultanate", "Bahmani Kingdom", "Vijayanagara Empire" ] // Role names @@ -66,7 +124,11 @@ exports.view = function (state, role) { actions: null, log: game.log, current: game.current, - deck: [ this_card, deck_size ], + vp: [ 18, 0, 0 ], + resources: [ 12, 6, 7 ], + bk_inf: 0, + ve_inf: 0, + deck: [ this_card, deck_size, game.of_gods_and_kings ], } if (game.result) { @@ -149,7 +211,14 @@ exports.setup = function (seed, scenario, _options) { current: 0, state: null, - // TODO: control, pieces, and tracks + cylinder: [ ELIGIBLE, ELIGIBLE, ELIGIBLE ], + resources: [ 12, 6, 7 ], + bk_inf: 0, + ve_inf: 0, + tributary: 8191, // all 13 provinces + rebel: 0, // amir/raja rebel status + pieces: [], // piece locations + deck: [], } diff --git a/tools/layout.svg b/tools/layout.svg new file mode 100644 index 0000000..1ead306 --- /dev/null +++ b/tools/layout.svg @@ -0,0 +1,378 @@ +<?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="1650" + version="1.1" + id="svg4" + sodipodi:docname="layout.svg" + inkscape:version="1.0.2 (e86c870879, 2021-01-15)"> + <metadata + id="metadata10"> + <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="defs8" /> + <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="namedview6" + showgrid="false" + inkscape:zoom="1.3237657" + inkscape:cx="732.66925" + inkscape:cy="245.31771" + inkscape:current-layer="g74" + inkscape:document-rotation="0" /> + <image + sodipodi:absref="/home/tor/src/rally/public/vijayanagara/map75.jpg" + xlink:href="../map75.jpg" + id="image2" + sodipodi:insensitive="true" + image-rendering="pixelated" + height="1650" + width="1275" + y="0" + x="0" /> + <g + id="g58" + inkscape:label="provinces"> + <circle + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="path12" + cx="65.337875" + cy="393.10236" + r="34" + inkscape:label="Sindh" /> + <circle + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="circle837" + cx="328.72156" + cy="402.87799" + r="34" + inkscape:label="Rajput Kingdoms" /> + <circle + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="circle839" + cx="605.31427" + cy="572.21539" + r="34" + inkscape:label="Malwa" /> + <circle + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="circle841" + cx="894.45099" + cy="455.29669" + r="34" + inkscape:label="Jaunpur" /> + <circle + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="circle843" + cx="1192.2776" + cy="535.87579" + r="34" + inkscape:label="Bengal" /> + <circle + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="circle845" + cx="1034.0819" + cy="858.19214" + r="34" + inkscape:label="Orissa" /> + <circle + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="circle847" + cx="913.01575" + cy="736.92853" + r="34" + inkscape:label="Gondwana" /> + <circle + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="circle849" + cx="670.44879" + cy="817.22009" + r="34" + inkscape:label="Madhyadesh" /> + <circle + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="circle851" + cx="743.06372" + cy="1089.9803" + r="34" + inkscape:label="Andhra" /> + <circle + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="circle853" + cx="438.38513" + cy="968.98218" + r="34" + inkscape:label="Maharashtra" /> + <circle + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="circle855" + cx="220.07103" + cy="678.30847" + r="34" + inkscape:label="Gujarat" /> + <circle + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="circle857" + cx="550.05054" + cy="1278.4006" + r="34" + inkscape:label="Karnataka" /> + <circle + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="circle859" + cx="703.82556" + cy="1399.2897" + r="34" + inkscape:label="Tamilakam" /> + </g> + <g + id="g64" + inkscape:label="available_boxes"> + <rect + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="rect863" + width="177.06958" + height="110.22359" + x="23.983149" + y="99.914024" + inkscape:label="Mongol Invaders" /> + <rect + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="rect865" + width="387.69797" + height="223.10062" + x="796.19531" + y="90.733284" + inkscape:label="DS Available" /> + <rect + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="rect867" + width="237.8972" + height="223.72247" + x="21.017628" + y="908.0174" + inkscape:label="BK Available" /> + <rect + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="rect869" + width="238.52563" + height="224.56042" + x="20.668497" + y="1405.1785" + inkscape:label="VE Available" /> + </g> + <g + id="g86" + inkscape:label="tracks"> + <rect + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="rect915" + width="60.631813" + height="61.421806" + x="1197.824" + y="403.09293" + inkscape:label="Track 24" /> + <rect + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="rect913" + width="61.619305" + height="62.804291" + x="1196.8364" + y="14.219839" + inkscape:label="Track 18" /> + <rect + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="rect911" + width="61.619305" + height="62.804291" + x="15.404826" + y="14.219839" + inkscape:label="Track 0" /> + <rect + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="rect871" + width="61.446884" + height="62.564102" + x="18.15476" + y="1186.2042" + inkscape:label="BK Influence 0" /> + <rect + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="rect873" + width="61.446884" + height="62.564102" + x="88.81868" + y="1186.4835" + inkscape:label="BK Influence 1" /> + <rect + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="rect877" + width="61.446884" + height="62.564102" + x="160.11537" + y="1186.286" + inkscape:label="BK Influence 2" /> + <rect + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="rect879" + width="61.446884" + height="62.564102" + x="231.21457" + y="1185.891" + inkscape:label="BK Influence 3" /> + <rect + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="rect881" + width="61.446884" + height="62.564102" + x="302.31375" + y="1186.0885" + inkscape:label="BK Influence 4" /> + <rect + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="rect885" + width="61.446884" + height="62.564102" + x="18.15476" + y="1291.7811" + inkscape:label="VE Influence 0" /> + <rect + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="rect887" + width="61.446884" + height="62.564102" + x="88.81868" + y="1292.0604" + inkscape:label="VE Influence 1" /> + <rect + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="rect889" + width="61.446884" + height="62.564102" + x="160.11537" + y="1291.8629" + inkscape:label="VE Influence 2" /> + <rect + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="rect891" + width="61.446884" + height="62.564102" + x="231.21457" + y="1291.4679" + inkscape:label="VE Influence 3" /> + <rect + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="rect893" + width="61.446884" + height="62.564102" + x="302.31375" + y="1291.6654" + inkscape:label="VE Influence 4" /> + </g> + <g + id="g97" + inkscape:label="sequence_of_play"> + <rect + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="rect895" + width="90.453941" + height="53.719433" + x="853.98035" + y="1305.0652" + inkscape:label="Limited Command" /> + <rect + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="rect897" + width="90.848976" + height="200.65773" + x="853.58533" + y="1367.4745" + inkscape:label="Eligible Factions" /> + <rect + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="rect899" + width="90.453979" + height="56.48436" + x="853.58533" + y="1577.6122" + inkscape:label="Pass" /> + <rect + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="rect901" + width="90.058937" + height="201.0527" + x="1166.4218" + y="1366.6846" + inkscape:label="Ineligible Factions" /> + <rect + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="rect905" + width="77.024101" + height="77.024071" + x="1016.3234" + y="1371.0295" + inkscape:label="Command and Decree" /> + <rect + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:4" + id="rect907" + width="77.024101" + height="77.024071" + x="1016.3234" + y="1487.5532" + inkscape:label="Event or Command" /> + </g> + <g + id="g74" + inkscape:label="mongol_invasion_regions"> + <ellipse + style="fill:#00fff2;fill-opacity:0.566802;stroke-width:4" + id="path66" + cx="301.7406" + cy="139.98946" + rx="82.824387" + ry="27.608128" + inkscape:label="Mountain Passes" /> + <ellipse + style="fill:#00fff2;fill-opacity:0.566802;stroke-width:4.16953" + id="path68" + cx="478.43262" + cy="219.56583" + rx="58.139465" + ry="18.838488" + inkscape:transform-center-x="0.5963195" + inkscape:transform-center-y="-19.991867" + inkscape:label="Punjab" /> + <circle + style="fill:#00fff2;fill-opacity:0.566802;stroke:none;stroke-width:5.05769" + id="path861" + cx="646.8681" + cy="374.82599" + r="148" + inkscape:label="Delhi" /> + </g> +</svg> diff --git a/tools/parse-layout.js b/tools/parse-layout.js new file mode 100644 index 0000000..2060e17 --- /dev/null +++ b/tools/parse-layout.js @@ -0,0 +1,187 @@ +"use strict" + +const fs = require("fs") + +let circles = {} +let rects = {} +let edges = {} +let mode, name, subname, x, y, w, h, cx, cy, rx, ry, x2, y2 +let labels = [] + +function add_circle(cx, cy, rx, ry) { + if (!(name in circles)) + circles[name] = {} + circles[name][subname] = {cx,cy,rx,ry} +} + +function add_rect(x, y, w, h) { + if (!(name in rects)) + rects[name] = {} + rects[name][subname] = {x,y,w,h} +} + +function add_edge(x1, y1, x2, y2) { + if (name in edges) + edges[name].push({x1,y1,x2,y2}) + else + edges[name] = [ {x1,y1,x2,y2} ] +} + +function flush() { + if (mode === 'path') { + add_edge(x, y, x2, y2) + } + if (mode === 'rect') { + add_rect(x, y, w, h) + } + if (mode === 'circle') { + add_circle( cx, cy, rx, ry ) + } + x = y = x2 = y2 = w = h = cx = cy = rx = ry = 0 + subname = null +} + +function parse_path_data(path) { + let cx = 0 + let cy = 0 + let abs = 0 + for (let i = 0; i < path.length;) { + switch (path[i]) { + case 'M': + x2 = x = cx = Number(path[i+1]) + y2 = y = cy = Number(path[i+2]) + i += 3 + abs = true + break + case 'm': + x2 = x = cx = cx + Number(path[i+1]) + y2 = y = cy = cy + Number(path[i+2]) + i += 3 + abs = false + break + case 'C': + x2 = cx = Number(path[i+5]) + y2 = cy = Number(path[i+6]) + i += 7 + abs = true + break + case 'L': + i += 1 + abs = true + break + case 'H': + x2 = cx = Number(path[i+1]) + i += 2 + abs = true + break + case 'V': + y2 = cy = Number(path[i+1]) + i += 2 + abs = true + break + case 'c': + x2 = cx = cx + Number(path[i+5]) + y2 = cy = cy + Number(path[i+6]) + i += 7 + abs = false + break + case 'l': + i += 1 + abs = false + break + case 'h': + x2 = cx = cx + Number(path[i+1]) + i += 2 + abs = false + break + case 'v': + y2 = cy = cy + Number(path[i+1]) + i += 2 + abs = false + break + default: + if (abs) { + x2 = cx = Number(path[i+0]) + y2 = cy = Number(path[i+1]) + } else { + x2 = cx = cx + Number(path[i+0]) + y2 = cy = cy + Number(path[i+1]) + } + i += 2 + break + } + } +} + +for (let line of fs.readFileSync("tools/layout.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("<path")) { + flush() + mode = "path" + } + else if (line.startsWith("<g")) { + flush() + mode = "g" + } + else if (line.startsWith('x="')) + x = (Math.round(line.split('"')[1])) + else if (line.startsWith('y="')) + y = (Math.round(line.split('"')[1])) + else if (line.startsWith('width="')) + w = (Math.round(line.split('"')[1])) + else if (line.startsWith('height="')) + h = (Math.round(line.split('"')[1])) + else if (line.startsWith('cx="')) + cx = (Math.round(line.split('"')[1])) + else if (line.startsWith('cy="')) + cy = (Math.round(line.split('"')[1])) + else if (line.startsWith('r="')) + rx = ry = (Math.round(line.split('"')[1])) + else if (line.startsWith('rx="')) + rx = (Math.round(line.split('"')[1])) + else if (line.startsWith('ry="')) + ry = (Math.round(line.split('"')[1])) + else if (line.startsWith('inkscape:label="') && mode === "g") + name = line.split('"')[1] + else if (line.startsWith('inkscape:label="') && mode !== "g") + subname = line.split('"')[1] + else if (line.startsWith('d="')) + parse_path_data(line.split('"')[1].split(/[ ,]/)) + if (line.includes("</tspan>")) { + let name = line.replace(/^[^>]*>/, "").replace(/<\/tspan.*/, "") + labels.push({x, y, name}) + } +} + +flush() + +function find_closest_node(list, x, y) { + let nd = Infinity, nn = null + + for (let n of list) { + let d = Math.hypot(n.x - x, n.y - y) + if (d < nd) { + nd = d + nn = n + } + } + + if (!nn) { + console.log("NOT FOUND", x, y) + return [ null, 0 ] + } + + return [ nn, nd ] +} + +console.log("const layout = " + JSON.stringify({ circles, rects }, null, "\t")) |