From 3ac604faaa4fe2c6d60f69f27a0e9885525cf0a0 Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Fri, 21 Jun 2024 19:34:05 +0200 Subject: streamline ui --- play.js | 2 + rules.js | 1840 ++++++++++++++++++++++++++++++++------------------------------ 2 files changed, 939 insertions(+), 903 deletions(-) diff --git a/play.js b/play.js index d1f0ff4..8411ea8 100644 --- a/play.js +++ b/play.js @@ -548,6 +548,8 @@ function on_update() { action_button("britain_first", "Britain") action_button("america_first", "America") action_button("surrender", "Surrender") + action_button("stop", "Stop") + action_button("next", "Next") action_button("done", "Done") action_button("pass", "Pass") diff --git a/rules.js b/rules.js index c2bc472..f216bdf 100644 --- a/rules.js +++ b/rules.js @@ -1,8 +1,8 @@ "use strict" -// TODO: capture washington is messy -// TODO: campaign messed up who is who after battle -// TODO: retreat with 0 CU after battle +// TODO: capture washington +// TODO: campaign messed up who is who after battle ? +// TODO: retreat with 0 CU after battle ? /* washington's capture - winning general with no CU on enemy PC (no undo anyway) @@ -766,7 +766,6 @@ function capture_washington() { game.french_alliance = 0 } - // set flag to handle washington's capture! game.captured_washington = 1 } @@ -1378,6 +1377,7 @@ function end_strategy_card() { states.end_strategy_card = { prompt() { view.prompt = "Done." + view.actions.next = 1 }, next() { clear_undo() @@ -1430,6 +1430,7 @@ states.discard_event_pc_action = { gen_american_discard_event_pc_action() }, space(s) { + push_undo() if (game.active === P_BRITAIN) { if (has_no_pc(s)) place_british_pc(s) @@ -1448,6 +1449,7 @@ states.discard_event_pc_action = { end_strategy_card() }, pass() { + push_undo() end_strategy_card() }, } @@ -1513,15 +1515,21 @@ states.ops_pc = { else place_american_pc(s) } - --game.count + if (--game.count === 0) + end_ops_pc() }, pass() { - if (game.active === P_BRITAIN) - gen_british_pc_ops_end() - end_strategy_card() + push_undo() + end_ops_pc() }, } +function end_ops_pc() { + if (game.active === P_BRITAIN) + gen_british_pc_ops_end() + end_strategy_card() +} + function gen_british_pc_ops_start() { game.british_pc_space_list = [] for (let space of all_spaces) { @@ -1579,6 +1587,10 @@ function goto_ops_reinforcements(c) { } } +// TODO: british reinforcements: pick general +// TODO: british reinforcements: pick number of CU +// TODO: british reinforcements: pick destination + states.ops_british_reinforcements_who = { prompt() { view.prompt = "Reinforcements: choose an available general or pass to bring only CU." @@ -1620,8 +1632,9 @@ states.ops_british_reinforcements_where = { }, space(space) { place_british_reinforcements(game.who, game.count, space) - end_strategy_card() delete game.who + // capture george washington happens in end_strategy_card + end_strategy_card() }, } @@ -1655,8 +1668,8 @@ states.ops_american_reinforcements_where = { place_french_reinforcements(game.who, space) else place_american_reinforcements(game.who, game.count, space) - end_strategy_card() delete game.who + end_strategy_card() }, } @@ -1707,6 +1720,13 @@ function gen_american_reinforcements_where(general) { /* PLAY OPS CARD TO MOVE A GENERAL */ +// TODO: new procedure: +// 1) pick general +// 2) take CU +// 3) drop CU / take CU / move to space +// 4) confirm move if intercept/battle +// 5) goto battle/intercept/3 + function goto_ops_general(c) { play_card(c, "to activate a general") if (game.active === P_BRITAIN) { @@ -1896,34 +1916,33 @@ function goto_ops_general_move(g, marblehead) { states.ops_general_move = { prompt() { - view.prompt = "Move " + game.move.who + " with " + view.prompt = "Move " + data.generals[game.move.who].name + " with " if (game.move.carry_british > 0) { - view.prompt += game.move.carry_british + " British CU." + view.prompt += game.move.carry_british + " CU." } else if (game.move.carry_french + game.move.carry_american > 0) { if (game.move.carry_french > 0) { if (game.move.carry_american > 0) { view.prompt += game.move.carry_french + " French CU and " view.prompt += game.move.carry_american + " American CU." } else { - view.prompt += game.move.carry_french + " French CU." + view.prompt += game.move.carry_french + " CU." } } else { - view.prompt += game.move.carry_american + " American CU." + view.prompt += game.move.carry_american + " CU." } } else { view.prompt += game.move.carry_american + " no CU." } - if (game.count === 1) - view.prompt += " " + game.move.count + " move left." - else if (game.count > 1) - view.prompt += " " + game.move.count + " moves left." + view.prompt += " " + game.move.count + " MP left." // Cannot stop on enemy general if (!has_enemy_general(location_of_general(game.move.who))) - view.actions.pass = 1 + view.actions.stop = 1 - gen_carry_cu() - gen_move_general() + if (view.move.count > 0) { + gen_carry_cu() + gen_move_general() + } }, pickup_british_cu() { @@ -1962,11 +1981,11 @@ states.ops_general_move = { let from = location_of_general(game.move.who) let cu = game.move.carry_british + game.move.carry_american + game.move.carry_french - let intercept = false + let may_intercept = false if (game.active === P_BRITAIN) { let is_sea_move = path_type(from, to) === "sea" if (has_american_pc(to) && cu > 0 && !is_sea_move && !has_british_cu(to)) - intercept = can_intercept_to(to) + may_intercept = can_intercept_to(to) } game.move.from = from @@ -1974,10 +1993,10 @@ states.ops_general_move = { move_army(game.move.who, from, to) + // TODO: overrun after intercept? + // TODO: disperse continental congress after intercept? + if (cu > 0) { - if (has_enemy_general(to) && !has_enemy_cu(to)) { - capture_enemy_general(to) - } if (game.active === P_BRITAIN && game.congress === to && !has_enemy_cu(to)) { disperse_continental_congress() } @@ -1986,101 +2005,23 @@ states.ops_general_move = { } } - if (intercept) + if (may_intercept) goto_intercept() else resume_moving() }, - pass() { - clear_undo() - let where = location_of_general(game.move.who) + stop() { + push_undo() end_move() - if (count_friendly_generals(where) > 1) - goto_remove_general(where) - else - end_strategy_card() }, } function resume_moving() { - if (has_enemy_cu(game.move.to)) { + // TODO: if general moving alone was captured by intercepting army + if (has_enemy_cu(game.move.to)) goto_start_battle() - } -} - -function can_intercept_to(to) { - for (let space of SPACES[to].adjacent) { - if (has_american_army(space)) { - let g = find_american_or_french_general(space) - if (g && !has_general_moved(g)) - return true - } - } - return false -} - -function gen_intercept() { - for (let space of SPACES[game.move.to].adjacent) { - if (has_american_army(space)) { - let g = find_american_or_french_general(space) - if (g && !has_general_moved(g)) - gen_action_general(g) - } - } -} - -function goto_intercept() { - clear_undo() - game.active = P_AMERICA - game.state = "intercept" -} - -states.intercept = { - prompt() { - view.prompt = "Intercept " + game.move.who + " in " + game.move.to + "?" - view.actions.pass = 1 - gen_intercept() - }, - general(g) { - set_general_moved(g) - let die = roll_d6() - if (die <= GENERALS[g].agility) { - log(g + " intercepted (" + die + " <= " + GENERALS[g].agility + ")") - game.move.did_intercept = 1 - - game.intercept = { - who: g, - from: location_of_general(g), - to: game.move.to, - carry_british: 0, - carry_american: 0, - carry_french: 0, - } - - pickup_max_american_cu(game.intercept, location_of_general(g)) - intercept_army(g, location_of_general(g), game.move.to) - - if (count_friendly_generals(game.move.to) > 1) - goto_remove_general_after_intercept() - else - end_intercept() - } else { - log(g + " failed to intercept (" + die + " > " + GENERALS[g].agility + ")") - delete game.intercept - if (!can_intercept_to(game.move.to)) - end_intercept() - } - }, - pass() { - end_intercept() - }, -} - -function end_intercept() { - game.active = P_BRITAIN - game.state = "ops_general_move" - delete game.intercept - resume_moving() + else if (game.move.count === 0) + end_move() } function end_move() { @@ -2091,6 +2032,11 @@ function end_move() { mark_moved_french_cu(where, game.move.carry_french) } delete game.move + + if (count_friendly_generals(where) > 1) + goto_remove_general(where) + else + end_strategy_card() } function path_type(from, to) { @@ -2183,600 +2129,502 @@ function gen_move_general() { } } -/* CAMPAIGN */ - -function goto_campaign(c) { - play_card(c) - game.state = "campaign" - game.campaign = CARDS[c].count - if (game.active === P_BRITAIN) - set_flag(F_LANDING_PARTY) - else - clear_flag(F_LANDING_PARTY) - game.count = 3 // can activate any general! - game.state = "ops_general_who" -} - -/* EVENTS */ - -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() -} - -events.remove_random_british_card = function (c, card) { - play_card(c) - remove_random_card(game.b_hand) -} - -events.remove_random_american_card = function (c, card) { - play_card(c) - remove_random_card(game.a_hand) -} - -function remove_random_card(hand) { - if (hand.length > 0) { - let i = random(hand.length) - let c = hand[i] - discard_card_from_hand(hand, c) - if (CARDS[c].type === "mandatory-event") - do_event(c) - else - end_strategy_card() - } else - end_strategy_card() -} - -function advance_french_alliance(count) { - if (game.french_alliance < 9) { - game.french_alliance += count - if (game.french_alliance > 9) - game.french_alliance = 9 - log("French alliance advanced to " + count) - } -} +/* INTERCEPT */ -function lose_regular_advantage() { - if (has_flag(F_REGULARS)) { - log("British Regulars Advantage lost!") - clear_flag(F_REGULARS) - advance_french_alliance(2) +function can_intercept_to(to) { + for (let space of SPACES[to].adjacent) { + if (has_american_army(space)) { + let g = find_american_or_french_general(space) + if (g && !has_general_moved(g)) + return true + } } + return false } -events.baron_von_steuben_trains_the_continental_army = function (c, card) { - play_card(c) - if (is_general_on_map(WASHINGTON)) { - let where = location_of_general(WASHINGTON) - logp("placed 2 CU with Washington in " + where) - place_american_cu(where, 2) - lose_regular_advantage() +function gen_intercept() { + for (let space of SPACES[game.move.to].adjacent) { + if (has_american_army(space)) { + let g = find_american_or_french_general(space) + if (g && !has_general_moved(g)) + gen_action_general(g) + } } - end_strategy_card() -} - -events.advance_french_alliance = function (c, card) { - play_card(c) - advance_french_alliance(card.count) - end_strategy_card() -} - -events.remove_french_navy = function (c, card) { - play_card(c) - game.french_navy = game.year + 1 - end_strategy_card() } -events.remove_british_pc_from = function (c, card) { - play_card(c) - game.count = card.count - game.where = card.where - game.state = "remove_british_pc_from" +function goto_intercept() { + clear_undo() + game.active = P_AMERICA + game.state = "intercept" } -states.remove_british_pc_from = { +states.intercept = { prompt() { - view.prompt = "Remove British PC markers from " + game.where.join(", ") + ". " + game.count + " left." + view.prompt = "Intercept " + game.move.who + " at " + game.move.to + "?" view.actions.pass = 1 - gen_remove_british_pc_from(game.where) + gen_intercept() }, - space(where) { - remove_pc(where) - if (--game.count === 0) { - delete game.where - end_strategy_card() + general(g) { + set_general_moved(g) + let die = roll_d6() + if (die <= GENERALS[g].agility) { + log(g + " intercepted (" + die + " <= " + GENERALS[g].agility + ")") + game.move.did_intercept = 1 + + game.intercept = { + who: g, + from: location_of_general(g), + to: game.move.to, + carry_british: 0, + carry_american: 0, + carry_french: 0, + } + + pickup_max_american_cu(game.intercept, location_of_general(g)) + intercept_army(g, location_of_general(g), game.move.to) + + if (has_enemy_general(game.move.to) && !has_enemy_cu(game.move.to)) { + capture_enemy_general(game.move.to) + } + + if (count_friendly_generals(game.move.to) > 1) + goto_remove_general_after_intercept() + else + end_intercept() + } else { + log(g + " failed to intercept (" + die + " > " + GENERALS[g].agility + ")") + delete game.intercept + if (!can_intercept_to(game.move.to)) + end_intercept() } }, pass() { - delete game.where - end_strategy_card() + end_intercept() }, } -events.remove_american_pc = function (c, card) { - play_card(c) - game.count = card.count - game.state = "remove_american_pc" +function end_intercept() { + game.active = P_BRITAIN + game.state = "ops_general_move" + delete game.intercept + resume_moving() } -states.remove_american_pc = { - prompt() { - view.prompt = "Remove American PC markers. " + game.count + " left." - view.actions.pass = 1 - gen_remove_american_pc() - }, - space(where) { - remove_pc(where) - if (--game.count === 0) { - end_strategy_card() - } - }, - pass() { - end_strategy_card() - }, -} +/* RETREAT BEFORE BATTLE */ -events.remove_american_pc_from = function (c, card) { - play_card(c) - game.count = card.count - game.where = card.where - game.state = "remove_american_pc_from" +function can_retreat_before_battle() { + if (game.move.did_intercept) + 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)) + return true + return false } -states.remove_american_pc_from = { - prompt() { - view.prompt = "Remove American PC markers from " + game.where.join(", ") + ". " + game.count + " left." - view.actions.pass = 1 - gen_remove_american_pc_from(game.where) - }, - space(where) { - remove_pc(where) - if (--game.count === 0) { - delete game.where - end_strategy_card() - } - }, - pass() { - delete game.where - end_strategy_card() - }, +function goto_start_battle() { + clear_undo() + + game.combat = { + attacker: game.active, + a_bonus: 0, + b_bonus: 0, + b_draw_after_battle: false, + a_draw_after_battle: false, + british_losses: 0, + } + + if (game.active === P_BRITAIN && can_retreat_before_battle()) + goto_retreat_before_battle() + else + goto_play_attacker_battle_card() } -events.remove_american_pc_from_non_port = function (c, card) { - play_card(c) - game.count = card.count - game.where = card.where - game.state = "remove_american_pc_from_non_port" +function goto_retreat_before_battle() { + game.active = P_AMERICA + game.state = "retreat_before_battle" } -states.remove_american_pc_from_non_port = { +states.retreat_before_battle = { prompt() { - view.prompt = - "Remove American PC markers from non-Port space in " + game.where.join(", ") + ". " + game.count + " left." + view.prompt = "Attempt retreat before battle?" view.actions.pass = 1 - gen_remove_american_pc_from_non_port(game.where) + gen_defender_retreat() }, - space(where) { - remove_pc(where) - if (--game.count === 0) { - delete game.where - end_strategy_card() + space(to) { + let who = find_american_or_french_general(game.move.to) + let agility = GENERALS[who].agility + if (GENERALS[who].bonus) + agility += 2 + let roll = roll_d6() + if (roll <= agility) { + logp("successfully retreated before battle: " + roll + " <= " + agility) + pickup_max_american_cu(game.move.to) + move_army(who, game.move.to, to) + goto_remove_general_after_retreat_before_battle(to) + } else { + logp("failed to retreat before battle: " + roll + " > " + agility) + end_retreat_before_battle() } }, pass() { - delete game.where - end_strategy_card() + end_retreat_before_battle() }, } -events.remove_american_pc_within_two_spaces_of_a_british_general = function (c, card) { - play_card(c) - game.count = card.count - game.state = "remove_american_pc_within_two_spaces_of_a_british_general" +function goto_remove_general_after_retreat_before_battle(where) { + if (count_friendly_generals(where) > 1) { + game.state = "remove_general_after_retreat_before_battle" + game.where = where + } else { + end_remove_general_after_retreat_before_battle() + } } -states.remove_american_pc_within_two_spaces_of_a_british_general = { +states.remove_general_after_retreat_before_battle = { prompt() { - view.prompt = "Remove American PC markers within two spaces of a British general. " + game.count + " left." - view.actions.pass = 1 - gen_remove_american_pc_within_two_spaces_of_a_british_general() - }, - space(where) { - remove_pc(where) - if (--game.count === 0) { - delete game.where - end_strategy_card() - } + view.prompt = "Remove a general to the reinforcements box." + gen_remove_general(game.where) }, - pass() { - delete game.where - end_strategy_card() + general(g) { + if (game.active === P_BRITAIN) + move_general(g, BRITISH_REINFORCEMENTS) + else + move_general(g, AMERICAN_REINFORCEMENTS) + end_remove_general_after_retreat_before_battle() }, } -events.place_american_pc = function (c, card) { - play_card(c) - game.count = card.count - game.state = "place_american_pc" +function end_remove_general_after_retreat_before_battle() { + let b_cu = count_british_cu(game.move.to) + let a_cu = count_american_and_french_cu(game.move.to) + if (a_cu === 0) { + end_battle() + } else if (b_cu >= 4 && a_cu === 1) { + overrun(game.move.to) + end_battle() + } else { + end_retreat_before_battle() + } } -states.place_american_pc = { - prompt() { - view.prompt = "Place American PC markers. " + game.count + " left." - view.actions.pass = 1 - gen_place_american_pc() - }, - place_american_pc(where) { - place_american_pc(where) - if (--game.count === 0) { - end_strategy_card() - } - }, - pass() { - end_strategy_card() - }, -} +/* BATTLE CARDS */ -events.place_american_pc_in = function (c, card) { - play_card(c) - game.count = card.count - game.where = card.where - game.state = "place_american_pc_in" +function goto_play_attacker_battle_card() { + game.active = game.combat.attacker + game.state = "play_attacker_battle_card" } -states.place_american_pc_in = { +states.play_attacker_battle_card = { prompt() { - view.prompt = "Place American PC markers in " + game.where.join(", ") + ". " + game.count + " left." + view.prompt = "Attack: Play or discard event for DRM." view.actions.pass = 1 - gen_place_american_pc_in(game.where) + gen_battle_card() }, - place_american_pc(where) { - place_american_pc(where) - if (--game.count === 0) { - delete game.where - end_strategy_card() + card_battle_play(c) { + 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 + } + goto_play_defender_battle_card() + }, + card_battle_discard(c) { + discard_card(c, "for +1 DRM") + if (game.active === P_BRITAIN) { + game.combat.b_draw_after_battle = true + game.combat.b_bonus += 1 + } else { + game.combat.a_draw_after_battle = true + game.combat.a_bonus += 1 } + goto_play_defender_battle_card() }, pass() { - delete game.where - end_strategy_card() + goto_play_defender_battle_card() }, } -events.lord_sandwich_coastal_raids = function (c, card) { - play_card(c) - game.state = "lord_sandwich_coastal_raids" - game.count = 2 - delete game.where +function goto_play_defender_battle_card() { + game.state = "play_defender_battle_card" + game.active = ENEMY[game.combat.attacker] } -states.lord_sandwich_coastal_raids = { +states.play_defender_battle_card = { prompt() { - view.prompt = "Remove two or flip one American PC in a port space." + view.prompt = "Defend: Play or discard event for DRM." view.actions.pass = 1 - gen_lord_sandwich_coastal_raids(game.where) + gen_battle_card() }, - space(where) { - if (has_american_pc(space)) { - game.where = where - remove_pc(where) - if (--game.count === 0) - end_strategy_card() + card_battle_play(c) { + 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 { - place_british_pc(where) + game.combat.a_draw_after_battle = true + game.combat.a_bonus += 2 } - end_strategy_card() - }, - pass() { - end_strategy_card() + resolve_battle() }, -} - -function gen_lord_sandwich_coastal_raids(first_removed) { - for (let space of all_spaces) { - if (is_port(space)) - if (has_american_pc(space) && has_no_american_unit(space)) - gen_action_space(space) - if (space === first_removed) - gen_action_space(space) - } -} - -events.remove_american_cu = function (c, card) { - play_card(c) - game.state = "remove_american_cu" -} - -states.remove_american_cu = { - prompt() { - view.prompt = "Remove one American CU from any space." - view.actions.pass = 1 - gen_remove_american_cu() - }, - remove_cu(where) { - if (count_american_cu(where) > 0) - remove_american_cu(where, 1) - else - remove_french_cu(where, 1) - end_strategy_card() + card_battle_discard(c) { + discard_card(c, "for +1 DRM") + if (game.active === P_BRITAIN) { + game.combat.b_draw_after_battle = true + game.combat.b_bonus += 1 + } else { + game.combat.a_draw_after_battle = true + game.combat.a_bonus += 1 + } + resolve_battle() }, pass() { - end_strategy_card() + resolve_battle() }, } -function gen_remove_american_cu() { - for (let space of all_spaces) { - if (has_american_or_french_cu(space)) - gen_action("remove_cu", space) +function gen_battle_card() { + for (let c of active_hand()) { + let card = CARDS[c] + if (game.active === P_BRITAIN) { + switch (card.type) { + case "british-battle": + case "british-event-or-battle": + gen_action_card("card_battle_play", c) + break + case "british-event": + case "american-event": + case "american-battle": + gen_action_card("card_battle_discard", c) + break + } + } else { + switch (card.type) { + case "british-battle": + case "british-event-or-battle": + case "british-event": + case "american-event": + gen_action_card("card_battle_discard", c) + break + case "american-battle": + gen_action_card("card_battle_play", c) + break + } + } } } -events.remove_british_cu = function (c, card) { - play_card(c) - game.state = "remove_british_cu" - game.count = card.count -} - -states.remove_british_cu = { - prompt() { - view.prompt = "Remove " + game.count + " British CU from any space." - view.actions.pass = 1 - gen_remove_british_cu() - }, - remove_cu(where) { - remove_british_cu(where, 1) - if (--game.count === 0) - end_strategy_card() - }, - pass() { - end_strategy_card() - }, -} +/* RESOLVE BATTLE */ -function gen_remove_british_cu() { - for (let space of all_spaces) { - if (has_british_cu(space)) - gen_action("remove_cu", space) +function roll_loser_combat_losses(log) { + let roll = roll_d6() + let losses = 0 + log.push("Loser Combat Loss Roll: " + roll) + switch (roll) { + case 1: + case 2: + case 3: + losses = 1 + break + case 4: + case 5: + losses = 2 + break + case 6: + losses = 3 + break } + return losses } -events.pennsylvania_and_new_jersey_line_mutinies = function (c, card) { - play_card(c) - set_flag(F_MUTINIES) - game.state = "pennsylvania_and_new_jersey_line_mutinies" - game.count = 2 - delete game.where +function roll_winner_combat_losses(log, losing_general) { + let agility = losing_general !== NOBODY ? GENERALS[losing_general].agility : 0 + let roll = roll_d6() + log.push("Enemy Agility Rating: " + agility) + log.push("Winner Combat Loss Roll: " + roll) + let losses = 0 + switch (agility) { + case 0: + losses = roll === 1 ? 1 : 0 + break + case 1: + losses = roll <= 2 ? 1 : 0 + break + case 2: + losses = roll <= 3 ? 1 : 0 + break + case 3: + losses = roll <= 4 ? 1 : 0 + break + } + return losses } -states.pennsylvania_and_new_jersey_line_mutinies = { - prompt() { - view.prompt = "Remove two American CUs from the map, one each from two different spaces." - view.actions.pass = 1 - gen_pennsylvania_and_new_jersey_line_mutinies(game.where) - }, - remove_cu(where) { - if (count_american_cu(where) > 0) - remove_american_cu(where, 1) - else - remove_french_cu(where, 1) - game.where = where - if (--game.count === 0) - end_strategy_card() - }, - pass() { - end_strategy_card() - }, +function apply_british_combat_losses(max) { + let n = Math.min(count_british_cu(game.move.to), max) + remove_british_cu(game.move.to, n) + return n } -function gen_pennsylvania_and_new_jersey_line_mutinies(first_removed) { - for (let space of all_spaces) { - if (has_american_or_french_cu(space)) - if (space !== first_removed) - gen_action("remove_cu", space) - } +function apply_american_combat_losses(max) { + let n = Math.min(count_american_cu(game.move.to), max) + remove_american_cu(game.move.to, n) + return n } -events.john_glovers_marblehead_regiment = function (c, card) { - play_card(c) - game.state = "john_glovers_marblehead_regiment_who" - game.count = 3 // strategy rating for gen_activate_general +function apply_french_combat_losses(max) { + let n = Math.min(count_french_cu(game.move.to), max) + remove_french_cu(game.move.to, n) + return n } -states.john_glovers_marblehead_regiment_who = { - prompt() { - view.prompt = "Activate an American general." - gen_activate_general() - }, - general(g) { - goto_ops_general_move(g, true) - }, +function apply_american_and_french_combat_losses(max) { + let n = apply_american_combat_losses(max) + if (n < max) + n += apply_french_combat_losses(max - n) + return n } -events.declaration_of_independence = function (c, card) { - play_card(c) - game.save = game.active - game.active = P_AMERICA - game.colonies = THE_13_COLONIES.slice() - game.state = "declaration_of_independence" -} +function resolve_battle() { + let a_log = [] + let b_log = [] -states.declaration_of_independence = { - prompt() { - view.prompt = "Declaration of Independence: Place 1 PC marker in each of the 13 colonies. " - view.prompt += game.colonies.length + " left." - view.actions.pass = 1 - gen_place_american_pc_in(game.colonies) - }, - place_american_pc(space) { - let colony = SPACES[space].colony - set_delete(game.colonies, colony) - place_american_pc(space) - if (game.colonies.length === 0) - end_declaration_of_independence() - }, - pass() { - end_declaration_of_independence() - }, -} + game.active = ENEMY[game.active] + let b_g = find_british_general(game.move.to) + let b_cu = count_british_cu(game.move.to) + let a_g = find_american_or_french_general(game.move.to) + let a_cu = count_american_and_french_cu(game.move.to) + let b_br = 0 + let a_br = 0 -function end_declaration_of_independence() { - game.active = game.save - delete game.save - delete game.colonies - end_strategy_card() -} + if (b_g !== NOBODY) + b_log.push("General: " + b_g) + if (a_g !== NOBODY) + a_log.push("General: " + a_g) -function goto_george_washington_captured() { - // TODO: this is not robust enough! figure out exactly where we are called. + if (b_g !== NOBODY) { + let roll = roll_d6() + if (roll <= 3) + b_br = Math.min(Math.floor(GENERALS[b_g].battle / 2), b_cu) + else + b_br = Math.min(GENERALS[b_g].battle, b_cu) + b_log.push("Actual Battle Rating Roll: " + roll) + } - /* Save all the state we clobber during the interrupt. */ - game.save = { - state: game.state, - active: game.active, - count: game.count + if (a_g !== NOBODY) { + let roll = roll_d6() + if (roll <= 3) + a_br = Math.min(Math.floor(GENERALS[a_g].battle / 2), a_cu) + else + a_br = Math.min(GENERALS[a_g].battle, a_cu) + a_log.push("Actual Battle Rating Roll: " + roll) } - game.state = "george_washington_captured" - game.active = P_BRITAIN - game.count = 5 -} + b_log.push("+" + b_cu + " CU") + a_log.push("+" + a_cu + " CU") + b_log.push("+" + b_br + " Actual Battle Rating") + a_log.push("+" + a_br + " Actual Battle Rating") -states.george_washington_captured = { - prompt() { - view.prompt = "George Washington is captured! Remove American PC markers. " + game.count + " left." - view.actions.pass = 1 - gen_remove_american_pc() - }, - space(where) { - remove_pc(where) - if (--game.count === 0) { - end_george_washington_captured() + let b_drm = b_cu + b_br + game.combat.b_bonus + if (has_flag(F_REGULARS)) { + b_log.push("+1 British Regulars' Advantage") + b_drm += 1 + } + if (is_non_blockaded_port(game.move.to)) { + if (is_fortified_port(game.move.to)) { + if (has_british_pc(game.move.to)) { + b_log.push("+1 Royal Navy Support") + b_drm += 1 + } + } else { + b_log.push("+1 Royal Navy Support") + b_drm += 1 } - }, - pass() { - end_george_washington_captured() - }, -} - -function end_george_washington_captured() { - /* Restore previous state. */ - game.state = game.save.state - game.count = game.save.count - game.active = game.save.active - delete game.save -} + } + if (is_british_militia(game.move.to)) { + b_log.push("+1 Militia") + b_drm += 1 + } + if (game.combat.b_bonus === 2) + b_log.push("+2 Battle Card") + else if (game.combat.b_bonus === 1) + b_log.push("+1 Discard of an Event Card") -function do_event(c) { - let card = CARDS[c] - if (card.event in events) - events[card.event](c, card) - else - throw new Error("Event not implemented yet: " + card.event) -} + let a_drm = a_cu + a_br + game.combat.a_bonus + if (is_american_militia(game.move.to)) { + a_log.push("+1 Militia") + a_drm += 1 + } + if (is_american_winter_offensive()) { + a_log.push("+2 American Winter Offensive") + a_drm += 2 + } + if (game.combat.a_bonus === 2) + a_log.push("+2 Battle Card") + else if (game.combat.a_bonus === 1) + a_log.push("+1 Discard of an Event Card") + if (game.move.did_intercept) { + a_log.push("+1 Interception") + a_drm += 1 + } -/* BATTLE */ + let b_roll = roll_d6() + let a_roll = roll_d6() -function can_retreat_before_battle() { - if (game.move.did_intercept) - 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)) - return true - return false -} + b_log.push("Battle Roll: " + b_roll) + b_log.push("Battle Total: " + (b_roll + b_drm)) + a_log.push("Battle Roll: " + a_roll) + a_log.push("Battle Total: " + (a_roll + a_drm)) -function goto_start_battle() { - clear_undo() + let victor + if (game.active === P_BRITAIN) + victor = b_roll + b_drm >= a_roll + a_drm ? P_BRITAIN : P_AMERICA + else + victor = b_roll + b_drm >= a_roll + a_drm ? P_BRITAIN : P_AMERICA - game.combat = { - attacker: game.active, - a_bonus: 0, - b_bonus: 0, - b_draw_after_battle: false, - a_draw_after_battle: false, - british_losses: 0, + let a_lost_cu, b_lost_cu + if (victor === P_BRITAIN) { + b_lost_cu = roll_winner_combat_losses(b_log, a_g) + a_lost_cu = roll_loser_combat_losses(a_log) + } else { + b_lost_cu = roll_loser_combat_losses(b_log) + a_lost_cu = roll_winner_combat_losses(a_log, b_g) } - if (game.active === P_BRITAIN && can_retreat_before_battle()) - goto_retreat_before_battle() - else - goto_play_attacker_battle_card() -} - -function goto_retreat_before_battle() { - game.active = P_AMERICA - game.state = "retreat_before_battle" -} + game.combat.british_losses = apply_british_combat_losses(b_lost_cu) + let american_losses = apply_american_and_french_combat_losses(a_lost_cu) -states.retreat_before_battle = { - prompt() { - view.prompt = "Attempt retreat before battle?" - view.actions.pass = 1 - gen_defender_retreat() - }, - space(to) { - let who = find_american_or_french_general(game.move.to) - let agility = GENERALS[who].agility - if (GENERALS[who].bonus) - agility += 2 - let roll = roll_d6() - if (roll <= agility) { - logp("successfully retreated before battle: " + roll + " <= " + agility) - pickup_max_american_cu(game.move.to) - move_army(who, game.move.to, to) - goto_remove_general_after_retreat_before_battle(to) - } else { - logp("failed to retreat before battle: " + roll + " > " + agility) - end_retreat_before_battle() - } - }, - pass() { - end_retreat_before_battle() - }, -} + b_log.push("Losses: " + game.combat.british_losses + " CU") + a_log.push("Losses: " + american_losses + " CU") -function goto_remove_general_after_retreat_before_battle(where) { - if (count_friendly_generals(where) > 1) { - game.state = "remove_general_after_retreat_before_battle" - game.where = where + // Special case: winning general with no CU on enemy PC is captured + if (victor === P_BRITAIN) { + if (b_g && count_british_cu(game.move.to) === 0 && has_american_pc(game.move.to)) + capture_british_general(game.move.to) } else { - end_remove_general_after_retreat_before_battle() + if (a_g && count_american_and_french_cu(game.move.to) === 0 && has_british_pc(game.move.to)) + capture_american_or_french_general(game.move.to) } -} -states.remove_general_after_retreat_before_battle = { - prompt() { - view.prompt = "Remove a general to the reinforcements box." - gen_remove_general(game.where) - }, - general(g) { - if (game.active === P_BRITAIN) - move_general(g, BRITISH_REINFORCEMENTS) - else - move_general(g, AMERICAN_REINFORCEMENTS) - end_remove_general_after_retreat_before_battle() - }, -} + log("BRITISH BATTLE REPORT:\n" + b_log.join("\n")) + log("AMERICAN BATTLE REPORT:\n" + a_log.join("\n")) + log(victor + " victory in " + game.move.to + "!") -function end_remove_general_after_retreat_before_battle() { - let b_cu = count_british_cu(game.move.to) - let a_cu = count_american_and_french_cu(game.move.to) - if (a_cu === 0) { - end_battle() - } else if (b_cu >= 4 && a_cu === 1) { - overrun(game.move.to) - end_battle() - } else { - end_retreat_before_battle() - } + if (victor === P_AMERICA) + advance_french_alliance(1) + + goto_retreat_after_battle(victor) } +/* RETREAT AFTER BATTLE */ + function can_defender_retreat(to) { if (to === game.move.from) return false @@ -2833,384 +2681,570 @@ function end_retreat_before_battle() { goto_play_attacker_battle_card() } -function goto_play_attacker_battle_card() { - game.active = game.combat.attacker - game.state = "play_attacker_battle_card" + +function goto_retreat_after_battle(victor) { + 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) + return end_battle() + } else { + let who = find_british_general(game.move.to) + if (who === NOBODY && count_british_cu(game.move.to) === 0) + return end_battle() + } + game.active = ENEMY[victor] + game.state = "retreat_after_battle" } -states.play_attacker_battle_card = { +states.retreat_after_battle = { prompt() { - view.prompt = "Attack: Play or discard event for DRM." - view.actions.pass = 1 - gen_battle_card() + view.prompt = "Retreat after battle." + gen_action("surrender") + if (game.active === game.combat.attacker) + gen_attacker_retreat() + else + gen_defender_retreat() }, - card_battle_play(c) { - 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 - } - goto_play_defender_battle_card() + space(to) { + logp("retreated to " + to) + if (game.active === P_BRITAIN) + retreat_british_army(game.move.to, to) + else + retreat_american_army(game.move.to, to) + if (count_friendly_generals(to) > 1) + goto_remove_general_after_retreat(to) + else + end_battle() }, - card_battle_discard(c) { - discard_card(c, "for +1 DRM") - if (game.active === P_BRITAIN) { - game.combat.b_draw_after_battle = true - game.combat.b_bonus += 1 - } else { - game.combat.a_draw_after_battle = true - game.combat.a_bonus += 1 - } - goto_play_defender_battle_card() - }, - pass() { - goto_play_defender_battle_card() + surrender() { + logp("surrendered") + + if (game.active === P_BRITAIN) + surrender_british_army(game.move.to) + else + surrender_american_army(game.move.to) + + end_battle() }, } -function goto_play_defender_battle_card() { - game.state = "play_defender_battle_card" - game.active = ENEMY[game.combat.attacker] +/* END BATTLE */ + +function end_battle() { + game.active = game.combat.attacker + + if (game.active === P_BRITAIN && game.congress === game.move.to) + disperse_continental_congress() + + if (game.combat.british_losses >= 3) + lose_regular_advantage() + + // TODO: delay until end of campaign + if (game.combat.b_draw_after_battle) + set_add(game.b_hand, deal_card()) + if (game.combat.a_draw_after_battle) + set_add(game.a_hand, deal_card()) + + delete game.combat + + end_move() } -states.play_defender_battle_card = { +/* CAMPAIGN */ + +function goto_campaign(c) { + play_card(c) + game.state = "campaign" + game.campaign = CARDS[c].count + if (game.active === P_BRITAIN) + set_flag(F_LANDING_PARTY) + else + clear_flag(F_LANDING_PARTY) + game.count = 3 // can activate any general! + game.state = "ops_general_who" +} + +/* EVENTS */ + +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() +} + +events.remove_random_british_card = function (c, card) { + play_card(c) + remove_random_card(game.b_hand) +} + +events.remove_random_american_card = function (c, card) { + play_card(c) + remove_random_card(game.a_hand) +} + +function remove_random_card(hand) { + clear_undo() + if (hand.length > 0) { + let i = random(hand.length) + let c = hand[i] + discard_card_from_hand(hand, c) + if (CARDS[c].type === "mandatory-event") + do_event(c) + else + end_strategy_card() + } else + end_strategy_card() +} + +function advance_french_alliance(count) { + if (game.french_alliance < 9) { + game.french_alliance += count + if (game.french_alliance > 9) + game.french_alliance = 9 + log("French alliance advanced to " + count) + } +} + +function lose_regular_advantage() { + if (has_flag(F_REGULARS)) { + log("British Regulars Advantage lost!") + clear_flag(F_REGULARS) + advance_french_alliance(2) + } +} + +events.baron_von_steuben_trains_the_continental_army = function (c, card) { + play_card(c) + if (is_general_on_map(WASHINGTON)) { + let where = location_of_general(WASHINGTON) + logp("placed 2 CU with Washington in " + where) + place_american_cu(where, 2) + lose_regular_advantage() + } + end_strategy_card() +} + +events.advance_french_alliance = function (c, card) { + play_card(c) + advance_french_alliance(card.count) + end_strategy_card() +} + +events.remove_french_navy = function (c, card) { + play_card(c) + game.french_navy = game.year + 1 + end_strategy_card() +} + +events.remove_british_pc_from = function (c, card) { + play_card(c) + game.count = card.count + game.where = card.where + game.state = "remove_british_pc_from" +} + +states.remove_british_pc_from = { prompt() { - view.prompt = "Defend: Play or discard event for DRM." + view.prompt = "Remove British PC markers from " + game.where.map(x=>data.colony_name[x]).join(", ") + ". " + game.count + " left." view.actions.pass = 1 - gen_battle_card() - }, - card_battle_play(c) { - 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 - } - resolve_battle() + gen_remove_british_pc_from(game.where) }, - card_battle_discard(c) { - discard_card(c, "for +1 DRM") - if (game.active === P_BRITAIN) { - game.combat.b_draw_after_battle = true - game.combat.b_bonus += 1 - } else { - game.combat.a_draw_after_battle = true - game.combat.a_bonus += 1 + space(where) { + remove_pc(where) + if (--game.count === 0) { + delete game.where + end_strategy_card() } - resolve_battle() }, pass() { - resolve_battle() + delete game.where + end_strategy_card() }, } -function gen_battle_card() { - for (let c of active_hand()) { - let card = CARDS[c] - if (game.active === P_BRITAIN) { - switch (card.type) { - case "british-battle": - case "british-event-or-battle": - gen_action_card("card_battle_play", c) - break - case "british-event": - case "american-event": - case "american-battle": - gen_action_card("card_battle_discard", c) - break - } - } else { - switch (card.type) { - case "british-battle": - case "british-event-or-battle": - case "british-event": - case "american-event": - gen_action_card("card_battle_discard", c) - break - case "american-battle": - gen_action_card("card_battle_play", c) - break - } - } - } +events.remove_american_pc = function (c, card) { + play_card(c) + game.count = card.count + game.state = "remove_american_pc" } -function roll_loser_combat_losses(log) { - let roll = roll_d6() - let losses = 0 - log.push("Loser Combat Loss Roll: " + roll) - switch (roll) { - case 1: - case 2: - case 3: - losses = 1 - break - case 4: - case 5: - losses = 2 - break - case 6: - losses = 3 - break - } - return losses +states.remove_american_pc = { + prompt() { + view.prompt = "Remove American PC markers. " + game.count + " left." + view.actions.pass = 1 + gen_remove_american_pc() + }, + space(where) { + remove_pc(where) + if (--game.count === 0) { + end_strategy_card() + } + }, + pass() { + end_strategy_card() + }, } -function roll_winner_combat_losses(log, losing_general) { - let agility = losing_general !== NOBODY ? GENERALS[losing_general].agility : 0 - let roll = roll_d6() - log.push("Enemy Agility Rating: " + agility) - log.push("Winner Combat Loss Roll: " + roll) - let losses = 0 - switch (agility) { - case 0: - losses = roll === 1 ? 1 : 0 - break - case 1: - losses = roll <= 2 ? 1 : 0 - break - case 2: - losses = roll <= 3 ? 1 : 0 - break - case 3: - losses = roll <= 4 ? 1 : 0 - break - } - return losses +events.remove_american_pc_from = function (c, card) { + play_card(c) + game.count = card.count + game.where = card.where + game.state = "remove_american_pc_from" } -function apply_british_combat_losses(max) { - let n = Math.min(count_british_cu(game.move.to), max) - remove_british_cu(game.move.to, n) - return n +states.remove_american_pc_from = { + prompt() { + view.prompt = "Remove American PC markers from " + game.where.join(", ") + ". " + game.count + " left." + view.actions.pass = 1 + gen_remove_american_pc_from(game.where) + }, + space(where) { + remove_pc(where) + if (--game.count === 0) { + delete game.where + end_strategy_card() + } + }, + pass() { + delete game.where + end_strategy_card() + }, } -function apply_american_combat_losses(max) { - let n = Math.min(count_american_cu(game.move.to), max) - remove_american_cu(game.move.to, n) - return n +events.remove_american_pc_from_non_port = function (c, card) { + play_card(c) + game.count = card.count + game.where = card.where + game.state = "remove_american_pc_from_non_port" } -function apply_french_combat_losses(max) { - let n = Math.min(count_french_cu(game.move.to), max) - remove_french_cu(game.move.to, n) - return n +states.remove_american_pc_from_non_port = { + prompt() { + view.prompt = + "Remove American PC markers from non-Port space in " + game.where.join(", ") + ". " + game.count + " left." + view.actions.pass = 1 + gen_remove_american_pc_from_non_port(game.where) + }, + space(where) { + remove_pc(where) + if (--game.count === 0) { + delete game.where + end_strategy_card() + } + }, + pass() { + delete game.where + end_strategy_card() + }, } -function apply_american_and_french_combat_losses(max) { - let n = apply_american_combat_losses(max) - if (n < max) - n += apply_french_combat_losses(max - n) - return n +events.remove_american_pc_within_two_spaces_of_a_british_general = function (c, card) { + play_card(c) + game.count = card.count + game.state = "remove_american_pc_within_two_spaces_of_a_british_general" } -function resolve_battle() { - let a_log = [] - let b_log = [] +states.remove_american_pc_within_two_spaces_of_a_british_general = { + prompt() { + view.prompt = "Remove American PC markers within two spaces of a British general. " + game.count + " left." + view.actions.pass = 1 + gen_remove_american_pc_within_two_spaces_of_a_british_general() + }, + space(where) { + remove_pc(where) + if (--game.count === 0) { + delete game.where + end_strategy_card() + } + }, + pass() { + delete game.where + end_strategy_card() + }, +} - game.active = ENEMY[game.active] - let b_g = find_british_general(game.move.to) - let b_cu = count_british_cu(game.move.to) - let a_g = find_american_or_french_general(game.move.to) - let a_cu = count_american_and_french_cu(game.move.to) - let b_br = 0 - let a_br = 0 +events.place_american_pc = function (c, card) { + play_card(c) + game.count = card.count + game.state = "place_american_pc" +} - if (b_g !== NOBODY) - b_log.push("General: " + b_g) - if (a_g !== NOBODY) - a_log.push("General: " + a_g) +states.place_american_pc = { + prompt() { + view.prompt = "Place American PC markers. " + game.count + " left." + view.actions.pass = 1 + gen_place_american_pc() + }, + place_american_pc(where) { + place_american_pc(where) + if (--game.count === 0) { + end_strategy_card() + } + }, + pass() { + end_strategy_card() + }, +} - if (b_g !== NOBODY) { - let roll = roll_d6() - if (roll <= 3) - b_br = Math.min(Math.floor(GENERALS[b_g].battle / 2), b_cu) - else - b_br = Math.min(GENERALS[b_g].battle, b_cu) - b_log.push("Actual Battle Rating Roll: " + roll) - } +events.place_american_pc_in = function (c, card) { + play_card(c) + game.count = card.count + game.where = card.where + game.state = "place_american_pc_in" +} - if (a_g !== NOBODY) { - let roll = roll_d6() - if (roll <= 3) - a_br = Math.min(Math.floor(GENERALS[a_g].battle / 2), a_cu) - else - a_br = Math.min(GENERALS[a_g].battle, a_cu) - a_log.push("Actual Battle Rating Roll: " + roll) - } +states.place_american_pc_in = { + prompt() { + view.prompt = "Place American PC markers in " + game.where.join(", ") + ". " + game.count + " left." + view.actions.pass = 1 + gen_place_american_pc_in(game.where) + }, + place_american_pc(where) { + place_american_pc(where) + if (--game.count === 0) { + delete game.where + end_strategy_card() + } + }, + pass() { + delete game.where + end_strategy_card() + }, +} - b_log.push("+" + b_cu + " CU") - a_log.push("+" + a_cu + " CU") - b_log.push("+" + b_br + " Actual Battle Rating") - a_log.push("+" + a_br + " Actual Battle Rating") +events.lord_sandwich_coastal_raids = function (c, card) { + play_card(c) + game.state = "lord_sandwich_coastal_raids" + game.count = 2 + delete game.where +} - let b_drm = b_cu + b_br + game.combat.b_bonus - if (has_flag(F_REGULARS)) { - b_log.push("+1 British Regulars' Advantage") - b_drm += 1 - } - if (is_non_blockaded_port(game.move.to)) { - if (is_fortified_port(game.move.to)) { - if (has_british_pc(game.move.to)) { - b_log.push("+1 Royal Navy Support") - b_drm += 1 - } +states.lord_sandwich_coastal_raids = { + prompt() { + view.prompt = "Remove two or flip one American PC in a port space." + view.actions.pass = 1 + gen_lord_sandwich_coastal_raids(game.where) + }, + space(where) { + if (has_american_pc(space)) { + game.where = where + remove_pc(where) + if (--game.count === 0) + end_strategy_card() } else { - b_log.push("+1 Royal Navy Support") - b_drm += 1 + place_british_pc(where) } - } - if (is_british_militia(game.move.to)) { - b_log.push("+1 Militia") - b_drm += 1 - } - if (game.combat.b_bonus === 2) - b_log.push("+2 Battle Card") - else if (game.combat.b_bonus === 1) - b_log.push("+1 Discard of an Event Card") + end_strategy_card() + }, + pass() { + end_strategy_card() + }, +} - let a_drm = a_cu + a_br + game.combat.a_bonus - if (is_american_militia(game.move.to)) { - a_log.push("+1 Militia") - a_drm += 1 - } - if (is_american_winter_offensive()) { - a_log.push("+2 American Winter Offensive") - a_drm += 2 - } - if (game.combat.a_bonus === 2) - a_log.push("+2 Battle Card") - else if (game.combat.a_bonus === 1) - a_log.push("+1 Discard of an Event Card") - if (game.move.did_intercept) { - a_log.push("+1 Interception") - a_drm += 1 +function gen_lord_sandwich_coastal_raids(first_removed) { + for (let space of all_spaces) { + if (is_port(space)) + if (has_american_pc(space) && has_no_american_unit(space)) + gen_action_space(space) + if (space === first_removed) + gen_action_space(space) } +} - let b_roll = roll_d6() - let a_roll = roll_d6() - - b_log.push("Battle Roll: " + b_roll) - b_log.push("Battle Total: " + (b_roll + b_drm)) - a_log.push("Battle Roll: " + a_roll) - a_log.push("Battle Total: " + (a_roll + a_drm)) +events.remove_american_cu = function (c, card) { + play_card(c) + game.state = "remove_american_cu" +} - let victor - if (game.active === P_BRITAIN) - victor = b_roll + b_drm >= a_roll + a_drm ? P_BRITAIN : P_AMERICA - else - victor = b_roll + b_drm >= a_roll + a_drm ? P_BRITAIN : P_AMERICA +states.remove_american_cu = { + prompt() { + view.prompt = "Remove one American CU from any space." + view.actions.pass = 1 + gen_remove_american_cu() + }, + remove_cu(where) { + if (count_american_cu(where) > 0) + remove_american_cu(where, 1) + else + remove_french_cu(where, 1) + end_strategy_card() + }, + pass() { + end_strategy_card() + }, +} - let a_lost_cu, b_lost_cu - if (victor === P_BRITAIN) { - b_lost_cu = roll_winner_combat_losses(b_log, a_g) - a_lost_cu = roll_loser_combat_losses(a_log) - } else { - b_lost_cu = roll_loser_combat_losses(b_log) - a_lost_cu = roll_winner_combat_losses(a_log, b_g) +function gen_remove_american_cu() { + for (let space of all_spaces) { + if (has_american_or_french_cu(space)) + gen_action("remove_cu", space) } +} - game.combat.british_losses = apply_british_combat_losses(b_lost_cu) - let american_losses = apply_american_and_french_combat_losses(a_lost_cu) +events.remove_british_cu = function (c, card) { + play_card(c) + game.state = "remove_british_cu" + game.count = card.count +} - b_log.push("Losses: " + game.combat.british_losses + " CU") - a_log.push("Losses: " + american_losses + " CU") +states.remove_british_cu = { + prompt() { + view.prompt = "Remove " + game.count + " British CU from any space." + view.actions.pass = 1 + gen_remove_british_cu() + }, + remove_cu(where) { + remove_british_cu(where, 1) + if (--game.count === 0) + end_strategy_card() + }, + pass() { + end_strategy_card() + }, +} - // Special case: winning general with no CU on enemy PC is captured - if (victor === P_BRITAIN) { - if (b_g && count_british_cu(game.move.to) === 0 && has_american_pc(game.move.to)) - capture_british_general(game.move.to) - } else { - if (a_g && count_american_and_french_cu(game.move.to) === 0 && has_british_pc(game.move.to)) - capture_american_or_french_general(game.move.to) +function gen_remove_british_cu() { + for (let space of all_spaces) { + if (has_british_cu(space)) + gen_action("remove_cu", space) } +} - log("BRITISH BATTLE REPORT:\n" + b_log.join("\n")) - log("AMERICAN BATTLE REPORT:\n" + a_log.join("\n")) - log(victor + " victory in " + game.move.to + "!") - - if (victor === P_AMERICA) - advance_french_alliance(1) +events.pennsylvania_and_new_jersey_line_mutinies = function (c, card) { + play_card(c) + set_flag(F_MUTINIES) + game.state = "pennsylvania_and_new_jersey_line_mutinies" + game.count = 2 + delete game.where +} - goto_retreat_after_battle(victor) +states.pennsylvania_and_new_jersey_line_mutinies = { + prompt() { + view.prompt = "Remove two American CUs from the map, one each from two different spaces." + view.actions.pass = 1 + gen_pennsylvania_and_new_jersey_line_mutinies(game.where) + }, + remove_cu(where) { + if (count_american_cu(where) > 0) + remove_american_cu(where, 1) + else + remove_french_cu(where, 1) + game.where = where + if (--game.count === 0) + end_strategy_card() + }, + pass() { + end_strategy_card() + }, } -function goto_retreat_after_battle(victor) { - 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) - return end_battle() - } else { - let who = find_british_general(game.move.to) - if (who === NOBODY && count_british_cu(game.move.to) === 0) - return end_battle() +function gen_pennsylvania_and_new_jersey_line_mutinies(first_removed) { + for (let space of all_spaces) { + if (has_american_or_french_cu(space)) + if (space !== first_removed) + gen_action("remove_cu", space) } - game.active = ENEMY[victor] - game.state = "retreat_after_battle" } -states.retreat_after_battle = { +events.john_glovers_marblehead_regiment = function (c, card) { + play_card(c) + game.state = "john_glovers_marblehead_regiment_who" + game.count = 3 // strategy rating for gen_activate_general +} + +states.john_glovers_marblehead_regiment_who = { prompt() { - view.prompt = "Retreat after battle." - gen_action("surrender") - if (game.active === game.combat.attacker) - gen_attacker_retreat() - else - gen_defender_retreat() + view.prompt = "Activate an American general." + gen_activate_general() }, - space(to) { - logp("retreated to " + to) - if (game.active === P_BRITAIN) - retreat_british_army(game.move.to, to) - else - retreat_american_army(game.move.to, to) - if (count_friendly_generals(to) > 1) - goto_remove_general_after_retreat(to) - else - end_battle() + general(g) { + goto_ops_general_move(g, true) }, - surrender() { - // End battle here, so if Washington is captured we can handle the interrupt state. - let active = game.active - // TODO: ugly clean this up - end_battle() +} - logp("surrendered") - if (active === P_BRITAIN) - surrender_british_army(game.move.to) - else - surrender_american_army(game.move.to) +events.declaration_of_independence = function (c, card) { + play_card(c) + game.save = game.active + game.active = P_AMERICA + game.colonies = THE_13_COLONIES.slice() + game.state = "declaration_of_independence" +} + +states.declaration_of_independence = { + prompt() { + view.prompt = "Declaration of Independence: Place 1 PC marker in each of the 13 colonies. " + view.prompt += game.colonies.length + " left." + view.actions.pass = 1 + gen_place_american_pc_in(game.colonies) + }, + place_american_pc(space) { + let colony = SPACES[space].colony + set_delete(game.colonies, colony) + place_american_pc(space) + if (game.colonies.length === 0) + end_declaration_of_independence() + }, + pass() { + end_declaration_of_independence() }, } -function end_battle() { - game.active = game.combat.attacker +function end_declaration_of_independence() { + game.active = game.save + delete game.save + delete game.colonies + end_strategy_card() +} - if (game.active === P_BRITAIN && game.congress === game.move.to) - disperse_continental_congress() +function goto_george_washington_captured() { + // TODO: this is not robust enough! figure out exactly where we are called. - if (game.combat.british_losses >= 3) - lose_regular_advantage() + /* Save all the state we clobber during the interrupt. */ + game.save = { + state: game.state, + active: game.active, + count: game.count + } - // TODO: delay until end of campaign - if (game.combat.b_draw_after_battle) - set_add(game.b_hand, deal_card()) - if (game.combat.a_draw_after_battle) - set_add(game.a_hand, deal_card()) + game.state = "george_washington_captured" + game.active = P_BRITAIN + game.count = 5 +} - delete game.combat +states.george_washington_captured = { + prompt() { + view.prompt = "George Washington is captured! Remove American PC markers. " + game.count + " left." + view.actions.pass = 1 + gen_remove_american_pc() + }, + space(where) { + remove_pc(where) + if (--game.count === 0) { + end_george_washington_captured() + } + }, + pass() { + end_george_washington_captured() + }, +} - end_move() - end_strategy_card() +function end_george_washington_captured() { + /* Restore previous state. */ + game.state = game.save.state + game.count = game.save.count + game.active = game.save.active + delete game.save +} + +function do_event(c) { + let card = CARDS[c] + if (card.event in events) + events[card.event](c, card) + else + throw new Error("Event not implemented yet: " + card.event) } /* WINTER ATTRITION PHASE */ -- cgit v1.2.3