diff options
-rw-r--r-- | play.css | 14 | ||||
-rw-r--r-- | play.js | 63 | ||||
-rw-r--r-- | rules.js | 171 |
3 files changed, 136 insertions, 112 deletions
@@ -14,11 +14,14 @@ body.America header.your_turn { background-color: hsl(211, 50%, 75%) } #log { background-color: hsl(35, 53%, 89%); } #turn_info { background-color: hsl(35, 25%, 50%); } -#log .h { border-top: 1px solid black; border-bottom: 1px solid black; text-align: center; } +#log .h { border-top: 1px solid black; border-bottom: 1px solid black; text-align: center; margin: 6px 0; } #log .h.turn { background-color: hsl(35, 45%, 70%); } #log .h.britain { background-color: hsl(15, 90%, 80%) } #log .h.america { background-color: hsl(211, 50%, 80%) } +#log .tip { cursor: pointer; } +#log .tip:hover { text-decoration: underline; } + #tooltip { display: none; pointer-events: none; @@ -99,6 +102,11 @@ body.America header.your_turn { background-color: hsl(211, 50%, 75%) } box-shadow: 0 0 2px 1px #000c, inset 0 0 2px 1px #000c; } +.space.tip { + border-color: lime; + box-shadow: 0 0 2px 1px #000c, inset 0 0 2px 1px #000c; +} + .space.action.Canada { background-color: #e4795080 } .space.action.NY { background-color: #52954680 } .space.action.NH { background-color: #ded36380 } @@ -190,6 +198,10 @@ body.America header.your_turn { background-color: hsl(211, 50%, 75%) } z-index: 1; } +.general.tip { + box-shadow: 0 0 0 3px lime, 0 0 2px 4px black; +} + /* CARDS */ #last_played { @@ -699,6 +699,7 @@ function on_update() { action_button("landing_party", "Landing Party") + action_button("no_general", "No General") action_button("pickup_french_cu", "Take French CU") action_button("pickup_british_cu", "Take CU") action_button("pickup_american_cu", "Take CU") @@ -706,8 +707,6 @@ function on_update() { action_button("drop_british_cu", "Drop CU") action_button("drop_american_cu", "Drop CU") - action_button("no_general", "No General") - action_button("britain_first", "Britain") action_button("america_first", "America") action_button("surrender", "Surrender") @@ -787,6 +786,62 @@ function on_click_card(evt) { /* LOG */ +function sub_space(_match, p1) { + let x = p1 | 0 + let n = data.spaces[x].name + if (n === "Wilmington DE") + n = "Wilmington" + n = n.replaceAll(" ", "\xa0") + let co = data.spaces[x].colony + if (co) + n += "\xa0(" + data.colony_name[data.spaces[x].colony] + ")" + return `<span class="tip" onclick="on_click_space_tip(${x})" onmouseenter="on_focus_space_tip(${x})" onmouseleave="on_blur_space_tip(${x})">${n}</span>` +} + +function sub_general(_match, p1) { + let x = p1 | 0 + let n = data.generals[x].name + return `<span class="tip" onclick="on_click_general_tip(${x})" onmouseenter="on_focus_general_tip(${x})" onmouseleave="on_blur_general_tip(${x})">${n}</span>` +} + +function sub_card(_match, p1) { + let x = p1 | 0 + let n = data.cards[x].title + return `<i class="tip" onmouseenter="on_focus_card_tip(${x})" onmouseleave="on_blur_card_tip(${x})">${n}</i>` +} + +function on_click_space_tip(s) { + ui.spaces[s].scrollIntoView({ block: "center", inline: "center", behavior: "smooth" }) +} + +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_general_tip(s) { + ui.generals[s].scrollIntoView({ block: "center", inline: "center", behavior: "smooth" }) +} + +function on_focus_general_tip(s) { + ui.generals[s].classList.add("tip") +} + +function on_blur_general_tip(s) { + ui.generals[s].classList.remove("tip") +} + +function on_focus_card_tip(s) { + ui.last_played.className = "card shrink card_" + s +} + +function on_blur_card_tip(s) { + ui.last_played.className = "card shrink card_" + view.last_played +} + function on_log(text) { let p = document.createElement("div") text = text.replace(/&/g, "&") @@ -804,6 +859,10 @@ function on_log(text) { text = text.substring(3) } + text = text.replace(/C(\d+)/g, sub_card) + text = text.replace(/S(\d+)/g, sub_space) + text = text.replace(/G(\d+)/g, sub_general) + p.innerHTML = text return p } @@ -85,8 +85,6 @@ const SOUTH_OF_WINTER_ATTRITION_LINE = [ 11, 12, 13 ] const CAMPAIGN_CARDS = [ 67, 68, 69, 70 ] const DECLARATION_OF_INDEPENDENCE = 99 const BARON_VON_STEUBEN = 86 -// const WAR_ENDS_1779 = 71 -// const BENJAMIN_FRANKLIN = 101 const F_RESHUFFLE = 1 const F_REGULARS = 2 @@ -243,7 +241,7 @@ function create_deck() { } function reshuffle_deck() { - log("Reshuffled the deck.") + log("Reshuffled.") clear_flag(F_RESHUFFLE) // Reconstitute deck, minus removed cards, cards in hand, war_ends, and reinforcement cards. @@ -283,14 +281,14 @@ function active_hand() { function play_card(c, reason) { if (reason) - log(game.active[0] + " played #" + c + " " + reason) + log("Played C" + c + " " + reason + ".") else - log(game.active[0] + " played #" + c) + log("Played C" + c + " for Event.") if (CARDS[c].reshuffle === "if_played") set_flag(F_RESHUFFLE) set_delete(active_hand(), c) if (CARDS[c].once) { - log("Removed card " + c + ".") + log("Removed card.") set_add(game.removed, c) } } @@ -299,15 +297,14 @@ function discard_card_from_hand(hand, c) { set_delete(hand, c) if (CARDS[c].reshuffle === "if_discarded") set_flag(F_RESHUFFLE) - logp("discarded #" + c) } function discard_card(c, reason) { - discard_card_from_hand(active_hand(), c) if (reason) - logp("discarded #" + c + " " + reason) + log("Discarded C" + c + " " + reason + ".") else - logp("discarded #" + c) + log("Discarded C" + c + ".") + discard_card_from_hand(active_hand(), c) } function can_exchange_for_discard(c) { @@ -467,30 +464,24 @@ function is_adjacent_to_american_pc(a) { } function place_british_pc(space) { - logp("placed PC in " + space) + log("Placed PC at S" + space + ".") if (game.british_pc_space_list) set_delete(game.british_pc_space_list, space) set_space_pc(space, PC_BRITISH) } function place_american_pc(space) { - logp("placed PC in " + space) + log("Placed PC at S" + space + ".") set_space_pc(space, PC_AMERICAN) } function remove_pc(space) { - if (game.active === P_BRITAIN) - logp("removed PC in " + space) - else - logp("removed PC in " + space) + log("Removed PC at S" + space + ".") set_space_pc(space, PC_NONE) } function flip_pc(space) { - if (game.active === P_BRITAIN) - logp("flipped PC in " + space) - else - logp("flipped PC in " + space) + log("Flipped PC at S" + space + ".") if (has_british_pc(space)) set_space_pc(space, PC_AMERICAN) else @@ -797,19 +788,18 @@ function capture_washington() { game.french_alliance = 0 } -console.log("CAPTURE WASHINGTON!!!") game.washington_captured = 1 } function capture_british_general(where) { let g = find_british_general(where) - log(general_name(g) + " was captured!") + log(general_name(g) + " captured!") move_general(g, CAPTURED_GENERALS) } function capture_american_or_french_general(where) { let g = find_american_or_french_general(where) - log(general_name(g) + " was captured!") + log(general_name(g) + " captured!") if (g === WASHINGTON) capture_washington() else @@ -859,11 +849,12 @@ function place_british_reinforcements(who, count, where) { move_general(already_there, BRITISH_REINFORCEMENTS) } if (who !== NOBODY) { - logp("reinforced " + where + " with " + who) + log(`${count} CU and G${who} at S${where}.`) move_general(who, where) + } else { + log(`${count} CU at S${where}.`) } if (count > 0) { - logp("reinforced " + where + " with " + count + " CU") move_british_cu(BRITISH_REINFORCEMENTS, where, count) if (has_enemy_general(where)) capture_enemy_general(where) @@ -882,10 +873,11 @@ function place_american_reinforcements(who, count, where) { move_general(already_there, AMERICAN_REINFORCEMENTS) } if (who !== NOBODY) { - logp("reinforced " + where + " with " + who) + log(`${count} CU and G${who} at S${where}.`) move_general(who, where) + } else { + log(`${count} CU at S${where}.`) } - logp("reinforced " + where + " with " + count + " CU") place_american_cu(where, count) if (has_enemy_general(where)) capture_enemy_general(where) @@ -901,10 +893,11 @@ function place_french_reinforcements(who, where) { move_general(already_there, AMERICAN_REINFORCEMENTS) } if (who !== NOBODY) { - logp("reinforced " + where + " with " + who) + log(`${count} CU and G${who} at S${where}.`) move_general(who, where) + } else { + log(`${count} CU at S${where}.`) } - logp("reinforced " + where + " with the French CU") move_french_cu(FRENCH_REINFORCEMENTS, where, count_french_cu(FRENCH_REINFORCEMENTS)) move_french_cu(AMERICAN_REINFORCEMENTS, where, count_french_cu(AMERICAN_REINFORCEMENTS)) } @@ -999,7 +992,6 @@ function gen_place_american_pc_in_colony(list_of_colonies) { function goto_committees_of_correspondence() { log("=a Committes of Correspondence") - logbr() game.active = P_AMERICA game.state = "committees_of_correspondence" game.colonies = THE_13_COLONIES.slice() @@ -1034,9 +1026,7 @@ states.committees_of_correspondence = { } function goto_for_the_king() { - logbr() log("=b For the King") - logbr() game.active = P_BRITAIN game.state = "for_the_king" game.count = 3 @@ -1069,9 +1059,7 @@ states.for_the_king = { /* REINFORCEMENTS AND START OF STRATEGY PHASE */ function goto_start_year() { - logbr() log("=t Year " + game.year) - logbr() // Prisoner exchange // TODO: manual? @@ -1115,7 +1103,7 @@ function goto_start_year() { if (game.year === 1776) { log("Added C" + DECLARATION_OF_INDEPENDENCE + ".") log("Added C" + BARON_VON_STEUBEN + ".") - log("Shuffled deck.") + log("Shuffled.") game.deck.push(DECLARATION_OF_INDEPENDENCE) game.deck.push(BARON_VON_STEUBEN) shuffle(game.deck) @@ -1154,7 +1142,8 @@ states.british_declare_first = { card(c) { push_undo() clear_flag(F_CONGRESS_WAS_DISPERSED) - logp("went first by playing a campaign card") + log("Britain declared first player.") + log("=b Britain") game.card = c game.state = "strategy_phase_event" }, @@ -1175,11 +1164,13 @@ states.choose_first_player = { view.actions.britain_first = 1 }, america_first() { - logp("went first") + if (game.active !== P_AMERICA) + log("Britain chose America to play first.") goto_strategy_phase(P_AMERICA) }, britain_first() { - logp("went first") + if (game.active !== P_BRITAIN) + log("America chose Britain to play first.") goto_strategy_phase(P_BRITAIN) }, } @@ -1222,7 +1213,7 @@ states.strategy_phase = { push_undo() let d = game.did_discard_event set_add(active_hand(), d) - logp("picked up up #" + d) + log("Picked up up C" + d) game.state = "exchange" }, } @@ -1238,7 +1229,7 @@ states.exchange = { }, card(c) { game.did_discard_event = 0 - discard_card(c, "exchange") + discard_card(c, " in exchange") game.state = "strategy_phase" }, } @@ -1249,8 +1240,6 @@ states.strategy_phase_ops = { let c = game.card view.selected_card = game.card view.prompt = "Use " + card_name(c) + "." -// if (can_exchange_for_discard(c)) -// view.actions.exchange = 1 if (can_activate_general(c)) view.actions.activate = 1 if (can_play_reinforcements()) @@ -1290,15 +1279,6 @@ states.strategy_phase_ops = { game.a_queue += CARDS[c].count end_strategy_card() }, - exchange() { - let c = game.card - let d = game.did_discard_event - game.did_discard_event = 0 - discard_card(c, "exchange") - set_add(active_hand(), d) - logp("picked up up #" + d) - game.state = "strategy_phase" - }, } states.strategy_phase_event = { @@ -1346,12 +1326,6 @@ states.strategy_phase_event = { break } }, - campaign() { - let c = game.card - game.did_discard_event = 0 - clear_queue() - goto_campaign(c) - }, event() { let c = game.card game.did_discard_event = 0 @@ -1362,7 +1336,7 @@ states.strategy_phase_event = { let c = game.card game.did_discard_event = c clear_queue() - discard_card(c, "PC action") + discard_card(c, "for PC action") game.state = "discard_event_pc_action" }, } @@ -1622,11 +1596,12 @@ function goto_ops_reinforcements(c) { states.ops_british_reinforcements_who = { prompt() { - view.prompt = "Reinforcements: choose an available general." - view.prompt += " Carrying " + game.count + " British CU." + view.prompt = "Reinforcements: Choose an available general." + view.prompt += " " + game.count + " British CU." view.move = { from: BRITISH_REINFORCEMENTS, to: BRITISH_REINFORCEMENTS, who: NOBODY, carry_british: game.count } view.actions.no_general = 1 gen_british_reinforcements_who() + gen_british_reinforcements_cu() }, drop_british_cu() { push_undo() @@ -1650,11 +1625,12 @@ states.ops_british_reinforcements_who = { states.ops_british_reinforcements_where = { prompt() { - view.prompt = "Reinforcements: choose a port space." - view.prompt += " Carrying " + game.count + " British CU." + view.prompt = "Reinforcements: Choose a port space." + view.prompt += " " + game.count + " British CU." view.move = { from: BRITISH_REINFORCEMENTS, to: BRITISH_REINFORCEMENTS, who: game.who, carry_british: game.count } view.selected_general = game.who gen_british_reinforcements_where() + gen_british_reinforcements_cu() }, drop_british_cu() { push_undo() @@ -1675,7 +1651,7 @@ states.ops_british_reinforcements_where = { states.ops_american_reinforcements_who = { prompt() { - view.prompt = "Reinforcements: choose an available general." + view.prompt = "Reinforcements: Choose an available general." view.move = { from: AMERICAN_REINFORCEMENTS, to: AMERICAN_REINFORCEMENTS, who: NOBODY, carry_american: game.count } view.actions.no_general = 1 gen_american_reinforcements_who() @@ -1694,7 +1670,7 @@ states.ops_american_reinforcements_who = { states.ops_american_reinforcements_where = { prompt() { - view.prompt = "Reinforcements: choose a space." + view.prompt = "Reinforcements: Choose a space." view.move = { from: AMERICAN_REINFORCEMENTS, to: AMERICAN_REINFORCEMENTS, who: game.who, carry_american: game.count } view.selected_general = game.who gen_american_reinforcements_where(game.who) @@ -1709,16 +1685,23 @@ states.ops_american_reinforcements_where = { }, } +function gen_british_reinforcements_cu() { + if (game.count > 0) + view.actions.drop_british_cu = 1 + else + view.actions.drop_british_cu = 0 + if (game.count < count_british_cu(BRITISH_REINFORCEMENTS)) + view.actions.pickup_british_cu = 1 + else + view.actions.pickup_british_cu = 0 +} + function gen_british_reinforcements_who() { for (let g of BRITISH_GENERALS) { if (is_general_at_location(g, BRITISH_REINFORCEMENTS)) { gen_action_general(g) } } - if (game.count > 0) - gen_action("drop_british_cu") - if (game.count < count_british_cu(BRITISH_REINFORCEMENTS)) - gen_action("pickup_british_cu") } function gen_british_reinforcements_where() { @@ -1727,10 +1710,6 @@ function gen_british_reinforcements_where() { if (!has_american_or_french_cu(space) && !has_american_pc(space)) gen_action_space(space) } - if (game.count > 0) - gen_action("drop_british_cu") - if (game.count < count_british_cu(BRITISH_REINFORCEMENTS)) - gen_action("pickup_british_cu") } function gen_american_reinforcements_who() { @@ -2170,19 +2149,19 @@ function gen_carry_cu() { let where = location_of_general(game.move.who) if (game.active === P_BRITAIN) { if (game.move.carry_british > 0) - gen_action("drop_british_cu") + view.actions.drop_british_cu = 1 if (game.move.carry_british < 5 && game.move.carry_british < count_unmoved_british_cu(where)) - gen_action("pickup_british_cu") + view.actions.pickup_british_cu = 1 } else { let carry_total = game.move.carry_french + game.move.carry_american if (game.move.carry_french > 0) - gen_action("drop_french_cu") + view.actions.drop_french_cu = 1 if (game.move.carry_american > 0) - gen_action("drop_american_cu") + view.actions.drop_american_cu = 1 if (carry_total < 5 && game.move.carry_french < count_unmoved_french_cu(where)) - gen_action("pickup_french_cu") + view.actions.pickup_french_cu = 1 if (carry_total < 5 && game.move.carry_american < count_unmoved_american_cu(where)) - gen_action("pickup_american_cu") + view.actions.pickup_american_cu = 1 } } @@ -2517,7 +2496,7 @@ states.remove_benedict_arnold_attacker = { view.prompt = "Remove Benedict Arnold from the game!" gen_action_general(ARNOLD) }, - general(g) { + general(_) { remove_benedict_arnold() game.state = "play_attacker_battle_card_confirm" } @@ -2528,7 +2507,7 @@ states.remove_benedict_arnold_defender = { view.prompt = "Remove Benedict Arnold from the game!" gen_action_general(ARNOLD) }, - general(g) { + general(_) { remove_benedict_arnold() game.state = "play_defender_battle_card_confirm" } @@ -3089,7 +3068,7 @@ events.campaign = function (c, card) { } events.the_war_ends = function (c, card) { - logp("played #" + c) + log("Played C" + c) log("The war will end in " + card.year) set_delete(active_hand(), c) game.war_ends = c @@ -3111,6 +3090,7 @@ function remove_random_card(hand) { if (hand.length > 0) { let i = random(hand.length) let c = hand[i] + log("Discarded C" + c + ".") discard_card_from_hand(hand, c) if (CARDS[c].type === "mandatory-event") do_event(c) @@ -3900,11 +3880,9 @@ states.place_rochambeau = { prompt() { view.prompt = "Place Rochambeau in a port." view.move = { who: ROCHAMBEAU, from: FRENCH_REINFORCEMENTS, to: FRENCH_REINFORCEMENTS, carry_american: 0, carry_french: 5 } - let can_place = false for (let space of all_port_spaces) { if (!has_british_cu(space) && !has_british_pc(space)) { gen_action_space(space) - can_place = true } } }, @@ -4026,9 +4004,6 @@ function has_british_place_pc_markers_segment() { return false } -function gen_british_place_pc_markers_segment() { -} - function goto_place_pc_markers_segment() { if (has_american_place_pc_markers_segment()) { game.active = P_AMERICA @@ -4354,10 +4329,6 @@ function gen_action(action, argument) { set_add(view.actions[action], argument) } -function gen_action_sea(s) { - gen_action("sea", s) -} - function gen_action_space(s) { gen_action("space", s) } @@ -4457,7 +4428,7 @@ function logp(s) { game.log.push(game.active + " " + s) } -function logbr() { +function log_br() { if (game.log.length > 0 && game.log[game.log.length - 1] !== "") game.log.push("") } @@ -4503,14 +4474,6 @@ function random(range) { return (game.seed = game.seed * 200105 % 34359738337) % range } -function random_bigint(range) { - // Largest MLCG that will fit its state in a double. - // Uses BigInt for arithmetic, so is an order of magnitude slower. - // https://www.ams.org/journals/mcom/1999-68-225/S0025-5718-99-00996-5/S0025-5718-99-00996-5.pdf - // m = 2**53 - 111 - return (game.seed = Number(BigInt(game.seed) * 5667072534355537n % 9007199254740881n)) % range -} - function shuffle(list) { // Fisher-Yates shuffle for (let i = list.length - 1; i > 0; --i) { @@ -4521,16 +4484,6 @@ function shuffle(list) { } } -function shuffle_bigint(list) { - // Fisher-Yates shuffle - for (let i = list.length - 1; i > 0; --i) { - let j = random_bigint(i + 1) - let tmp = list[j] - list[j] = list[i] - list[i] = tmp - } -} - // Fast deep copy for objects without cycles function object_copy(original) { if (Array.isArray(original)) { |