summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2022-04-22 20:52:42 +0200
committerTor Andersson <tor@ccxvii.net>2023-02-18 12:31:29 +0100
commit097fb37a0f8cdc02d34bfa51760300b1177f9ea1 (patch)
tree8ac9a9bba626a546bf1822b00a2d45b1bfdcd02e
parent5711adf3ef2c1e702849067ec3f68b04bd904c21 (diff)
downloadpax-pamir-097fb37a0f8cdc02d34bfa51760300b1177f9ea1.tar.gz
Client.
-rw-r--r--create.html4
-rw-r--r--play.css859
-rw-r--r--play.html437
-rw-r--r--play.js885
4 files changed, 2185 insertions, 0 deletions
diff --git a/create.html b/create.html
index e69de29..16a9da7 100644
--- a/create.html
+++ b/create.html
@@ -0,0 +1,4 @@
+<p>
+<label>
+<input type="checkbox" value="true" name="open_hands">Open hands.</span>
+</label>
diff --git a/play.css b/play.css
new file mode 100644
index 0000000..f301ab8
--- /dev/null
+++ b/play.css
@@ -0,0 +1,859 @@
+main { background-color: slategray; }
+header { background-color: silver; }
+#role_Gray { background-color: #b7b2b0; }
+#role_Blue { background-color: #95b4ca; }
+#role_Tan { background-color: #e7cea7; }
+#role_Red { background-color: #d18e95; }
+#role_Black { background-color: #7b7979; }
+
+#log { background-color: ivory; }
+#log div { padding-left: 20px; text-indent: -12px; }
+#log div.i { padding-left: 32px; text-indent: -12px; font-style: italic; }
+#log .turn, #log .dc { font-style: italic; text-align: right; margin: 0; text-indent: 0; padding: 4px 8px; }
+#log .turn.Gray { background-color: #b7b2b0; }
+#log .turn.Blue { background-color: #95b4ca; }
+#log .turn.Tan { background-color: #e7cea7; }
+#log .turn.Red { background-color: #d18e95; }
+#log .turn.Black { background-color: #7b7979; }
+#log .dc.unsuccessful { background-color: #856781; color: lavenderblush; }
+#log .dc.Afghan { background-color: #5bbc93; }
+#log .dc.British { background-color: #e2a6ca; }
+#log .dc.Russian { background-color: #fff69a; }
+#log .tip { color: blue; }
+#log .tip:hover { text-decoration: underline; cursor: pointer; }
+
+main {
+ position: relative;
+ scrollbar-width: auto;
+}
+
+.action {
+ cursor: pointer;
+}
+
+#tooltip {
+ pointer-events: none;
+ position: fixed;
+ z-index: 100;
+ right: 240px;
+ top: 60px;
+ box-shadow: 0 0 20px black;
+}
+
+aside.hide + #tooltip {
+ right: 30px
+}
+
+#tooltip.focus { display: none; }
+body.shift #tooltip.focus { display: block; }
+
+#deck_info {
+ position: absolute;
+ right: 8px;
+ bottom: 5px;
+ white-space: pre-line;
+ font-family: "Source Serif SmText", "Georgia", serif;
+ font-size: 12px;
+ font-style: italic;
+}
+
+#popup {
+ position: fixed;
+ user-select: none;
+ background-color: white;
+ left: 10px;
+ top: 100px;
+ box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.3);
+ z-index: 300;
+ min-width: 20ex;
+ white-space: nowrap;
+ display: none;
+ border: 1px solid black;
+}
+#popup div { padding: 3pt 8pt; color: gray; display: none; }
+#popup div.enabled { color: black; display: block; }
+#popup div.enabled:hover { background-color: black; color: white; }
+#popup div.always { display: block; }
+#popup #menu_label { border-bottom: 1px solid silver; }
+
+#banner {
+ cursor: pointer;
+ display: flex;
+ justify-content: space-between;
+ user-select: none;
+ border-bottom: 1px solid black;
+ background-color: #856781;
+ height: 40px;
+}
+
+#banner > div {
+ flex-grow: 1;
+}
+
+.icon {
+ background-repeat: no-repeat;
+ background-size: auto 25px;
+ background-position: center;
+ filter: drop-shadow(0 1px 2px rgba(0,0,0,0.3));
+}
+
+#favored_suit_banner.Political { background-image: url(icons/suit_political.svg) }
+#favored_suit_banner.Intelligence { background-image: url(icons/suit_intelligence.svg) }
+#favored_suit_banner.Economic { background-image: url(icons/suit_economic.svg) }
+#favored_suit_banner.Military { background-image: url(icons/suit_military.svg) }
+
+#map_banner { background-image: url(icons/treasure-map.svg) }
+#hand_banner { background-image: url(icons/hand.svg) }
+
+.role {
+ position: relative;
+}
+
+.role_name {
+ cursor: pointer;
+}
+
+.role_rupees_icon,
+.role_rupees_text,
+.role_loyalty_icon,
+.role_loyalty_text,
+.role_cylinders_icon,
+.role_cylinders_text {
+ width: 24px;
+ height: 24px;
+ position: absolute;
+ cursor: pointer;
+ top: 2px;
+ text-align: right;
+ background-repeat: no-repeat;
+}
+
+.role_rupees_text,
+.role_loyalty_text,
+.role_cylinders_text {
+ opacity: 0.8;
+}
+
+.role_loyalty_icon {
+ background-position: 0 -1px;
+ background-size: 24px 24px;
+ opacity: 0.4;
+}
+
+.role_cylinders_icon {
+ width: 16px;
+ background-position: 0 3px;
+ background-size: 16px 16px;
+ background-image: url(icons/cylinder.svg);
+ opacity: 0.3;
+}
+
+.role_rupees_icon {
+ width: 17px;
+ background-position: 0 3px;
+ background-size: 17px 17px;
+ background-image: url(icons/rupee.svg);
+ opacity: 0.4;
+}
+
+.role_loyalty_icon.Afghan { background-image: url(icons/eagle.svg) }
+.role_loyalty_icon.British { background-image: url(icons/lion.svg) }
+.role_loyalty_icon.Russian { background-image: url(icons/bear.svg) }
+
+.role_loyalty_icon { right: 5px; }
+.role_loyalty_text { right: 32px; }
+.role_cylinders_icon { right: 55px; }
+.role_cylinders_text { right: 74px; }
+.role_rupees_icon { right: 100px; }
+.role_rupees_text { right: 119px; }
+
+/* CARDS */
+
+.card {
+ position: relative;
+ display: block;
+ width: 186px;
+ height: 258px;
+ border-radius: 10px;
+ background-color: #fefcf0;
+ background-size: cover;
+ background-repeat: no-repeat;
+ border: 1px solid #4d452b;
+ box-shadow: 0 2px 3px rgba(0,0,0,0.5);
+}
+
+#tooltip.card {
+ width: 372px;
+ height: 516px;
+ border-radius: 20px;
+}
+
+.card.action {
+ box-shadow: 0 0 0 3px yellow;
+}
+
+.card .spyrow {
+ position: absolute;
+ display: flex;
+ flex-wrap: wrap;
+ width: 168px;
+ bottom: 108px;
+ right: 8px;
+ gap: 4px;
+ justify-content: end;
+}
+
+/* if discarded */
+#global_events .card.event,
+.hand .card.event {
+ height: 178px;
+ border-radius: 10px 10px 0 0;
+}
+
+/* if played */
+#global_events .card_109.event,
+#global_events .card_115.event,
+.player_court .card.event {
+ margin-top: 71px;
+ background-position: bottom;
+ height: 187px;
+ border-radius: 0 0 10px 10px;
+}
+
+#global_events .card_109.event,
+#global_events .card_115.event {
+ margin-top: 0;
+}
+
+.card .card_action {
+ display: none;
+ position: absolute;
+ width: 36px;
+ height: 37px;
+ border: 3px solid transparent;
+ top: 190px;
+ border-radius: 8px;
+}
+
+.card .card_action.action { display: block; }
+
+.card .card_action.n1 { left: 133px; }
+.card .card_action.n2 { left: 85px; }
+
+.card.passive .card_action.n1 { left: 128px; }
+
+.card.three .card_action.n1 { left: 119px; }
+.card.three .card_action.n2 { left: 72px; }
+.card.three .card_action.n3 { left: 25px; }
+
+.card.card_61 .card_action.n1 { left: 72px; }
+.card.card_64 .card_action.n1 { left: 72px; }
+.card.card_84 .card_action.n1 { left: 72px; }
+.card.card_69 .card_action.n1 { left: 96px; }
+.card.card_69 .card_action.n2 { left: 48px; }
+.card.card_56 .card_action.n1 { top: 192px; }
+.card.card_41 .card_action.n1 { top: 198px; }
+.card.card_21 .card_action.n1 { top: 194px; }
+
+.card.Political .card_action { border-color: #8d198f }
+.card.Intelligence .card_action { border-color: #3871c1 }
+.card.Economic .card_action { border-color: #cd700f }
+.card.Military .card_action { border-color: #bf1b2c }
+
+/*
+.card.Political .card_action { box-shadow: 0 0 3px #8d198f }
+.card.Intelligence .card_action { box-shadow: 0 0 3px #8d198f }
+.card.Economic .card_action { box-shadow: 0 0 3px #8d198f }
+.card.Military .card_action { box-shadow: 0 0 3px #8d198f }
+*/
+
+/* PIECES */
+
+#board div {
+ transition: top 500ms, left 500ms;
+}
+
+.coin {
+ position: absolute;
+ pointer-events: none;
+ width: 50px;
+ height: 50px;
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-image: url(pieces/rupee.svg);
+ color: ivory;
+ font-weight: bold;
+ font-size: 32px;
+ line-height: 50px;
+ text-align: center;
+ text-shadow: 0 0 8px black;
+ user-select: none;
+ filter: drop-shadow(0 2px 3px rgba(0,0,0,0.5));
+}
+
+.cylinder {
+ width: 30px;
+ height: 30px;
+ background-size: contain;
+ background-repeat: no-repeat;
+ filter: drop-shadow(0 2px 3px rgba(0,0,0,0.5));
+}
+
+#board .cylinder {
+ position: absolute;
+}
+
+.cylinder.p0 { background-image: url(pieces/cylinder_gray.svg) }
+.cylinder.p1 { background-image: url(pieces/cylinder_blue.svg) }
+.cylinder.p2 { background-image: url(pieces/cylinder_tan.svg) }
+.cylinder.p3 { background-image: url(pieces/cylinder_red.svg) }
+.cylinder.p4 { background-image: url(pieces/cylinder_black.svg) }
+
+.block {
+ position: absolute;
+ width: 35px;
+ height: 45px;
+ background-size: contain;
+ background-repeat: no-repeat;
+ filter: drop-shadow(0 2px 3px rgba(0,0,0,0.5));
+}
+
+.block.road {
+ position: absolute;
+ width: 50px;
+ height: 30px;
+}
+
+.block.Afghan { background-image: url(pieces/block_afghan.svg) }
+.block.British { background-image: url(pieces/block_british.svg) }
+.block.Russian { background-image: url(pieces/block_russian.svg) }
+.block.army.Afghan { background-image: url(pieces/army_afghan.svg) }
+.block.army.British { background-image: url(pieces/army_british.svg) }
+.block.army.Russian { background-image: url(pieces/army_russian.svg) }
+.block.road.Afghan { background-image: url(pieces/road_afghan.svg) }
+.block.road.British { background-image: url(pieces/road_british.svg) }
+.block.road.Russian { background-image: url(pieces/road_russian.svg) }
+
+.Afghan.block.action {
+ filter:
+ drop-shadow(0 -2px 0 turquoise)
+ drop-shadow(0 2px 0 turquoise)
+ drop-shadow(-2px 0 0 turquoise)
+ drop-shadow(2px 0 0 turquoise)
+}
+.British.block.action {
+ filter:
+ drop-shadow(0 -2px 0 hotpink)
+ drop-shadow(0 2px 0 hotpink)
+ drop-shadow(-2px 0 0 hotpink)
+ drop-shadow(2px 0 0 hotpink)
+}
+.Russian.block.action {
+ filter:
+ drop-shadow(0 -2px 0 orange)
+ drop-shadow(0 2px 0 orange)
+ drop-shadow(-2px 0 0 orange)
+ drop-shadow(2px 0 0 orange)
+}
+
+.cylinder.action {
+ 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)
+}
+
+.block.selected {
+ filter:
+ drop-shadow(0 -2px 0 deepskyblue)
+ drop-shadow(0 2px 0 deepskyblue)
+ drop-shadow(-2px 0 0 deepskyblue)
+ drop-shadow(2px 0 0 deepskyblue)
+}
+
+.cylinder.selected {
+ filter:
+ drop-shadow(0 -2px 0 deepskyblue)
+ drop-shadow(0 2px 0 deepskyblue)
+ drop-shadow(-2px 0 0 deepskyblue)
+ drop-shadow(2px 0 0 deepskyblue)
+}
+
+/* FAVORED SUIT MARKER AND SPACES */
+
+#favored_suit_marker {
+ position: absolute;
+ width: 32px;
+ height: 70px;
+ background-image: url(pieces/favored_suit_marker.svg);
+ background-size: contain;
+ background-repeat: no-repeat;
+ left: 42px;
+ filter: drop-shadow(0 2px 3px rgba(0,0,0,0.5));
+ transition: 500ms;
+ transform-origin: bottom center;
+}
+
+#favored_suit_marker.Political { top: 112px; }
+#favored_suit_marker.Intelligence { top: 200px; }
+#favored_suit_marker.Economic { top: 289px; }
+#favored_suit_marker.Military { top: 378px; }
+
+.suit {
+ position: absolute;
+ border: 3px solid transparent;
+ border-radius: 50%;
+ left: 31px;
+ width: 48px;
+ height: 48px;
+}
+
+#suit_political { top: 129px; }
+#suit_intelligence { top: 216px; }
+#suit_economic { top: 305px; }
+#suit_military { top: 394px; }
+
+#suit_political.action { border-color: orchid; }
+#suit_intelligence.action { border-color: deepskyblue; }
+#suit_economic.action { border-color: orange; }
+#suit_military.action { border-color: orangered; }
+
+/* MARKET BOARD */
+
+#market {
+ width: 1280px;
+ height: 630px;
+ background-color: #e7cea7;
+ background-repeat: no-repeat;
+ background-size: cover;
+ box-shadow: 0 2px 3px rgba(0,0,0,0.5);
+ margin: 0px auto 10px auto;
+}
+
+#market_a {
+ /* TRBL */
+ padding: 74px 0 0 26px ;
+ display: flex;
+ gap: 20px;
+}
+
+#market_b {
+ padding: 16px 0 0 26px ;
+ display: flex;
+ gap: 20px;
+}
+
+.market_slot {
+ position: relative;
+ width: 188px;
+ height: 260px;
+}
+
+#market .coin {
+ top: 70px;
+ left: -8px;
+}
+
+/* MAP BOARD */
+
+#board {
+ position: relative;
+ width: 1280px;
+ height: 630px;
+ box-shadow: 0 2px 3px rgba(0,0,0,0.5);
+ margin: 0px auto 0px auto;
+ background-color: ivory;
+ background-repeat: no-repeat;
+ background-size: cover;
+}
+
+#board svg {
+ position: absolute;
+}
+
+.border, .region {
+ fill: none;
+ stroke: none;
+}
+
+.region.action {
+ fill: transparent;
+ stroke: #66a2b4;
+ stroke-width: 5px;
+ opacity: 0.7;
+}
+
+.border.action {
+ fill: transparent;
+ stroke: #b88a40;
+ stroke-width: 5px;
+ opacity: 0.7;
+}
+
+.rule {
+ position: absolute;
+ background-repeat: no-repeat;
+ background-size: 100%;
+ background-position: center;
+ background-color: #bf935b;
+ border: 1px solid #4d452b;
+ border-radius: 50%;
+ width: 50px;
+ height: 50px;
+ box-shadow: 0 2px 3px rgba(0,0,0,0.5);
+ margin: 0 auto 8px auto;
+}
+
+.rule.Transcaspia { left:229px; top:127px; }
+.rule.Punjab { left:904px; top:281px; }
+.rule.Persia { left:181px; top:400px; }
+.rule.Kandahar { left:707px; top:412px; }
+.rule.Kabul { left:648px; top:138px; }
+.rule.Herat { left:431px; top:358px; }
+
+.rule.Persia { background-image: url(pieces/ruler_persia.svg) }
+.rule.Transcaspia { background-image: url(pieces/ruler_transcaspia.svg) }
+.rule.Herat { background-image: url(pieces/ruler_herat.svg) }
+.rule.Kabul { background-image: url(pieces/ruler_kabul.svg) }
+.rule.Kandahar { background-image: url(pieces/ruler_kandahar.svg) }
+.rule.Punjab { background-image: url(pieces/ruler_punjab.svg) }
+
+.rule.Gray { background-color: #b7b2b0; }
+.rule.Blue { background-color: #95b4ca; }
+.rule.Tan { background-color: #e7cea7; }
+.rule.Red { background-color: #d18e95; }
+.rule.Black { background-color: #7b7979; }
+
+/* PLAYER AREAS */
+
+#global_events {
+ box-sizing: border-box;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ align-items: center;
+ padding: 0 25px;
+ gap: 15px;
+ margin: 15px auto;
+ max-width: min(calc(100% - 30px), 1260px);
+}
+
+.hand {
+ display: flex;
+ flex-wrap: wrap;
+ box-sizing: border-box;
+ justify-content: start;
+ padding: 15px;
+ margin: 15px auto 0 auto;
+ gap: 15px;
+ min-height: 260px;
+ max-width: min(calc(100% - 20px), 1260px);
+}
+
+.hand.hide {
+ display: none;
+}
+
+.player_area {
+ box-sizing: border-box;
+ margin: 15px auto;
+ min-width: min(100%, 1280px);
+ width: fit-content;
+ max-width: 100%;
+}
+
+.player_court {
+ display: flex;
+ padding: 10px 15px;
+ min-height: 283px;
+ box-shadow: 0 2px 3px rgba(0,0,0,0.5);
+ justify-content: start;
+ flex-wrap: wrap;
+ gap: 15px;
+ background-repeat: no-repeat;
+ background-size: 100%;
+ background-position: center bottom;
+}
+
+#player_court_0 { background-color: #b7b2b0; background-image: url(backgrounds/mountains_gray.jpg) }
+#player_court_1 { background-color: #95b4ca; background-image: url(backgrounds/mountains_blue.jpg) }
+#player_court_2 { background-color: #e7cea7; background-image: url(backgrounds/mountains_tan.jpg) }
+#player_court_3 { background-color: #d18e95; background-image: url(backgrounds/mountains_red.jpg) }
+#player_court_4 { background-color: #7b7979; background-image: url(backgrounds/mountains_black.jpg) }
+
+#player_hand_0 { background-image: linear-gradient(transparent, #b7b2b080) }
+#player_hand_1 { background-image: linear-gradient(transparent, #95b4ca80) }
+#player_hand_2 { background-image: linear-gradient(transparent, #e7cea780) }
+#player_hand_3 { background-image: linear-gradient(transparent, #d18e9580) }
+#player_hand_4 { background-image: linear-gradient(transparent, #7b797980) }
+
+.player_pool {
+ display: inline-flex;
+ justify-content: center;
+ align-content: start;
+ flex-wrap: wrap;
+ gap: 7px;
+ width: 209px;
+}
+
+.player_dial {
+ position: relative;
+ width: 207px;
+ height: 207px;
+ border: 1px solid #4d452b;
+ border-radius: 50%;
+ background-size: 100%;
+ background-repeat: no-repeat;
+ box-shadow: 0px 2px 3px 1px rgba(0,0,0,0.5);
+}
+
+.player_dial .coin {
+ top: 19px;
+ right: 0px;
+}
+
+.player_hand_size {
+ position: absolute;
+ user-select: none;
+ top: 19px;
+ left: -3px;
+ width: 36px;
+ height: 50px;
+ background-repeat: no-repeat;
+ background-size: 100%;
+ background-color: #fefcf0;
+ background-image: url(icons/card_back.png);
+ color: ivory;
+ border: 1px solid #4d452b;
+ border-radius: 3px;
+ font-size: 32px;
+ line-height: 47px;
+ font-weight: bold;
+ text-align: center;
+ text-shadow: 0 0 8px black;
+ text-align: center;
+ box-shadow: 0 2px 3px rgba(0,0,0,0.5);
+}
+
+.player_dial .prize {
+ position: absolute;
+ user-select: none;
+ top: 94px;
+ left: 135px;
+ height: 26px;
+ font-family: "Source Serif";
+ font-size: 16px;
+ line-height: 25px;
+ color: black;
+ font-weight: bold;
+}
+
+.gift_2, .gift_4, .gift_6 {
+ position: absolute;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 42px;
+ height: 42px;
+ border: 3px solid transparent;
+ border-radius: 50%;
+}
+.gift_2.action, .gift_4.action, .gift_6.action {
+ border-color: white;
+}
+.gift_2 { top: 75px; left: 8px; }
+.gift_4 { top: 120px; left: 21px; }
+.gift_6 { top: 147px; left: 60px; }
+
+/* IMAGES */
+
+#market { background-image: url(market.1x.jpg) }
+#board { background-image: url(board_fg.svg), url(board_bg.1x.jpg) }
+
+.player_dial.p0 { background-image: url(dials/loyalty_gray.1x.opt.png) }
+.player_dial.p1 { background-image: url(dials/loyalty_blue.1x.opt.png) }
+.player_dial.p2 { background-image: url(dials/loyalty_tan.1x.opt.png) }
+.player_dial.p3 { background-image: url(dials/loyalty_red.1x.opt.png) }
+.player_dial.p4 { background-image: url(dials/loyalty_black.1x.opt.png) }
+.player_dial.Afghan.p0 { background-image: url(dials/loyalty_gray.1x.opt.png), url(dials/loyalty_afghan.1x.opt.png) }
+.player_dial.Afghan.p1 { background-image: url(dials/loyalty_blue.1x.opt.png), url(dials/loyalty_afghan.1x.opt.png) }
+.player_dial.Afghan.p2 { background-image: url(dials/loyalty_tan.1x.opt.png), url(dials/loyalty_afghan.1x.opt.png) }
+.player_dial.Afghan.p3 { background-image: url(dials/loyalty_red.1x.opt.png), url(dials/loyalty_afghan.1x.opt.png) }
+.player_dial.Afghan.p4 { background-image: url(dials/loyalty_black.1x.opt.png), url(dials/loyalty_afghan.1x.opt.png) }
+.player_dial.British.p0 { background-image: url(dials/loyalty_gray.1x.opt.png), url(dials/loyalty_british.1x.opt.png) }
+.player_dial.British.p1 { background-image: url(dials/loyalty_blue.1x.opt.png), url(dials/loyalty_british.1x.opt.png) }
+.player_dial.British.p2 { background-image: url(dials/loyalty_tan.1x.opt.png), url(dials/loyalty_british.1x.opt.png) }
+.player_dial.British.p3 { background-image: url(dials/loyalty_red.1x.opt.png), url(dials/loyalty_british.1x.opt.png) }
+.player_dial.British.p4 { background-image: url(dials/loyalty_black.1x.opt.png), url(dials/loyalty_british.1x.opt.png) }
+.player_dial.Russian.p0 { background-image: url(dials/loyalty_gray.1x.opt.png), url(dials/loyalty_russian.1x.opt.png) }
+.player_dial.Russian.p1 { background-image: url(dials/loyalty_blue.1x.opt.png), url(dials/loyalty_russian.1x.opt.png) }
+.player_dial.Russian.p2 { background-image: url(dials/loyalty_tan.1x.opt.png), url(dials/loyalty_russian.1x.opt.png) }
+.player_dial.Russian.p3 { background-image: url(dials/loyalty_red.1x.opt.png), url(dials/loyalty_russian.1x.opt.png) }
+.player_dial.Russian.p4 { background-image: url(dials/loyalty_black.1x.opt.png), url(dials/loyalty_russian.1x.opt.png) }
+
+@media (min-resolution: 97dpi) {
+ #market { background-image: url(market.2x.jpg) }
+ #board { background-image: url(board_fg.svg), url(board_bg.2x.jpg) }
+
+ .player_dial.p0 { background-image: url(dials/loyalty_gray.2x.opt.png) }
+ .player_dial.p1 { background-image: url(dials/loyalty_blue.2x.opt.png) }
+ .player_dial.p2 { background-image: url(dials/loyalty_tan.2x.opt.png) }
+ .player_dial.p3 { background-image: url(dials/loyalty_red.2x.opt.png) }
+ .player_dial.p4 { background-image: url(dials/loyalty_black.2x.opt.png) }
+ .player_dial.Afghan.p0 { background-image: url(dials/loyalty_gray.2x.opt.png), url(dials/loyalty_afghan.2x.opt.png) }
+ .player_dial.Afghan.p1 { background-image: url(dials/loyalty_blue.2x.opt.png), url(dials/loyalty_afghan.2x.opt.png) }
+ .player_dial.Afghan.p2 { background-image: url(dials/loyalty_tan.2x.opt.png), url(dials/loyalty_afghan.2x.opt.png) }
+ .player_dial.Afghan.p3 { background-image: url(dials/loyalty_red.2x.opt.png), url(dials/loyalty_afghan.2x.opt.png) }
+ .player_dial.Afghan.p4 { background-image: url(dials/loyalty_black.2x.opt.png), url(dials/loyalty_afghan.2x.opt.png) }
+ .player_dial.British.p0 { background-image: url(dials/loyalty_gray.2x.opt.png), url(dials/loyalty_british.2x.opt.png) }
+ .player_dial.British.p1 { background-image: url(dials/loyalty_blue.2x.opt.png), url(dials/loyalty_british.2x.opt.png) }
+ .player_dial.British.p2 { background-image: url(dials/loyalty_tan.2x.opt.pn.opt.png url(dials/loyalty_british.2x.opt.png) }
+ .player_dial.British.p3 { background-image: url(dials/loyalty_red.2x.opt.png), url(dials/loyalty_british.2x.opt.png) }
+ .player_dial.British.p4 { background-image: url(dials/loyalty_black.2x.opt.png), url(dials/loyalty_british.2x.opt.png) }
+ .player_dial.Russian.p0 { background-image: url(dials/loyalty_gray.2x.opt.png), url(dials/loyalty_russian.2x.opt.png) }
+ .player_dial.Russian.p1 { background-image: url(dials/loyalty_blue.2x.opt.png), url(dials/loyalty_russian.2x.opt.png) }
+ .player_dial.Russian.p2 { background-image: url(dials/loyalty_tan.2x.opt.png), url(dials/loyalty_russian.2x.opt.png) }
+ .player_dial.Russian.p3 { background-image: url(dials/loyalty_red.2x.opt.png), url(dials/loyalty_russian.2x.opt.png) }
+ .player_dial.Russian.p4 { background-image: url(dials/loyalty_black.2x.opt.png), url(dials/loyalty_russian.2x.opt.png) }
+}
+
+/* CARD IMAGES */
+
+.card_1{background-image:url(cards/card_1.jpg)}
+.card_2{background-image:url(cards/card_2.jpg)}
+.card_3{background-image:url(cards/card_3.jpg)}
+.card_4{background-image:url(cards/card_4.jpg)}
+.card_5{background-image:url(cards/card_5.jpg)}
+.card_6{background-image:url(cards/card_6.jpg)}
+.card_7{background-image:url(cards/card_7.jpg)}
+.card_8{background-image:url(cards/card_8.jpg)}
+.card_9{background-image:url(cards/card_9.jpg)}
+.card_10{background-image:url(cards/card_10.jpg)}
+.card_11{background-image:url(cards/card_11.jpg)}
+.card_12{background-image:url(cards/card_12.jpg)}
+.card_13{background-image:url(cards/card_13.jpg)}
+.card_14{background-image:url(cards/card_14.jpg)}
+.card_15{background-image:url(cards/card_15.jpg)}
+.card_16{background-image:url(cards/card_16.jpg)}
+.card_17{background-image:url(cards/card_17.jpg)}
+.card_18{background-image:url(cards/card_18.jpg)}
+.card_19{background-image:url(cards/card_19.jpg)}
+.card_20{background-image:url(cards/card_20.jpg)}
+.card_21{background-image:url(cards/card_21.jpg)}
+.card_22{background-image:url(cards/card_22.jpg)}
+.card_23{background-image:url(cards/card_23.jpg)}
+.card_24{background-image:url(cards/card_24.jpg)}
+.card_25{background-image:url(cards/card_25.jpg)}
+.card_26{background-image:url(cards/card_26.jpg)}
+.card_27{background-image:url(cards/card_27.jpg)}
+.card_28{background-image:url(cards/card_28.jpg)}
+.card_29{background-image:url(cards/card_29.jpg)}
+.card_30{background-image:url(cards/card_30.jpg)}
+.card_31{background-image:url(cards/card_31.jpg)}
+.card_32{background-image:url(cards/card_32.jpg)}
+.card_33{background-image:url(cards/card_33.jpg)}
+.card_34{background-image:url(cards/card_34.jpg)}
+.card_35{background-image:url(cards/card_35.jpg)}
+.card_36{background-image:url(cards/card_36.jpg)}
+.card_37{background-image:url(cards/card_37.jpg)}
+.card_38{background-image:url(cards/card_38.jpg)}
+.card_39{background-image:url(cards/card_39.jpg)}
+.card_40{background-image:url(cards/card_40.jpg)}
+.card_41{background-image:url(cards/card_41.jpg)}
+.card_42{background-image:url(cards/card_42.jpg)}
+.card_43{background-image:url(cards/card_43.jpg)}
+.card_44{background-image:url(cards/card_44.jpg)}
+.card_45{background-image:url(cards/card_45.jpg)}
+.card_46{background-image:url(cards/card_46.jpg)}
+.card_47{background-image:url(cards/card_47.jpg)}
+.card_48{background-image:url(cards/card_48.jpg)}
+.card_49{background-image:url(cards/card_49.jpg)}
+.card_50{background-image:url(cards/card_50.jpg)}
+.card_51{background-image:url(cards/card_51.jpg)}
+.card_52{background-image:url(cards/card_52.jpg)}
+.card_53{background-image:url(cards/card_53.jpg)}
+.card_54{background-image:url(cards/card_54.jpg)}
+.card_55{background-image:url(cards/card_55.jpg)}
+.card_56{background-image:url(cards/card_56.jpg)}
+.card_57{background-image:url(cards/card_57.jpg)}
+.card_58{background-image:url(cards/card_58.jpg)}
+.card_59{background-image:url(cards/card_59.jpg)}
+.card_60{background-image:url(cards/card_60.jpg)}
+.card_61{background-image:url(cards/card_61.jpg)}
+.card_62{background-image:url(cards/card_62.jpg)}
+.card_63{background-image:url(cards/card_63.jpg)}
+.card_64{background-image:url(cards/card_64.jpg)}
+.card_65{background-image:url(cards/card_65.jpg)}
+.card_66{background-image:url(cards/card_66.jpg)}
+.card_67{background-image:url(cards/card_67.jpg)}
+.card_68{background-image:url(cards/card_68.jpg)}
+.card_69{background-image:url(cards/card_69.jpg)}
+.card_70{background-image:url(cards/card_70.jpg)}
+.card_71{background-image:url(cards/card_71.jpg)}
+.card_72{background-image:url(cards/card_72.jpg)}
+.card_73{background-image:url(cards/card_73.jpg)}
+.card_74{background-image:url(cards/card_74.jpg)}
+.card_75{background-image:url(cards/card_75.jpg)}
+.card_76{background-image:url(cards/card_76.jpg)}
+.card_77{background-image:url(cards/card_77.jpg)}
+.card_78{background-image:url(cards/card_78.jpg)}
+.card_79{background-image:url(cards/card_79.jpg)}
+.card_80{background-image:url(cards/card_80.jpg)}
+.card_81{background-image:url(cards/card_81.jpg)}
+.card_82{background-image:url(cards/card_82.jpg)}
+.card_83{background-image:url(cards/card_83.jpg)}
+.card_84{background-image:url(cards/card_84.jpg)}
+.card_85{background-image:url(cards/card_85.jpg)}
+.card_86{background-image:url(cards/card_86.jpg)}
+.card_87{background-image:url(cards/card_87.jpg)}
+.card_88{background-image:url(cards/card_88.jpg)}
+.card_89{background-image:url(cards/card_89.jpg)}
+.card_90{background-image:url(cards/card_90.jpg)}
+.card_91{background-image:url(cards/card_91.jpg)}
+.card_92{background-image:url(cards/card_92.jpg)}
+.card_93{background-image:url(cards/card_93.jpg)}
+.card_94{background-image:url(cards/card_94.jpg)}
+.card_95{background-image:url(cards/card_95.jpg)}
+.card_96{background-image:url(cards/card_96.jpg)}
+.card_97{background-image:url(cards/card_97.jpg)}
+.card_98{background-image:url(cards/card_98.jpg)}
+.card_99{background-image:url(cards/card_99.jpg)}
+.card_100{background-image:url(cards/card_100.jpg)}
+.card_101{background-image:url(cards/card_101.jpg)}
+.card_102{background-image:url(cards/card_102.jpg)}
+.card_103{background-image:url(cards/card_103.jpg)}
+.card_104{background-image:url(cards/card_104.jpg)}
+.card_105{background-image:url(cards/card_105.jpg)}
+.card_106{background-image:url(cards/card_106.jpg)}
+.card_107{background-image:url(cards/card_107.jpg)}
+.card_108{background-image:url(cards/card_108.jpg)}
+.card_109{background-image:url(cards/card_109.jpg)}
+.card_110{background-image:url(cards/card_110.jpg)}
+.card_111{background-image:url(cards/card_111.jpg)}
+.card_112{background-image:url(cards/card_112.jpg)}
+.card_113{background-image:url(cards/card_113.jpg)}
+.card_114{background-image:url(cards/card_114.jpg)}
+.card_115{background-image:url(cards/card_115.jpg)}
+.card_116{background-image:url(cards/card_116.jpg)}
+.card_back{background-image:url(cards/card_back_116.jpg)}
+
+/* MOBILE PHONE LAYOUT */
+
+@media (max-width: 640px) {
+ #tooltip {
+ top: 75px;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ margin: auto;
+ }
+}
+
+@media (max-width: 400px) or (max-height: 590px) {
+ #tooltip.card {
+ width: 248px;
+ height: 344px;
+ border-radius: 13px;
+ }
+}
diff --git a/play.html b/play.html
new file mode 100644
index 0000000..452a3af
--- /dev/null
+++ b/play.html
@@ -0,0 +1,437 @@
+<!DOCTYPE html>
+<!-- vim:set nowrap: -->
+<html>
+<head>
+<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1">
+<meta charset="UTF-8">
+<title>PAX PAMIR</title>
+<link rel="icon" href="icons/eagle.svg">
+<link rel="stylesheet" href="/fonts/fonts.css">
+<link rel="stylesheet" href="/common/play.css">
+<link rel="stylesheet" href="play.css">
+<script defer src="/common/play.js"></script>
+<script defer src="cards.js"></script>
+<script defer src="play.js"></script>
+</head>
+<body>
+
+<div id="popup" onmouseleave="hide_popup_menu()">
+<div id="popup_label" class="always">Actions</div>
+<div id="menu_play_left" onclick="popup_action('play_left')">Play to left side</div>
+<div id="menu_play_right" onclick="popup_action('play_right')">Play to right side</div>
+</div>
+
+<header>
+ <div class="menu">
+ <div class="menu_title"><img src="/images/cog.svg"></div>
+ <div class="menu_popup">
+ <div class="menu_item" onclick="window.open('info/rules.html', '_blank')">Rules</div>
+ <div class="menu_item" onclick="window.open('info/pac.html', '_blank')">Player Aid</div>
+ <div class="menu_item" onclick="window.open('info/cards.html', '_blank')">Cards</div>
+ <div class="resign menu_separator"></div>
+ <div class="resign menu_item" onclick="confirm_resign()">Resign</div>
+ <div class="debug menu_separator"></div>
+ <div class="debug menu_item" onclick="send_save()">&#x1F41E; Save</div>
+ <div class="debug menu_item" onclick="send_restore()">&#x1F41E; Restore</div>
+ <div class="debug menu_separator"></div>
+ <div class="debug menu_item" onclick="send_restart('2P')">&#x26a0; Restart</div>
+ </div>
+ </div>
+ <div class="icon_button" onclick="toggle_log()"><img src="/images/scroll-quill.svg"></div>
+ <div id="prompt"></div>
+ <div id="actions"></div>
+</header>
+
+<aside>
+ <div id="roles">
+ <div id="banner" class="Political">
+ <div id="favored_suit_banner" class="icon" onclick="scroll_to_market()"></div>
+ <div id="map_banner" class="icon"onclick="scroll_to_map()"></div>
+ <div id="hand_banner" class="icon" onclick="toggle_open_hands()"></div>
+ </div>
+
+ <div class="role hide" onclick="scroll_to_player(0)" id="role_Gray">
+ <div class="role_name">Gray
+ <div class="role_rupees_icon" id="rupees_0_icon"></div>
+ <div class="role_rupees_text" id="rupees_0_text">15</div>
+ <div class="role_cylinders_icon" id="cylinders_0_icon"></div>
+ <div class="role_cylinders_text" id="cylinders_0_text">15</div>
+ <div class="role_loyalty_icon" id="loyalty_0_icon"></div>
+ <div class="role_loyalty_text" id="loyalty_0_text">15</div>
+ <div class="role_user">-</div>
+ </div>
+ </div>
+
+ <div class="role hide" onclick="scroll_to_player(1)" id="role_Blue">
+ <div class="role_name"><span>Blue</span>
+ <div class="role_rupees_icon" id="rupees_1_icon"></div>
+ <div class="role_rupees_text" id="rupees_1_text">15</div>
+ <div class="role_cylinders_icon" id="cylinders_1_icon"></div>
+ <div class="role_cylinders_text" id="cylinders_1_text">15</div>
+ <div class="role_loyalty_icon" id="loyalty_1_icon"></div>
+ <div class="role_loyalty_text" id="loyalty_1_text">15</div>
+ <div class="role_user">-</div>
+ </div>
+ </div>
+
+ <div class="role hide" onclick="scroll_to_player(2)" id="role_Tan">
+ <div class="role_name"><span>Tan</span>
+ <div class="role_rupees_icon" id="rupees_2_icon"></div>
+ <div class="role_rupees_text" id="rupees_2_text">15</div>
+ <div class="role_cylinders_icon" id="cylinders_2_icon"></div>
+ <div class="role_cylinders_text" id="cylinders_2_text">15</div>
+ <div class="role_loyalty_icon" id="loyalty_2_icon"></div>
+ <div class="role_loyalty_text" id="loyalty_2_text">15</div>
+ <div class="role_user">-</div>
+ </div>
+ </div>
+
+ <div class="role hide" onclick="scroll_to_player(3)" id="role_Red">
+ <div class="role_name"><span>Red</span>
+ <div class="role_rupees_icon" id="rupees_3_icon"></div>
+ <div class="role_rupees_text" id="rupees_3_text">15</div>
+ <div class="role_cylinders_icon" id="cylinders_3_icon"></div>
+ <div class="role_cylinders_text" id="cylinders_3_text">15</div>
+ <div class="role_loyalty_icon" id="loyalty_3_icon"></div>
+ <div class="role_loyalty_text" id="loyalty_3_text">15</div>
+ <div class="role_user">-</div>
+ </div>
+ </div>
+
+ <div class="role hide" onclick="scroll_to_player(4)" id="role_Black">
+ <div class="role_name"><span>Black</span>
+ <div class="role_rupees_icon" id="rupees_4_icon"></div>
+ <div class="role_rupees_text" id="rupees_4_text">15</div>
+ <div class="role_cylinders_icon" id="cylinders_4_icon"></div>
+ <div class="role_cylinders_text" id="cylinders_4_text">15</div>
+ <div class="role_loyalty_icon" id="loyalty_4_icon"></div>
+ <div class="role_loyalty_text" id="loyalty_4_text">15</div>
+ <div class="role_user">-</div>
+ </div>
+ </div>
+
+ </div>
+ <div id="log"></div>
+</aside>
+
+<div id="tooltip" class="hide"></div>
+
+<main>
+
+<div id="market">
+ <div id="market_a">
+ <div class="market_slot">
+ <div class="market_card" id="market_card_0_0"></div>
+ <div class="hide coin" id="market_coin_0_0"></div>
+ </div>
+ <div class="market_slot">
+ <div class="market_card" id="market_card_0_1"></div>
+ <div class="hide coin" id="market_coin_0_1"></div>
+ </div>
+ <div class="market_slot">
+ <div class="market_card" id="market_card_0_2"></div>
+ <div class="hide coin" id="market_coin_0_2"></div>
+ </div>
+ <div class="market_slot">
+ <div class="market_card" id="market_card_0_3"></div>
+ <div class="hide coin" id="market_coin_0_3"></div>
+ </div>
+ <div class="market_slot">
+ <div class="market_card" id="market_card_0_4"></div>
+ <div class="hide coin" id="market_coin_0_4"></div>
+ </div>
+ <div class="market_slot">
+ <div class="market_card" id="market_card_0_5"></div>
+ <div class="hide coin" id="market_coin_0_5"></div>
+ </div>
+ </div>
+ <div id="market_b">
+ <div class="market_slot">
+ <div class="market_card" id="market_card_1_0"></div>
+ <div class="hide coin" id="market_coin_1_0"></div>
+ </div>
+ <div class="market_slot">
+ <div class="market_card" id="market_card_1_1"></div>
+ <div class="hide coin" id="market_coin_1_1"></div>
+ </div>
+ <div class="market_slot">
+ <div class="market_card" id="market_card_1_2"></div>
+ <div class="hide coin" id="market_coin_1_2"></div>
+ </div>
+ <div class="market_slot">
+ <div class="market_card" id="market_card_1_3"></div>
+ <div class="hide coin" id="market_coin_1_3"></div>
+ </div>
+ <div class="market_slot">
+ <div class="market_card" id="market_card_1_4"></div>
+ <div class="hide coin" id="market_coin_1_4"></div>
+ </div>
+ <div class="market_slot">
+ <div class="market_card" id="market_card_1_5"></div>
+ <div class="hide coin" id="market_coin_1_5"></div>
+ </div>
+ </div>
+</div>
+
+<div id="board">
+<div id="favored_suit_marker" class="Political"></div>
+<div id="player_score_0" class="hide cylinder p0"></div>
+<div id="player_score_1" class="hide cylinder p1"></div>
+<div id="player_score_2" class="hide cylinder p2"></div>
+<div id="player_score_3" class="hide cylinder p3"></div>
+<div id="player_score_4" class="hide cylinder p4"></div>
+<div class="hide rule Persia"></div>
+<div class="hide rule Transcaspia"></div>
+<div class="hide rule Herat"></div>
+<div class="hide rule Kabul"></div>
+<div class="hide rule Kandahar"></div>
+<div class="hide rule Punjab"></div>
+
+<svg id="svgmap" width="1280px" height="630px" viewBox="0 0 1280 630">
+<g transform="translate(-1,632) scale(0.78125,-0.78125)">
+
+<path id="Persia" class="region"
+ d="M154 358 c-6 -1 -13 -2 -16 -3 l-5 -1 0 -112 0 -112 124 0 124 0 0 9
+ c0 8 -1 13 -5 39 -4 30 -6 70 -6 101 0 9 0 24 0 31 -1 13 -1 14 -4 15 -1
+ 1 -3 1 -5 1 -1 0 -3 1 -4 1 -2 1 -4 2 -6 2 -4 1 -10 3 -19 7 -4 1 -10 4
+ -13 5 -3 1 -7 2 -9 3 -2 1 -5 2 -7 2 -2 1 -6 1 -8 2 -9 2 -28 5 -50 8 -22
+ 3 -76 4 -91 2z"/>
+
+<path id="Transcaspia" class="region"
+ d="M133 550 l0 -130 9 1 c24 2 98 0 120 -3 4 -1 12 -2 19 -3 11 -1 16 -2
+ 27 -4 10 -2 14 -3 24 -6 9 -4 11 -4 16 -6 2 -1 6 -3 9 -3 2 -1 6 -3 9 -4
+ 6 -2 11 -4 18 -5 6 -1 6 -1 12 9 3 4 23 27 30 35 11 11 23 19 50 35 11 6
+ 14 8 28 19 7 7 14 13 17 15 5 4 26 15 28 15 2 0 9 3 10 4 1 1 2 5 3 8 1 4
+ 3 9 3 10 1 1 2 4 2 7 1 2 1 6 2 9 3 12 6 30 8 50 2 15 5 61 5 73 l0 4
+ -224 0 -225 0 0 -130z"/>
+
+<path id="Herat" class="region"
+ d="M568 453 c-2 -1 -10 -8 -17 -14 -7 -6 -15 -12 -16 -14 -7 -4 -12 -7
+ -24 -14 -16 -9 -19 -11 -23 -14 -1 -1 -5 -4 -8 -6 -2 -2 -8 -8 -13 -13 -4
+ -5 -10 -11 -12 -13 -1 -2 -3 -4 -3 -4 0 -1 -1 -3 -3 -5 -2 -2 -5 -6 -6 -8
+ -2 -2 -5 -6 -7 -9 l-4 -4 1 -15 c0 -8 0 -30 1 -49 0 -35 2 -70 5 -80 0 -2
+ 1 -5 1 -7 1 -3 1 -7 2 -9 1 -3 1 -14 2 -25 l0 -20 134 0 134 0 0 5 c1 4 2
+ 9 4 15 1 1 2 4 2 7 0 2 2 6 2 9 5 16 10 49 12 81 2 23 3 36 4 51 2 14 2
+ 33 1 38 -1 3 -2 6 -2 9 -2 7 -5 12 -10 16 -5 4 -11 7 -38 22 -14 7 -20 12
+ -30 20 -4 3 -8 7 -10 8 -2 1 -5 3 -7 4 -2 2 -5 4 -7 5 -2 1 -5 3 -7 5 -3
+ 2 -6 4 -8 6 -2 1 -6 4 -8 5 -2 2 -6 5 -7 6 -1 1 -4 2 -5 4 -1 1 -6 3 -10
+ 5 -8 5 -15 5 -20 2z"/>
+
+<path id="Kabul" class="region"
+ d="M644 661 c0 -19 -1 -42 -4 -63 -3 -28 -4 -39 -5 -42 0 -3 -1 -6 -1 -8
+ -1 -2 -1 -6 -2 -8 0 -2 -1 -7 -2 -9 0 -2 -1 -5 -2 -7 0 -1 -1 -3 -1 -4 0
+ -2 0 -4 -1 -5 0 -1 -1 -3 -1 -5 -1 -4 -1 -4 4 -7 2 -1 6 -3 7 -4 1 -1 8
+ -6 15 -11 7 -5 14 -10 15 -11 1 -1 4 -3 7 -5 2 -2 5 -4 7 -5 2 -1 6 -4 8
+ -6 2 -1 6 -4 7 -5 1 -1 4 -3 6 -4 2 -2 5 -5 7 -6 2 -1 5 -3 7 -5 1 -1 12
+ -7 23 -12 19 -11 24 -13 29 -17 1 -2 3 -2 14 -1 19 2 54 -1 80 -7 3 -1 7
+ -1 9 -2 3 0 5 -1 6 -1 1 -1 3 -2 5 -2 2 0 6 -2 9 -3 15 -6 21 -7 36 -3 9
+ 2 15 3 27 5 44 5 47 6 60 13 10 6 25 19 27 25 1 1 2 5 3 8 4 11 5 13 6 16
+ 1 2 2 5 2 7 0 1 2 6 3 10 2 3 4 8 4 10 0 1 2 5 3 8 1 3 3 7 3 10 1 2 4 8
+ 6 14 2 5 5 11 5 12 1 5 19 39 22 42 1 1 2 4 3 6 1 1 3 4 4 6 3 3 15 19 20
+ 25 7 10 17 30 22 46 2 10 3 11 4 18 l0 6 -253 0 -253 0 0 -19z"/>
+
+<path id="Kandahar" class="region"
+ d="M800 347 c1 -3 0 -53 -1 -62 -2 -15 -3 -27 -4 -44 -2 -34 -8 -81 -11
+ -85 0 -1 -1 -3 -1 -5 0 -3 -2 -7 -2 -10 -1 -3 -2 -7 -2 -9 l0 -2 147 0
+ c147 0 147 0 147 2 0 1 0 3 -1 4 0 1 -1 4 -2 6 -1 5 -5 16 -10 28 -1 2 -2
+ 7 -3 11 -1 3 -3 7 -4 9 0 2 -2 6 -3 10 -1 3 -2 7 -3 10 0 2 -1 6 -2 8 -1
+ 2 -2 6 -2 8 -1 1 -2 4 -2 6 -1 2 -2 6 -2 9 -3 9 -5 15 -6 19 0 2 -1 5 -2
+ 7 -3 8 -3 10 -4 13 -1 3 -2 6 -8 21 -2 4 -4 9 -5 12 -3 9 -9 22 -11 25 0
+ 2 -1 2 -3 1 -1 0 -12 -2 -24 -3 -12 -1 -22 -3 -23 -3 -1 -1 -3 -1 -5 -1
+ -1 0 -4 -1 -6 -1 -4 -2 -20 -4 -31 -4 -11 0 -29 3 -32 4 -1 1 -3 2 -4 2
+ -2 0 -6 2 -9 3 -6 3 -13 4 -27 7 -9 2 -36 6 -39 6 -1 0 -1 -1 0 -2z"/>
+
+<path id="Punjab" class="region"
+ d="M1216 676 c-1 -2 -2 -6 -2 -8 -1 -3 -1 -8 -2 -11 -1 -3 -1 -7 -2 -10
+ -1 -7 -3 -13 -4 -16 -1 -2 -4 -8 -6 -13 -6 -17 -20 -44 -27 -52 -2 -2 -5
+ -6 -8 -10 -3 -4 -6 -8 -7 -9 -1 -2 -3 -5 -5 -7 -2 -3 -4 -6 -5 -7 -1 -1
+ -2 -4 -4 -6 -2 -3 -10 -20 -12 -28 -1 -2 -3 -7 -4 -10 -2 -4 -3 -9 -4 -10
+ 0 -1 -2 -5 -3 -8 -1 -3 -3 -7 -3 -8 -1 -4 -3 -9 -5 -15 -2 -6 -4 -11 -5
+ -14 -3 -10 -8 -22 -12 -32 -5 -9 -7 -12 -14 -19 -9 -9 -17 -16 -19 -17 -1
+ 0 0 -3 2 -8 4 -8 7 -14 9 -21 1 -3 3 -8 5 -12 6 -16 7 -19 8 -21 1 -3 1
+ -5 4 -13 1 -2 2 -5 2 -7 0 -2 1 -4 2 -5 0 -1 1 -3 1 -5 0 -2 1 -5 2 -6 0
+ -2 1 -6 2 -8 1 -2 2 -6 2 -7 1 -2 2 -5 2 -7 0 -2 1 -5 2 -6 0 -2 2 -8 4
+ -13 8 -26 9 -31 11 -34 2 -7 4 -11 5 -15 0 -2 2 -4 2 -6 1 -2 2 -5 3 -7 0
+ -2 2 -6 3 -9 0 -4 2 -8 2 -9 1 -1 1 -3 1 -5 0 -1 1 -4 2 -7 l1 -5 61 0 61
+ 0 0 275 0 275 -23 0 -23 0 0 -4z"/>
+
+<path id="Persia/Transcaspia" class="border"
+ d="M148 416 c-4 -1 -9 -1 -11 -1 l-4 1 0 -29 0 -29 5 2 c19 6 87 5 135 -3
+ 13 -2 19 -3 24 -4 2 0 5 -1 8 -2 2 0 5 -1 7 -2 2 -1 6 -2 9 -3 3 -1 9 -4
+ 13 -5 20 -8 20 -8 26 -2 2 3 7 7 11 10 9 6 9 6 8 16 0 5 -1 11 -2 14 -1 4
+ -1 5 -4 5 -2 1 -6 2 -9 3 -3 1 -7 3 -9 4 -3 0 -7 2 -9 3 -5 2 -7 2 -16 6
+ -10 3 -14 4 -24 6 -11 2 -16 3 -27 4 -7 1 -15 2 -19 3 -21 3 -97 5 -112
+ 3z"/>
+
+<path id="Persia/Herat" class="border"
+ d="M391 330 c-2 -2 -7 -6 -11 -9 l-6 -6 1 -12 c1 -7 1 -17 0 -26 -1 -24 2
+ -66 6 -97 4 -27 5 -31 5 -40 l0 -10 27 0 26 0 0 19 c-1 10 -1 21 -2 24 -1
+ 2 -1 6 -2 9 0 2 -1 5 -1 7 -1 2 -2 13 -3 24 -2 16 -2 29 -3 60 0 21 0 41
+ -1 44 l0 6 -8 2 c-4 1 -10 4 -14 6 -4 1 -7 3 -8 3 0 0 -3 -2 -6 -4z"/>
+
+<path id="Transcaspia/Herat" class="border"
+ d="M536 503 c-9 -5 -12 -8 -28 -21 -14 -12 -17 -14 -28 -20 -35 -20 -42
+ -26 -67 -54 -12 -14 -12 -15 -11 -19 1 -2 2 -8 2 -13 2 -15 2 -16 11 -20
+ 4 -2 10 -5 14 -6 7 -1 8 -1 13 6 5 8 7 11 12 16 3 3 8 9 12 13 3 3 8 8 11
+ 10 2 2 6 5 7 6 4 3 7 5 23 14 12 7 17 10 24 14 1 2 9 8 17 14 7 7 16 14
+ 18 15 l5 2 0 14 1 14 -5 3 c-2 1 -8 5 -12 7 -5 3 -9 6 -9 7 -1 1 -1 2 -2
+ 2 0 0 -4 -2 -8 -4z"/>
+
+<path id="Transcaspia/Kabul" class="border"
+ d="M587 676 c0 -5 -3 -56 -5 -75 -2 -20 -5 -38 -8 -50 -1 -3 -1 -7 -2 -9
+ 0 -3 -1 -6 -2 -8 -1 -2 -2 -5 -3 -7 l-1 -4 7 -4 c4 -2 10 -6 12 -8 l5 -3
+ 9 3 c18 4 23 7 23 11 0 1 1 3 1 4 1 2 2 5 2 7 1 2 2 7 2 9 1 2 1 6 2 8 0
+ 2 1 5 1 8 1 3 2 14 5 42 3 21 4 43 4 62 l0 18 -27 0 -26 0 1 -4z"/>
+
+<path id="Herat/Kabul" class="border"
+ d="M630 492 c-3 -1 -14 -5 -25 -8 -4 -1 -7 -2 -8 -2 0 -1 0 -8 0 -15 0
+ -14 0 -14 2 -15 1 -1 5 -4 7 -5 2 -2 6 -5 8 -7 2 -1 5 -3 8 -5 6 -5 15
+ -11 22 -16 6 -4 10 -7 17 -12 10 -9 16 -13 31 -21 22 -12 31 -16 34 -19 1
+ -1 3 -2 4 -2 1 0 6 4 12 8 14 11 15 14 15 28 0 7 0 11 -1 12 -1 0 -11 6
+ -22 12 -11 5 -22 11 -23 12 -2 2 -5 4 -7 5 -2 1 -5 4 -7 6 -2 1 -5 3 -6 4
+ -1 1 -5 4 -7 5 -2 2 -6 5 -8 6 -2 1 -5 3 -7 5 -3 2 -6 4 -7 5 -1 1 -8 5
+ -14 10 -15 11 -15 11 -18 9z"/>
+
+<path id="Herat/Kandahar" class="border"
+ d="M760 352 c-4 -2 -9 -6 -12 -9 l-5 -5 0 -15 c0 -9 -1 -21 -2 -27 -1 -15
+ -2 -28 -4 -51 -2 -32 -7 -65 -12 -81 0 -3 -2 -7 -2 -9 0 -3 -1 -6 -2 -7
+ -2 -7 -3 -11 -3 -13 0 -1 -1 -3 -2 -3 -1 -2 2 -2 29 -2 l30 0 -1 3 c0 2 1
+ 6 2 10 0 3 2 7 2 10 0 2 1 4 1 5 3 4 9 51 11 85 1 16 2 28 4 43 1 4 1 15
+ 2 26 l0 19 -5 6 c-5 8 -6 8 -13 12 -3 2 -7 4 -8 6 -2 1 -3 2 -4 2 0 0 -3
+ -2 -6 -5z"/>
+
+<path id="Kabul/Kandahar" class="border"
+ d="M785 406 c-1 0 -2 -2 -2 -12 l-1 -12 3 -3 c2 -2 7 -5 11 -8 9 -6 11 -8
+ 14 -14 3 -5 4 -5 17 -7 9 -1 14 -2 23 -4 10 -2 13 -3 18 -5 3 -1 7 -3 9
+ -3 1 0 3 -1 4 -2 2 -1 9 -2 20 -3 11 -2 33 0 39 2 2 1 5 2 6 2 2 0 4 0 5
+ 1 1 0 10 2 20 3 14 1 18 2 19 3 0 2 6 15 8 20 1 2 2 5 3 7 1 4 1 4 -2 8
+ -2 3 -5 7 -6 9 -6 9 -3 9 -27 6 -12 -1 -22 -2 -23 -3 -1 0 -4 -1 -6 -1 -2
+ -1 -7 -1 -9 -2 -17 -4 -24 -3 -40 3 -3 1 -7 3 -9 3 -2 0 -4 1 -5 2 -1 0
+ -3 1 -6 1 -2 1 -6 1 -9 2 -24 5 -66 10 -74 7z"/>
+
+<path id="Kabul/Punjab" class="border"
+ d="M1154 678 c4 -8 -12 -53 -26 -72 -5 -5 -17 -22 -20 -25 -1 -2 -3 -5 -4
+ -6 -1 -2 -2 -5 -3 -6 -3 -3 -20 -36 -21 -40 0 -1 -3 -7 -5 -12 -2 -6 -5
+ -12 -6 -14 0 -3 -2 -7 -3 -10 -1 -3 -3 -7 -3 -8 0 -2 -2 -7 -4 -10 -1 -4
+ -3 -9 -3 -10 -1 -4 -2 -8 -8 -23 -4 -12 -5 -14 -9 -17 -5 -6 -16 -14 -21
+ -17 l-4 -2 3 -4 c3 -6 10 -14 13 -14 1 0 7 -2 13 -4 6 -2 15 -4 19 -5 l8
+ -1 8 9 c6 7 9 11 13 18 4 9 9 22 12 31 1 3 3 8 5 14 2 6 4 11 5 15 0 1 2
+ 5 3 8 1 3 3 7 3 8 1 3 4 12 8 21 3 8 12 28 13 29 1 0 2 2 3 4 1 2 4 6 5 8
+ 2 2 5 6 6 8 1 1 4 5 7 9 3 4 6 8 8 10 7 8 20 33 26 50 2 5 5 11 6 13 1 3
+ 3 9 4 16 2 11 3 16 4 21 0 3 1 7 2 8 l0 2 -29 0 c-29 0 -29 0 -28 -2z"/>
+
+<path id="Kandahar/Punjab" class="border"
+ d="M1025 359 c0 -1 -2 -7 -5 -13 -6 -14 -7 -17 -4 -23 1 -2 2 -6 3 -8 1
+ -3 3 -8 5 -12 6 -15 7 -18 8 -21 1 -3 1 -5 4 -13 1 -2 2 -5 2 -7 1 -3 2
+ -9 6 -19 0 -3 1 -7 2 -9 0 -2 1 -5 2 -6 0 -2 1 -6 2 -8 1 -2 2 -6 2 -8 1
+ -3 2 -7 3 -10 1 -4 3 -8 3 -10 1 -2 3 -7 4 -11 2 -5 4 -10 4 -12 5 -12 7
+ -19 8 -22 2 -7 3 -11 3 -14 l0 -3 29 0 28 0 0 3 c-1 1 -1 5 -2 7 0 2 -2 7
+ -2 10 -1 3 -2 7 -3 9 -1 4 -3 9 -5 14 -1 2 -2 5 -2 5 0 1 -1 3 -1 5 -3 7
+ -4 9 -14 42 -2 5 -4 11 -4 13 -1 1 -2 4 -2 6 0 2 -1 4 -1 5 -1 1 -2 3 -2
+ 5 -2 8 -3 12 -4 15 -3 8 -3 10 -4 13 0 2 -1 5 -2 7 -3 8 -3 10 -4 13 -1 2
+ -2 5 -8 21 -2 4 -4 9 -4 11 -1 2 -3 6 -4 10 -2 3 -3 6 -3 7 -1 1 -3 2 -7
+ 3 -3 0 -11 2 -17 4 -13 4 -13 4 -14 1z"/>
+
+</g>
+</svg>
+
+<div id="suit_political" class="suit"></div>
+<div id="suit_intelligence" class="suit"></div>
+<div id="suit_economic" class="suit"></div>
+<div id="suit_military" class="suit"></div>
+
+<div id="deck_info"></div>
+
+</div>
+
+<div id="global_events"></div>
+
+<div id="player_area_list">
+
+<div class="hide player_area" id="player_area_0">
+<div class="hide hand" id="player_hand_0"></div>
+<div class="player_court" id="player_court_0">
+<div class="player_pool" id="player_pool_0">
+<div class="player_dial p0" id="player_dial_0">
+<div class="player_hand_size" id="player_hand_size_0"></div>
+<div class="coin" id="player_coin_0">3</div>
+<div class="prize" id="player_prize_0"></div>
+<div class="gift_2" id="player_gift_0_2"></div>
+<div class="gift_4" id="player_gift_0_4"></div>
+<div class="gift_6" id="player_gift_0_6"></div>
+</div>
+</div>
+</div>
+</div>
+
+<div class="hide player_area" id="player_area_1">
+<div class="hide hand" id="player_hand_1"></div>
+<div class="player_court" id="player_court_1">
+<div class="player_pool" id="player_pool_1">
+<div class="player_dial p1" id="player_dial_1">
+<div class="player_hand_size" id="player_hand_size_1"></div>
+<div class="coin" id="player_coin_1"></div>
+<div class="prize" id="player_prize_1"></div>
+<div class="gift_2" id="player_gift_1_2"></div>
+<div class="gift_4" id="player_gift_1_4"></div>
+<div class="gift_6" id="player_gift_1_6"></div>
+</div>
+</div>
+</div>
+</div>
+
+<div class="hide player_area" id="player_area_2">
+<div class="hide hand" id="player_hand_2"></div>
+<div class="player_court" id="player_court_2">
+<div class="player_pool" id="player_pool_2">
+<div class="player_dial p2" id="player_dial_2">
+<div class="player_hand_size" id="player_hand_size_2"></div>
+<div class="coin" id="player_coin_2"></div>
+<div class="prize" id="player_prize_2"></div>
+<div class="gift_2" id="player_gift_2_2"></div>
+<div class="gift_4" id="player_gift_2_4"></div>
+<div class="gift_6" id="player_gift_2_6"></div>
+</div>
+</div>
+</div>
+</div>
+
+<div class="hide player_area" id="player_area_3">
+<div class="hide hand" id="player_hand_3"></div>
+<div class="player_court" id="player_court_3">
+<div class="player_pool" id="player_pool_3">
+<div class="player_dial p3" id="player_dial_3">
+<div class="player_hand_size" id="player_hand_size_3"></div>
+<div class="coin" id="player_coin_3"></div>
+<div class="prize" id="player_prize_3"></div>
+<div class="gift_2" id="player_gift_3_2"></div>
+<div class="gift_4" id="player_gift_3_4"></div>
+<div class="gift_6" id="player_gift_3_6"></div>
+</div>
+</div>
+</div>
+</div>
+
+<div class="hide player_area" id="player_area_4">
+<div class="hide hand" id="player_hand_4"></div>
+<div class="player_court" id="player_court_4">
+<div class="player_pool" id="player_pool_4">
+<div class="player_dial p4" id="player_dial_4">
+<div class="player_hand_size" id="player_hand_size_4"></div>
+<div class="coin" id="player_coin_4"></div>
+<div class="prize" id="player_prize_4"></div>
+<div class="gift_2" id="player_gift_4_2"></div>
+<div class="gift_4" id="player_gift_4_4"></div>
+<div class="gift_6" id="player_gift_4_6"></div>
+</div>
+</div>
+</div>
+</div>
+
+</div>
+
+</main>
+
+<footer id="status"></footer>
+
+</body>
diff --git a/play.js b/play.js
new file mode 100644
index 0000000..20e2a85
--- /dev/null
+++ b/play.js
@@ -0,0 +1,885 @@
+"use strict";
+
+// CONSTANTS
+
+const player_names = [ "Gray", "Blue", "Tan", "Red", "Black" ];
+const player_index = Object.fromEntries(Object.entries(player_names).map(([k,v])=>[v,k|0]));
+
+const Persia = 201;
+const Transcaspia = 202;
+const Herat = 203;
+const Kabul = 204;
+const Kandahar = 205;
+const Punjab = 206;
+
+const Persia_Transcaspia = 301;
+const Persia_Herat = 302;
+const Transcaspia_Herat = 303;
+const Transcaspia_Kabul = 304;
+const Herat_Kabul = 305;
+const Herat_Kandahar = 306;
+const Kabul_Kandahar = 307;
+const Kabul_Punjab = 308;
+const Kandahar_Punjab = 309;
+
+const Gift2 = 400;
+const Gift4 = 401;
+const Gift6 = 402;
+const Safe_House = 500;
+
+const region_index = {
+ "Persia": Persia,
+ "Transcaspia": Transcaspia,
+ "Herat": Herat,
+ "Kabul": Kabul,
+ "Kandahar": Kandahar,
+ "Punjab": Punjab,
+};
+
+const region_names = {
+ [Persia]: "Persia",
+ [Transcaspia]: "Transcaspia",
+ [Herat]: "Herat",
+ [Kabul]: "Kabul",
+ [Kandahar]: "Kandahar",
+ [Punjab]: "Punjab",
+};
+
+cards.forEach(card => {
+ if (card) {
+ card.region = region_index[card.region];
+ if (card.name === 'EVENT')
+ card.name = card.if_discarded + " / " + card.if_purchased;
+ }
+});
+
+const event_cards = {
+ new_tactics: 105,
+ koh_i_noor: 106,
+ courtly_manners: 107,
+ rumor: 108,
+ conflict_fatigue: 109,
+ nationalism: 110,
+ nation_building: 112,
+ pashtunwali_values: 115,
+ embarrassment_of_riches: 106,
+ disregard_for_customs: 107,
+};
+
+const VP_OFFSET = [
+ [-16, -16],
+ [-32, 0],
+ [0, 0],
+ [-16, 16],
+ [16, 16],
+];
+
+const VP_TRACK = [
+ [ 91, 43 ],
+ [ 183, 43 ],
+ [ 273, 43 ],
+ [ 363, 43 ],
+ [ 454, 43 ],
+ [ 545, 43 ],
+ [ 635, 43 ],
+ [ 726, 43 ],
+ [ 816, 43 ],
+ [ 906, 43 ],
+ [ 996, 43 ],
+ [ 1035, 78 ],
+ [ 1035, 169 ],
+ [ 1035, 259 ],
+ [ 1035, 350 ],
+ [ 1035, 441 ],
+ [ 1035, 531 ],
+ [ 996, 563 ],
+ [ 906, 563 ],
+ [ 816, 563 ],
+ [ 726, 563 ],
+ [ 635, 563 ],
+ [ 545, 563 ],
+ [ 454, 563 ],
+];
+
+// GAME STATE
+
+function player_cylinders(p) {
+ return 36 + p * 10;
+}
+
+function ruler_of_region(r) {
+ let ruler = -1;
+
+ let n_afghan = 0;
+ let n_british = 0;
+ let n_russian = 0;
+ for (let i = 0; i < 12; ++i) {
+ if (view.pieces[i] === r)
+ n_afghan ++;
+ if (view.pieces[i+12] === r)
+ n_british ++;
+ if (view.pieces[i+24] === r)
+ n_russian ++;
+ }
+
+ let max_ruling = Math.max(n_afghan, n_british, n_russian);
+
+ for (let p = 0; p < view.players.length; ++p) {
+ let n_tribes = 0;
+ let x = 36 + p * 10;
+ for (let i = x; i < x + 10; ++i)
+ if (view.pieces[i] === r)
+ n_tribes++;
+
+ let n_ruling = n_tribes;
+ if (view.players[p].loyalty === 'Afghan')
+ n_ruling += n_afghan;
+ if (view.players[p].loyalty === 'British')
+ n_ruling += n_british;
+ if (view.players[p].loyalty === 'Russian')
+ n_ruling += n_russian;
+
+ if (n_ruling === max_ruling) {
+ ruler = -1;
+ } else if (n_ruling > max_ruling) {
+ max_ruling = n_ruling;
+ if (n_tribes > 0)
+ ruler = p;
+ else
+ ruler = -1;
+ }
+ }
+
+ return ruler;
+}
+
+function count_influence_points(p) {
+ let n = 1 + view.players[p].prizes;
+ let x = player_cylinders(p);
+
+ if (!view.events.embarrassment_of_riches) {
+ let gv = view.players[p].events.koh_i_noor ? 2 : 1;
+ for (let i = x; i < x + 10; ++i) {
+ let s = view.pieces[i];
+ if (s === Gift2 || s === Gift4 || s === Gift6)
+ n += gv;
+ }
+ }
+
+ if (!view.players[p].events.rumor) {
+ let court = view.players[p].court;
+ for (let i = 0; i < court.length; ++i)
+ if (cards[court[i]].patriot)
+ ++n;
+ }
+
+ return n;
+}
+
+function count_cylinders_in_play(p) {
+ let n = 0;
+ let x = player_cylinders(p);
+ for (let i = x; i < x + 10; ++i)
+ if (view.pieces[i] > 0)
+ ++n;
+ return n;
+}
+
+function is_piece_army(i) {
+ return (view.pieces[i] >= 201 && view.pieces[i] <= 206);
+}
+
+function is_piece_road(i) {
+ return (view.pieces[i] >= 301 && view.pieces[i] <= 309);
+}
+
+function is_card_action(action, card) {
+ if (view.actions && view.actions[action] && view.actions[action].includes(card))
+ return true;
+ return false;
+}
+
+function is_place_gift_action(i) {
+ if (view.actions && view.actions.place_gift && view.actions.place_gift.includes(i))
+ return true;
+ return false;
+}
+
+function is_suit_action(suit) {
+ if (view.actions && view.actions.suit && view.actions.suit.includes(suit))
+ return true;
+ return false;
+}
+
+function is_piece_action(i) {
+ if (view.actions && view.actions.piece && view.actions.piece.includes(i))
+ return true;
+ return false;
+}
+
+function is_space_action(i) {
+ if (view.actions && view.actions.space && view.actions.space.includes(i))
+ return true;
+ return false;
+}
+
+// UI ELEMENTS
+
+let ui = {
+ pieces: [],
+ spaces: [],
+ cards: [],
+ spyrows: [],
+ market_card: [[],[]],
+ market_coin: [[],[]],
+ card_action_index: { battle: [], betray: [], build: [], gift: [], move: [], tax: [] },
+ card_action_element: { battle: [], betray: [], build: [], gift: [], move: [], tax: [] },
+ player: [],
+}
+
+function scroll_to_map() {
+ ui.board.scrollIntoView({behavior:'smooth'});
+}
+
+function scroll_to_market() {
+ ui.market.scrollIntoView({behavior:'smooth'});
+}
+
+function scroll_to_player(p) {
+ ui.player[p].area.scrollIntoView({behavior:'smooth'});
+}
+
+let open_toggle = true;
+function toggle_open_hands() {
+ open_toggle = !open_toggle;
+ for (let p = 0; p < view.players.length; ++p)
+ if (p !== player_index[player])
+ ui.player[p].hand.classList.toggle("hide", open_toggle);
+}
+
+function on_blur() {
+ ui.status.textContent = "";
+ ui.tooltip.classList = "hide";
+}
+
+function on_focus_card_tip(c) {
+ ui.tooltip.classList = "card card_" + c;
+}
+
+function on_click_card_tip(c) {
+ ui.cards[c].scrollIntoView({behavior:'smooth'});
+}
+
+function on_focus_card(evt) {
+ let c = evt.target.card;
+ if (!evt.target.classList.contains("card_back")) {
+ ui.status.textContent = `${evt.target.card} - ${cards[c].name}`;
+ ui.tooltip.classList = "focus card card_" + c;
+ }
+}
+
+function on_click_space(evt) {
+ send_action('space', evt.target.space);
+ evt.stopPropagation();
+}
+
+function on_click_block(evt) {
+ send_action('piece', evt.target.piece);
+ evt.stopPropagation();
+}
+
+function on_click_cylinder(evt) {
+ send_action('piece', evt.target.piece);
+ evt.stopPropagation();
+}
+
+function toggle_hand(p) {
+ ui.player[p].hand.classList.toggle("hide");
+}
+
+// CARD MENU
+
+const card_action_menu = [
+ 'play_left',
+ 'play_right',
+];
+
+let current_popup_card = 0;
+
+function show_popup_menu(evt, list) {
+ document.querySelectorAll("#popup div").forEach(e => e.classList.remove('enabled'));
+ for (let item of list) {
+ let e = document.getElementById("menu_" + item);
+ e.classList.add('enabled');
+ }
+ let popup = document.getElementById("popup");
+ popup.style.display = 'block';
+ popup.style.left = (evt.clientX-50) + "px";
+ popup.style.top = (evt.clientY-12) + "px";
+ ui.cards[current_popup_card].classList.add("selected");
+ ui.popup_label.textContent = cards[current_popup_card].name;
+}
+
+function hide_popup_menu() {
+ let popup = document.getElementById("popup");
+ popup.style.display = 'none';
+ if (current_popup_card) {
+ ui.cards[current_popup_card].classList.remove("selected");
+ current_popup_card = 0;
+ }
+}
+
+function popup_action(action) {
+ send_action(action, current_popup_card);
+ hide_popup_menu();
+}
+
+function on_click_card(evt) {
+ let c = evt.target.card;
+ if (is_card_action('card', c)) {
+ send_action('card', c);
+ } else {
+ let menu = card_action_menu.filter(a => is_card_action(a, c));
+ if (menu.length > 0) {
+ current_popup_card = c;
+ show_popup_menu(evt, menu);
+ }
+ }
+}
+
+// LOG
+
+function sub_card_name(match, p1) {
+ let c = p1 | 0;
+ let name = cards[c].name;
+ return `<span class="tip" onmouseenter="on_focus_card_tip(${c})" onmouseleave="on_blur()" onclick="on_click_card_tip(${c})">${name}</span>`;
+}
+
+function on_log(text) {
+ let p = document.createElement("div");
+ if (text.match(/^>/)) {
+ text = text.substring(1);
+ p.className = 'i';
+ }
+ text = text.replace(/&/g, "&amp;");
+ text = text.replace(/</g, "&lt;");
+ text = text.replace(/>/g, "&gt;");
+ text = text.replace(/#(\d+)/g, sub_card_name);
+ if (text.match(/^.turn/)) {
+ text = text.substring(6);
+ p.className = 'turn ' + text;
+ }
+ let m;
+ if ((m = text.match(/^.dc.(\w+) (.*)/))) {
+ text = m[2];
+ p.className = 'dc ' + m[1];
+ }
+ p.innerHTML = text;
+ return p;
+}
+
+// LAYOUT
+
+function layout_block_pool() {
+ function place_block_pool(i, x, y) {
+ ui.pieces[i].style = `top:${27+y*48}px;left:${1070+26+x*(26+35)}px`;
+ }
+ for (let k = 0, i = 0; i < 12; ++i) {
+ if (view.pieces[i] === 0) {
+ place_block_pool(i, 0, k);
+ ++k;
+ }
+ }
+ for (let k = 0, i = 12; i < 24; ++i) {
+ if (view.pieces[i] === 0) {
+ place_block_pool(i, 1, k);
+ ++k;
+ }
+ }
+ for (let k = 0, i = 24; i < 36; ++i) {
+ if (view.pieces[i] === 0) {
+ place_block_pool(i, 2, k);
+ ++k;
+ }
+ }
+}
+
+function layout_armies(list, xc, yc, maxcol) {
+ function place_army(y, x, i) {
+ ui.pieces[i].style = `top:${yc+y*16+x*1}px;left:${xc+x*26-y*16}px`;
+ }
+ let ncol = Math.min(maxcol, list.length);
+ let nrow = Math.ceil(list.length / ncol);
+ let i = 0;
+ for (let row = 0; row < nrow; ++row)
+ for (let col = 0; col < ncol && i < list.length; ++col)
+ place_army(row-nrow+1, col - (ncol/2) - ((nrow-1)/4), list[i++]);
+}
+
+function layout_tribes(list, xc, yc, maxcol) {
+ function place_tribe(y, x, i) {
+ ui.pieces[i].style = `top:${yc+y*16+x*4}px;left:${xc+x*32-y*20}px`;
+ }
+ let ncol = Math.min(maxcol, Math.ceil(Math.sqrt(list.length)));
+ let nrow = Math.ceil(list.length / ncol);
+ let i = 0;
+ for (let row = 0; row < nrow; ++row)
+ for (let col = 0; col < ncol && i < list.length; ++col)
+ place_tribe(row, col - (ncol/2) + ((nrow-1)/4), list[i++]);
+}
+
+function layout_region(r, xc, yc, maxcol) {
+ let list = [];
+ for (let i = 0; i < 36; ++i)
+ if (view.pieces[i] === r)
+ list.push(i);
+ layout_armies(list, xc - 2, yc - 50, maxcol);
+
+ list = [];
+ for (let i = 36; i < view.pieces.length; ++i)
+ if (view.pieces[i] === r)
+ list.push(i);
+ layout_tribes(list, xc, yc + 20, maxcol);
+}
+
+function layout_border(r, xc, yc, line) {
+ xc -= 24;
+ yc -= 12;
+ function place_piece_border(i, k) {
+ let x, y;
+ switch (line) {
+ case 0: x = k * 18; y = k * 7; break;
+ case 1: x = k * 4; y = k * 16; break;
+ case 2: x = k * -4; y = k * 16; break;
+ case 3: x = k * -12; y = k * 14; break;
+ }
+ ui.pieces[i].style = `top:${yc+y}px;left:${xc+x}px`;
+ }
+ let n = 0;
+ for (let i = 0; i < view.pieces.length; ++i) {
+ if (view.pieces[i] === r)
+ ++n;
+ }
+ for (let k = (-(n-1)/2), i = 0; i < view.pieces.length; ++i) {
+ if (view.pieces[i] === r) {
+ place_piece_border(i, k);
+ ++k;
+ }
+ }
+}
+
+// UPDATE UI
+
+let once = true;
+
+function on_update() {
+ if (once) {
+ build_ui();
+ once = false;
+ }
+
+ function update_event_cards(node, events) {
+ for (let evt in events)
+ node.appendChild(ui.cards[event_cards[evt]]);
+ }
+
+ let ruler = [
+ ruler_of_region(Persia),
+ ruler_of_region(Transcaspia),
+ ruler_of_region(Herat),
+ ruler_of_region(Kabul),
+ ruler_of_region(Kandahar),
+ ruler_of_region(Punjab)
+ ];
+
+ ui.prompt.innerHTML = view.prompt.replace(/#(\d+)/g, sub_card_name);
+
+ ui.deck_info.textContent = `${view.cards[0]}x Draw Deck, ${view.cards[1]}x Dominance Check`;
+
+ action_button("loyalty_afghan", "Afghan");
+ action_button("loyalty_british", "British");
+ action_button("loyalty_russian", "Russian");
+
+ action_button("courtly_manners", "Courtly Manners");
+ action_button("beg", "Beg");
+ action_button("pay", "Pay");
+ action_button("waive", "Waive");
+ action_button("accept", "Accept");
+
+ for (let i = 0; i < 10; ++i)
+ action_button("offer_" + i, i);
+
+ action_button("refuse", "Refuse");
+
+ action_button("player_0", "Gray");
+ action_button("player_1", "Blue");
+ action_button("player_2", "Tan");
+ action_button("player_3", "Red");
+ action_button("player_4", "Black");
+
+ action_button("pass", "Pass");
+ action_button("next", "Next");
+ confirm_action_button("end_turn_pass", "End turn",
+ "Are you sure you want to END TURN while you still have actions?");
+ action_button("end_turn", "End turn");
+ action_button("undo", "Undo");
+
+ ui.favored1.className = view.favored;
+ ui.favored2.className = view.favored + " icon";
+
+ for (let row = 0; row < 2; ++row) {
+ for (let col = 0; col < 6; ++col) {
+ let ce = ui.cards[view.market_cards[row][col]];
+ if (ce)
+ ce.classList.remove("card_back");
+ let me = ui.market_card[row][col];
+ if (me.firstChild !== ce) {
+ if (me.firstChild)
+ me.removeChild(me.firstChild);
+ if (ce)
+ me.appendChild(ce);
+ }
+ let coins = view.market_coins[row][col];
+ if (coins > 0) {
+ ui.market_coin[row][col].textContent = coins;
+ ui.market_coin[row][col].className = "coin";
+ } else {
+ ui.market_coin[row][col].textContent = "";
+ ui.market_coin[row][col].className = "coin hide";
+ }
+ }
+ }
+
+ for (let i = 1; i < cards.length; ++i) {
+ ui.cards[i].classList.toggle('action', is_card_action('card', i));
+ }
+
+ for (let i = 201; i <= 206; ++i) {
+ ui.spaces[i].classList.toggle('action', is_space_action(i));
+ ui.spaces[i].classList.toggle('selected', view.where === i);
+ }
+ for (let i = 301; i <= 309; ++i)
+ ui.spaces[i].classList.toggle('action', is_space_action(i));
+
+ for (let i = 0; i < 36; ++i) {
+ ui.pieces[i].classList.toggle('action', is_piece_action(i));
+ ui.pieces[i].classList.toggle('selected', view.selected === i);
+ ui.pieces[i].classList.toggle('road', is_piece_road(i));
+ ui.pieces[i].classList.toggle('army', is_piece_army(i));
+ }
+
+ for (let p = 0; p < view.players.length; ++p) {
+ let pp = view.players[p];
+ let me = ui.player[p].court;
+ while (me.firstChild)
+ me.removeChild(me.firstChild);
+ me.appendChild(ui.player[p].pool);
+ update_event_cards(me, view.players[p].events);
+ for (let i = 0; i < pp.court.length; ++i) {
+ let ce = ui.cards[pp.court[i]];
+ me.appendChild(ce);
+ ce.classList.remove("card_back");
+ }
+
+ if (p == player_index[player]) {
+ ui.player[p].gift_2.classList.toggle('action', is_place_gift_action(2));
+ ui.player[p].gift_4.classList.toggle('action', is_place_gift_action(4));
+ ui.player[p].gift_6.classList.toggle('action', is_place_gift_action(6));
+ }
+
+ me = ui.global_events;
+ while (me.firstChild)
+ me.removeChild(me.firstChild);
+ update_event_cards(me, view.events);
+
+ me = ui.player[p].hand;
+ while (me.firstChild)
+ me.removeChild(me.firstChild);
+ if (p === player_index[player])
+ me.classList.remove("hide");
+ for (let i = 0; i < pp.hand.length; ++i) {
+ let ce = ui.cards[pp.hand[i]];
+ if (p !== player_index[player] && !view.open)
+ ce.classList.add("card_back");
+ else
+ ce.classList.remove("card_back");
+ me.appendChild(ce);
+ }
+
+ if (view.players[p].coins == 0) {
+ ui.player[p].coin.classList.add("hide");
+ } else {
+ ui.player[p].coin.classList.remove("hide");
+ ui.player[p].coin.textContent = view.players[p].coins;
+ }
+
+ if (view.players[p].prizes == 0) {
+ ui.player[p].prize.classList.add("hide");
+ } else {
+ ui.player[p].prize.classList.remove("hide");
+ if (view.players[p].prizes === 1)
+ ui.player[p].prize.textContent = view.players[p].prizes + " prize";
+ else
+ ui.player[p].prize.textContent = view.players[p].prizes + " prizes";
+ }
+
+ ui.player[p].dial.className = "player_dial " + view.players[p].loyalty + " p" + p;
+
+ ui.player[p].role_loy_icon.className = "role_loyalty_icon " + view.players[p].loyalty;
+ ui.player[p].role_loy_text.textContent = count_influence_points(p);
+ ui.player[p].role_cyl_text.textContent = count_cylinders_in_play(p);
+ ui.player[p].role_rup_text.textContent = view.players[p].coins;
+
+ ui.player[p].hand_size.textContent = view.players[p].hand.length;
+
+ ui.player[p].score.style.left = (VP_OFFSET[p][0] + VP_TRACK[view.players[p].vp][0]) + "px";
+ ui.player[p].score.style.top = (VP_OFFSET[p][1] + VP_TRACK[view.players[p].vp][1]) + "px";
+
+ for (let i = 0; i < 10; ++i) {
+ let x = 36 + p * 10 + i;
+ let s = view.pieces[x];
+ if (s === 0 || s === Safe_House)
+ ui.player[p].pool.appendChild(ui.pieces[x]);
+ else if (s === Gift2)
+ ui.player[p].gift_2.appendChild(ui.pieces[x]);
+ else if (s === Gift4)
+ ui.player[p].gift_4.appendChild(ui.pieces[x]);
+ else if (s === Gift6)
+ ui.player[p].gift_6.appendChild(ui.pieces[x]);
+ else if (s <= 100)
+ ui.spyrows[s].appendChild(ui.pieces[x]);
+ else
+ ui.board.appendChild(ui.pieces[x]);
+ ui.pieces[x].classList.toggle('action', is_piece_action(x));
+ ui.pieces[x].classList.toggle('selected', view.selected === x);
+ ui.pieces[x].style = "";
+ }
+ }
+
+ for (let i = 0; i < 6; ++i)
+ if (ruler[i] === -1)
+ ui.rule[i].classList = "hide";
+ else
+ ui.rule[i].classList = `rule ${region_names[i+Persia]} ${player_names[ruler[i]]}`;
+
+ ui.suit_political.classList.toggle('action', is_suit_action('Political'));
+ ui.suit_intelligence.classList.toggle('action', is_suit_action('Intelligence'));
+ ui.suit_economic.classList.toggle('action', is_suit_action('Economic'));
+ ui.suit_military.classList.toggle('action', is_suit_action('Military'));
+
+ layout_block_pool();
+
+ layout_region(Persia, 206-16, 426, 5);
+ layout_region(Transcaspia, 254, 152, 10);
+ layout_region(Herat, 456, 383, 6);
+ layout_region(Kabul, 673, 163, 12);
+ layout_region(Kandahar, 732-23, 437, 6);
+ layout_region(Punjab, 929, 306, 3);
+
+ layout_border(Persia_Transcaspia, 188, 320, 0);
+ layout_border(Persia_Herat, 313, 441, 1);
+ layout_border(Transcaspia_Herat, 371, 297, 3);
+ layout_border(Transcaspia_Kabul, 477, 164, 1);
+ layout_border(Herat_Kabul, 527, 297, 0);
+ layout_border(Herat_Kandahar, 598, 441, 2);
+ layout_border(Kabul_Kandahar, 699, 332, 0);
+ layout_border(Kabul_Punjab, 859, 211, 2);
+ layout_border(Kandahar_Punjab, 836, 438, 1);
+
+ for (let action in ui.card_action_index) {
+ for (let i = 0; i < ui.card_action_index[action].length; ++i) {
+ let c = ui.card_action_index[action][i];
+ let e = ui.card_action_element[action][i];
+ e.classList.toggle("action", is_card_action(action, c));
+ }
+ }
+}
+
+// BUILD UI
+
+function build_ui() {
+ let passive_cards = [1,3,5,15,17,21,24,41,42,43,51,54,56,66,68,70,72,78,83,91,97,99];
+
+ function build_player_ui(p) {
+ return {
+ role: document.getElementById("role_" + player_names[p]),
+ role_rup_text: document.getElementById("rupees_" + p + "_text"),
+ role_cyl_text: document.getElementById("cylinders_" + p + "_text"),
+ role_loy_text: document.getElementById("loyalty_" + p + "_text"),
+ role_loy_icon: document.getElementById("loyalty_" + p + "_icon"),
+ score: document.getElementById("player_score_" + p),
+ area: document.getElementById("player_area_" + p),
+ hand_size: document.getElementById("player_hand_size_" + p),
+ hand: document.getElementById("player_hand_" + p),
+ court: document.getElementById("player_court_" + p),
+ pool: document.getElementById("player_pool_" + p),
+ dial: document.getElementById("player_dial_" + p),
+ coin: document.getElementById("player_coin_" + p),
+ prize: document.getElementById("player_prize_" + p),
+ gift_2: document.getElementById("player_gift_" + p + "_2"),
+ gift_4: document.getElementById("player_gift_" + p + "_4"),
+ gift_6: document.getElementById("player_gift_" + p + "_6"),
+ }
+ }
+
+ function build_card_action(card, action, i, x) {
+ let e = document.createElement("div");
+ e.className = `card_action ${action} n${x}`;
+ e.addEventListener("click", () => send_action(action, i));
+ ui.card_action_index[action].push(i);
+ ui.card_action_element[action].push(e);
+ card.appendChild(e);
+ }
+
+ function build_space(i, n) {
+ ui.spaces[i] = document.getElementById("svgmap").getElementById(n);
+ ui.spaces[i].space = i;
+ ui.spaces[i].addEventListener("click", on_click_space);
+ }
+
+ for (let c = 1; c < cards.length; ++c) {
+ let e = document.createElement("div");
+ e.card = c;
+ if (c <= 100) {
+ let info = cards[c];
+ e.className = "card card_" + c + " " + info.suit;
+ let n = 0;
+ if (info.gift) ++n, build_card_action(e, 'gift', c, info.gift);
+ if (info.move) ++n, build_card_action(e, 'move', c, info.move);
+ if (info.betray) ++n, build_card_action(e, 'betray', c, info.betray);
+ if (info.battle) ++n, build_card_action(e, 'battle', c, info.battle);
+ if (info.build) ++n, build_card_action(e, 'build', c, info.build);
+ if (info.tax) ++n, build_card_action(e, 'tax', c, info.tax);
+ if (passive_cards.includes(c))
+ e.classList.add("passive");
+ if (n === 3)
+ e.classList.add("three");
+ } else {
+ e.className = "event card card_" + c;
+ }
+ e.addEventListener("click", on_click_card);
+ e.addEventListener("mouseenter", on_focus_card);
+ e.addEventListener("mouseleave", on_blur);
+ ui.cards[c] = e;
+ let ee = document.createElement("div");
+ ee.className = "spyrow";
+ e.appendChild(ee);
+ ui.spyrows[c] = ee;
+ }
+
+ for (let row = 0; row < 2; ++row) {
+ for (let col = 0; col < 6; ++col) {
+ ui.market_card[row][col] = document.getElementById("market_card_" + row + "_" + col);
+ ui.market_coin[row][col] = document.getElementById("market_coin_" + row + "_" + col);
+ }
+ }
+
+ for (let p = 0; p < 5; ++p) {
+ ui.player[p] = build_player_ui(p);
+
+ ui.player[p].hand_size.addEventListener("click",
+ () => toggle_hand(p));
+
+ for (let i = 0; i < 10; ++i) {
+ let x = 36 + p * 10 + i;
+ ui.pieces[x] = document.createElement("div");
+ ui.pieces[x].piece = x;
+ ui.pieces[x].className = "cylinder p" + p;
+ ui.pieces[x].addEventListener("click", on_click_cylinder);
+ ui.player[p].pool.appendChild(ui.pieces[x]);
+ }
+
+ ui.player[p].gift_2.addEventListener("click", () => send_action('place_gift', 2));
+ ui.player[p].gift_4.addEventListener("click", () => send_action('place_gift', 4));
+ ui.player[p].gift_6.addEventListener("click", () => send_action('place_gift', 6));
+ }
+
+ ui.rule = [
+ document.querySelector(`#board .rule.Persia`),
+ document.querySelector(`#board .rule.Transcaspia`),
+ document.querySelector(`#board .rule.Herat`),
+ document.querySelector(`#board .rule.Kabul`),
+ document.querySelector(`#board .rule.Kandahar`),
+ document.querySelector(`#board .rule.Punjab`),
+ ];
+
+ ui.prompt = document.getElementById("prompt");
+ ui.deck_info = document.getElementById("deck_info");
+ ui.board = document.getElementById("board");
+ ui.market = document.getElementById("market");
+ ui.status = document.getElementById("status");
+ ui.tooltip = document.getElementById("tooltip");
+ ui.favored1 = document.getElementById("favored_suit_marker");
+ ui.favored2 = document.getElementById("favored_suit_banner");
+ ui.popup_label = document.getElementById("popup_label");
+ ui.global_events = document.getElementById("global_events");
+
+ ui.suit_political = document.getElementById("suit_political");
+ ui.suit_intelligence = document.getElementById("suit_intelligence");
+ ui.suit_economic = document.getElementById("suit_economic");
+ ui.suit_military = document.getElementById("suit_military");
+
+ ui.suit_political.addEventListener("click", () => send_action('suit', 'Political'));
+ ui.suit_intelligence.addEventListener("click", () => send_action('suit', 'Intelligence'));
+ ui.suit_economic.addEventListener("click", () => send_action('suit', 'Economic'));
+ ui.suit_military.addEventListener("click", () => send_action('suit', 'Military'));
+
+ build_space(Transcaspia, "Transcaspia");
+ build_space(Kabul, "Kabul");
+ build_space(Punjab, "Punjab");
+ build_space(Persia, "Persia");
+ build_space(Herat, "Herat");
+ build_space(Kandahar, "Kandahar");
+ build_space(Persia_Transcaspia, "Persia/Transcaspia");
+ build_space(Persia_Herat, "Persia/Herat");
+ build_space(Transcaspia_Herat, "Transcaspia/Herat");
+ build_space(Transcaspia_Kabul, "Transcaspia/Kabul");
+ build_space(Herat_Kabul, "Herat/Kabul");
+ build_space(Herat_Kandahar, "Herat/Kandahar");
+ build_space(Kabul_Kandahar, "Kabul/Kandahar");
+ build_space(Kabul_Punjab, "Kabul/Punjab");
+ build_space(Kandahar_Punjab, "Kandahar/Punjab");
+
+ function make_block(p, faction) {
+ let div = document.createElement("div");
+ div.className = faction + " block";
+ div.piece = p;
+ div.addEventListener("click", on_click_block);
+ ui.board.appendChild(div);
+ return div;
+ }
+
+ for (let i = 0; i < 12; ++i) ui.pieces[i] = make_block(i, "Afghan");
+ for (let i = 12; i < 24; ++i) ui.pieces[i] = make_block(i, "British");
+ for (let i = 24; i < 36; ++i) ui.pieces[i] = make_block(i, "Russian");
+
+ // Sort player roles so active player is on top!
+ let top = player === 'Observer' ? 0 : player_index[player];
+ let alist = document.getElementById("player_area_list");
+ let rlist = document.getElementById("roles");
+ for (let p = top; p < view.players.length; ++p) {
+ alist.appendChild(ui.player[p].area);
+ rlist.appendChild(ui.player[p].role);
+ ui.player[p].area.classList.remove("hide");
+ ui.player[p].role.classList.remove("hide");
+ ui.player[p].score.classList.remove("hide");
+ }
+ for (let p = 0; p < top; ++p) {
+ alist.appendChild(ui.player[p].area);
+ rlist.appendChild(ui.player[p].role);
+ ui.player[p].area.classList.remove("hide");
+ ui.player[p].role.classList.remove("hide");
+ ui.player[p].score.classList.remove("hide");
+ }
+
+ if (player !== 'Observer')
+ ui.player[top].hand_size.classList.add("hide");
+}
+
+function debug() {
+ function rr(k,v) { return k === 'log' || k === 'players' || k === 'actions' ? undefined: v; }
+ console.log("VIEW", JSON.stringify(view, rr, 0));
+ console.log("ACTIONS", JSON.stringify(view.actions, rr, 0));
+ for (let i = 0; i < view.players.length; ++i)
+ console.log("PLAYER", i, JSON.stringify(view.players[i], rr, 0));
+}