diff options
-rw-r--r-- | play.js | 15 | ||||
-rw-r--r-- | rules.js | 263 |
2 files changed, 183 insertions, 95 deletions
@@ -437,7 +437,7 @@ function get_static_xy(s) { let x, y if (s >= 66) { x = data.spaces[s].x - y = data.spaces[s].y + y = data.spaces[s].y + 60 } else { x = data.spaces[s].x - 20 y = data.spaces[s].y + 15 @@ -568,7 +568,7 @@ function on_update() { if (s === NOWHERE) continue - let pos = ui.layout_static[s] + let pos = (s < 66) ? ui.layout_static[s] : get_static_xy(s) if (view.move && view.move.who === g) pos = ui.layout_move if (view.react && view.react.who === g) @@ -576,6 +576,9 @@ function on_update() { let [ x, y ] = pos + if (s >= 66) + y -= 60 + if (view.move && view.move.who === g) { ui.generals[g].classList.add("selected") } else if (view.react && view.react.who === g) { @@ -587,13 +590,13 @@ function on_update() { let offset = general_offset(g) x += offset * (45 + 9) } else { + let wrap = (s === 66 ? 3 : 4) let total = general_total(g) let offset = general_offset(g) - let off_x = offset % 3 - let off_y = offset / 3 | 0 - let ctr_x = total > 3 ? 3 : total + let off_x = offset % wrap + let off_y = offset / wrap | 0 + let ctr_x = total > wrap ? wrap : total off_x -= (ctr_x - 1) / 2 - // off_y -= (total / 3 | 0) / 2 x += off_x * (45 + 9) y += off_y * (45 + 9) y -= 15 @@ -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, } |