summaryrefslogtreecommitdiff
path: root/rules.js
diff options
context:
space:
mode:
Diffstat (limited to 'rules.js')
-rw-r--r--rules.js263
1 files changed, 174 insertions, 89 deletions
diff --git a/rules.js b/rules.js
index f8d8d35..99bb69d 100644
--- a/rules.js
+++ b/rules.js
@@ -12,9 +12,6 @@
- move (no player shift)
*/
-// campaign
-// event
-
/* DATA */
const data = require("./data.js")
@@ -126,6 +123,14 @@ function card_name(c) {
return "\u201c" + CARDS[c].title + "\u201d"
}
+function space_name(s) {
+ return SPACES[s].name
+}
+
+function general_name(s) {
+ return GENERALS[s].name
+}
+
/* SETUP */
function setup_game(seed) {
@@ -158,6 +163,7 @@ function setup_game(seed) {
a_queue: 0,
b_queue: 0,
+ card: 0,
last_played: 0,
did_discard_event: 0,
deck: null,
@@ -285,7 +291,6 @@ function play_card(c, reason) {
if (CARDS[c].reshuffle === "if_played")
set_flag(F_RESHUFFLE)
set_delete(active_hand(), c)
- game.last_played = c
if (CARDS[c].once) {
log("Removed card " + c + ".")
set_add(game.removed, c)
@@ -300,7 +305,6 @@ function discard_card_from_hand(hand, c) {
}
function discard_card(c, reason) {
- game.last_played = c
discard_card_from_hand(active_hand(), c)
if (reason)
logp("discarded #" + c + " " + reason)
@@ -799,13 +803,6 @@ function capture_enemy_general(where) {
capture_british_general(where)
}
-function remove_benedict_arnold() {
- if (!is_general_at_location(ARNOLD, NOWHERE)) {
- log("Removed Arnold from the game!")
- set_general_location(ARNOLD, NOWHERE)
- }
-}
-
/* ARMIES */
function has_british_army(where) {
@@ -1476,7 +1473,7 @@ function end_strategy_card() {
states.end_strategy_card = {
prompt() {
- view.prompt = "Done."
+ view.prompt = "Card play done."
view.actions.next = 1
},
next() {
@@ -1486,6 +1483,8 @@ states.end_strategy_card = {
}
function next_strategy_card() {
+ game.card = 0
+
if (!has_flag(F_FRENCH_ALLIANCE_TRIGGERED) && game.french_alliance === 9) {
log("The French signed an alliance with the Americans!")
set_flag(F_FRENCH_ALLIANCE_TRIGGERED)
@@ -2178,13 +2177,14 @@ function disperse_and_overrun_after_move() {
let cu = game.move.carry_british + game.move.carry_american + game.move.carry_french
let to = game.move.to
if (cu > 0) {
- if (has_enemy_general(to))
+ let egen = has_enemy_general(to)
+ let ecu = count_enemy_cu(to)
+ if (egen && ecu === 0)
capture_enemy_general(to)
- if (cu >= 4 && count_enemy_cu(to) === 1 && !has_enemy_general(to))
+ if (cu >= 4 && ecu === 1 && !egen)
overrun(to)
- if (game.active === P_BRITAIN && game.congress === to && !has_enemy_cu(to)) {
+ if (game.active === P_BRITAIN && game.congress === to && ecu === 0)
disperse_continental_congress()
- }
}
}
@@ -2341,7 +2341,7 @@ function goto_intercept() {
states.intercept_who = {
prompt() {
- view.prompt = "Intercept " + game.move.who + " at " + game.move.to + "?"
+ view.prompt = "Intercept " + general_name(game.move.who) + " at " + space_name(game.move.to) + "?"
view.actions.pass = 1
gen_intercept()
},
@@ -2414,7 +2414,7 @@ function can_retreat_before_battle() {
return false
// can't retreat if attempted (successful or not) interception!
let g = find_american_or_french_general(game.move.to)
- if (g && !has_general_moved(g))
+ if (g !== NOBODY && !has_general_moved(g))
return true
return false
}
@@ -2532,6 +2532,41 @@ function end_retreat_before_battle() {
/* BATTLE CARDS */
+function is_trigger_remove_benedict_arnold(c) {
+ return (
+ game.active === P_BRITAIN &&
+ CARDS[c].event === "remove_benedict_arnold" &&
+ !is_general_at_location(ARNOLD, NOWHERE)
+ )
+}
+
+function remove_benedict_arnold() {
+ log("Removed Arnold from the game!")
+ set_general_location(ARNOLD, NOWHERE)
+}
+
+states.remove_benedict_arnold_attacker = {
+ prompt() {
+ view.prompt = "Remove Benedict Arnold from the game!"
+ gen_action_general(ARNOLD)
+ },
+ general(g) {
+ remove_benedict_arnold()
+ game.state = "play_attacker_battle_card_confirm"
+ }
+}
+
+states.remove_benedict_arnold_defender = {
+ prompt() {
+ view.prompt = "Remove Benedict Arnold from the game!"
+ gen_action_general(ARNOLD)
+ },
+ general(g) {
+ remove_benedict_arnold()
+ game.state = "play_defender_battle_card_confirm"
+ }
+}
+
function goto_play_attacker_battle_card() {
game.active = game.combat.attacker
game.state = "play_attacker_battle_card"
@@ -2550,15 +2585,16 @@ states.play_attacker_battle_card = {
push_undo()
play_card(c, "for +2 DRM")
if (game.active === P_BRITAIN) {
- if (CARDS[c].event === "remove_benedict_arnold")
- remove_benedict_arnold()
game.combat.b_draw_after_battle = true
game.combat.b_bonus += 2
} else {
game.combat.a_draw_after_battle = true
game.combat.a_bonus += 2
}
- game.state = "play_attacker_battle_card_confirm"
+ if (is_trigger_remove_benedict_arnold(c))
+ game.state = "remove_benedict_arnold_attacker"
+ else
+ game.state = "play_attacker_battle_card_confirm"
},
card_battle_discard(c) {
push_undo()
@@ -2613,15 +2649,16 @@ states.play_defender_battle_card = {
push_undo()
play_card(c, "for +2 DRM")
if (game.active === P_BRITAIN) {
- if (CARDS[c].event === "remove_benedict_arnold")
- remove_benedict_arnold()
game.combat.b_draw_after_battle = true
game.combat.b_bonus += 2
} else {
game.combat.a_draw_after_battle = true
game.combat.a_bonus += 2
}
- game.state = "play_defender_battle_card_confirm"
+ if (is_trigger_remove_benedict_arnold(c))
+ game.state = "remove_benedict_arnold_defender"
+ else
+ game.state = "play_defender_battle_card_confirm"
},
card_battle_discard(c) {
push_undo()
@@ -3014,13 +3051,14 @@ function gen_attacker_retreat() {
}
function goto_retreat_after_battle(victor) {
+ let from = game.move.to
if (victor === P_BRITAIN) {
- let who = find_american_or_french_general(game.move.to)
- if (who === NOBODY && count_american_and_french_cu(game.move.to) === 0)
+ let who = find_american_or_french_general(from)
+ if (who === NOBODY && count_american_and_french_cu(from) === 0)
return end_battle()
} else {
- let who = find_british_general(game.move.to)
- if (who === NOBODY && count_british_cu(game.move.to) === 0)
+ let who = find_british_general(from)
+ if (who === NOBODY && count_british_cu(from) === 0)
return end_battle()
}
game.active = ENEMY[victor]
@@ -3043,7 +3081,9 @@ states.retreat_after_battle = {
else
retreat_american_army(game.move.to, to)
- // TODO: disperse congress?
+ // TODO: disperse congress with end_battle?
+ if (game.active === P_BRITAIN && game.congress === to)
+ disperse_continental_congress()
if (has_enemy_general(to))
capture_enemy_general(to)
@@ -3105,7 +3145,6 @@ events.campaign = function (c, card) {
events.the_war_ends = function (c, card) {
logp("played #" + c)
log("The war will end in " + card.year)
- game.last_played = c
set_delete(active_hand(), c)
game.war_ends = c
end_strategy_card()
@@ -3660,18 +3699,40 @@ function goto_winter_attrition_phase() {
goto_american_winter_attrition()
}
+function has_american_winter_attrition(s) {
+ let n_american = count_american_cu(s)
+ let n_french = count_french_cu(s)
+
+ // EXCEPT: Single American CU in a space with American or French General.
+ if (n_american === 1 && n_french === 0 && has_american_or_french_general(s))
+ return false
+
+ // EXCEPT: Single French CU in a space with American or French General not at WQ.
+ if (n_american === 0 && n_french === 1 && has_american_or_french_general(s))
+ return false
+
+ // EXCEPT: Stack with up to 5 CU with Washington at WQ.
+ if (n_american + n_french <= 5 && is_general_at_location(WASHINGTON, s) && is_winter_quarter_space(s))
+ return false
+
+ // American (or mixed with French) suffer attrition regardless of location.
+ if (n_american > 0)
+ return true
+
+ // French suffer attrition only at WQ
+ if (n_french > 0 && !is_winter_quarter_space(s))
+ return true
+
+ return false
+}
+
function goto_american_winter_attrition() {
log("=a Winter Attrition")
game.active = P_AMERICA
game.attrition = []
- for (let space of all_spaces) {
- let wq = is_winter_quarter_space(space)
- let n_american = count_american_cu(space)
- let n_french = count_french_cu(space)
- let has_washington = is_general_at_location(WASHINGTON, space)
- if (n_american > 0 || (n_french > 0 && !wq))
- game.attrition.push(space)
- }
+ for (let s of all_spaces)
+ if (has_american_winter_attrition(s))
+ game.attrition.push(s)
if (game.attrition.length > 0)
game.state = "american_winter_attrition"
else
@@ -3708,11 +3769,14 @@ function end_british_winter_attrition() {
states.american_winter_attrition = {
prompt() {
- view.prompt = "Winter Attrition."
- for (let s of game.attrition)
- gen_action_space(s)
- if (game.attrition.length === 0)
+ if (game.attrition.length > 0) {
+ view.prompt = "Winter Attrition."
+ for (let s of game.attrition)
+ gen_action_space(s)
+ } else {
+ view.prompt = "Winter Attrition: Done."
view.actions.next = 1
+ }
},
space(s) {
let wq = is_winter_quarter_space(s)
@@ -3720,32 +3784,38 @@ states.american_winter_attrition = {
let n_french = count_french_cu(s)
let has_washington = is_general_at_location(WASHINGTON, s)
- if (n_american === 0 && n_french === 1 && !wq)
- apply_single_winter_attrition(remove_french_cu, s, has_american_or_french_general(s))
- if (n_american === 0 && n_french > 1 && !wq)
- apply_winter_attrition(remove_french_cu, s, n_french)
+ // single american
if (n_american === 1 && n_french === 0)
- apply_single_winter_attrition(remove_american_cu, s, has_american_or_french_general(s))
- if (n_american > 1 && n_french === 0) {
- let n = n_american
- if (has_washington && wq)
- n = Math.max(0, n - 5)
- apply_winter_attrition(remove_american_cu, s, n)
+ apply_single_winter_attrition(remove_american_cu, s)
+
+ // single french
+ else if (n_american === 0 && n_french === 1)
+ apply_single_winter_attrition(remove_french_cu, s)
+
+ // french stack
+ else if (n_american === 0 && n_french > 1)
+ apply_winter_attrition(remove_french_cu, s, n_french)
+
+ // american stack
+ else if (n_american > 0 && n_french === 0) {
+ if (wq && has_washington)
+ apply_winter_attrition(remove_american_cu, s, n_american - 5)
+ else
+ apply_winter_attrition(remove_american_cu, s, n_american)
}
- if (n_american > 0 && n_french > 0) {
- let n = n_american + n_french
- if (has_washington && wq)
- n = Math.max(0, n - 5)
- let half = Math.floor(n / 2)
+ // mixed stack
+ else if (n_american > 0 && n_french > 0) {
+ let n_total = n_american + n_french
+ if (wq && has_washington)
+ n_total = Math.max(0, n_total - 5)
- // TODO: let player choose (but why would he ever choose the french?)
+ // TODO: let player choose (but why would they ever choose French?)
+ let half = Math.floor(n_total / 2)
let lose_american = Math.min(half, n_american)
log("Lost " + lose_american + " American CU in " + s)
remove_american_cu(s, n_american)
half -= lose_american
- n_american -= lose_american
-
if (half > 0) {
log("Lost " + half + " French CU in " + s)
remove_french_cu(s, half)
@@ -3761,19 +3831,21 @@ states.american_winter_attrition = {
states.british_winter_attrition = {
prompt() {
- view.prompt = "Winter Attrition."
- for (let s of game.attrition)
- gen_action_space(s)
- if (game.attrition.length === 0)
+ if (game.attrition.length > 0) {
+ view.prompt = "Winter Attrition."
+ for (let s of game.attrition)
+ gen_action_space(s)
+ } else {
+ view.prompt = "Winter Attrition: Done."
view.actions.next = 1
+ }
},
space(s) {
- let wq = is_winter_quarter_space(s)
let n_british = count_british_cu(s)
- if (n_british === 1 && !wq)
- apply_single_winter_attrition(remove_british_cu, s, has_british_general(s))
- if (n_british > 1 && !wq)
+ if (n_british === 1)
+ apply_single_winter_attrition(remove_british_cu, s)
+ else if (n_british > 1)
apply_winter_attrition(remove_british_cu, s, n_british)
set_delete(game.attrition, s)
@@ -3960,22 +4032,7 @@ function has_british_place_pc_markers_segment() {
return false
}
-function gen_american_place_pc_markers_segment() {
- for (let space of all_spaces)
- if (has_american_army(space))
- if (has_no_pc(space) || has_british_pc(space))
- gen_action_space(space)
- if (!view.actions.space)
- view.actions.next = 1
-}
-
function gen_british_place_pc_markers_segment() {
- for (let space of all_spaces)
- if (has_british_army(space))
- if (has_no_pc(space) || has_american_pc(space))
- gen_action_space(space)
- if (!view.actions.space)
- view.actions.next = 1
}
function goto_place_pc_markers_segment() {
@@ -3992,8 +4049,22 @@ function goto_place_pc_markers_segment() {
states.place_american_pc_markers_segment = {
prompt() {
- view.prompt = "Place American PC markers."
- gen_american_place_pc_markers_segment()
+ let done = true
+ for (let space of all_spaces) {
+ if (has_american_army(space)) {
+ if (has_no_pc(space) || has_british_pc(space)) {
+ done = false
+ gen_action_space(space)
+ }
+ }
+ }
+ if (done) {
+ view.prompt = "Place American PC markers: Done."
+ view.actions.next = 1
+ } else {
+ view.prompt = "Place American PC markers."
+ view.actions.next = 0
+ }
},
space(s) {
if (has_no_pc(s))
@@ -4008,8 +4079,22 @@ states.place_american_pc_markers_segment = {
states.place_british_pc_markers_segment = {
prompt() {
- view.prompt = "Place British PC markers."
- gen_british_place_pc_markers_segment()
+ let done = true
+ for (let space of all_spaces) {
+ if (has_british_army(space)) {
+ if (has_no_pc(space) || has_american_pc(space)) {
+ done = false
+ gen_action_space(space)
+ }
+ }
+ }
+ if (done) {
+ view.prompt = "Place British PC markers: Done."
+ view.actions.next = 1
+ } else {
+ view.prompt = "Place British PC markers."
+ view.actions.next = 0
+ }
},
space(s) {
if (has_no_pc(s))
@@ -4339,7 +4424,7 @@ exports.view = function (state, current) {
b_cards: game.b_hand.length,
a_queue: game.a_queue,
b_queue: game.b_queue,
- last_played: game.last_played,
+ last_played: game.card ? game.card : game.did_discard_event,
move: game.move,
}