summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--play.css14
-rw-r--r--play.js63
-rw-r--r--rules.js171
3 files changed, 136 insertions, 112 deletions
diff --git a/play.css b/play.css
index 0ea7f5a..a8837a0 100644
--- a/play.css
+++ b/play.css
@@ -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 {
diff --git a/play.js b/play.js
index ebb70b5..9917428 100644
--- a/play.js
+++ b/play.js
@@ -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, "&amp;")
@@ -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
}
diff --git a/rules.js b/rules.js
index 93daeaf..a1d9bbf 100644
--- a/rules.js
+++ b/rules.js
@@ -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)) {