summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2024-06-21 19:34:05 +0200
committerTor Andersson <tor@ccxvii.net>2024-08-21 00:28:20 +0200
commit3ac604faaa4fe2c6d60f69f27a0e9885525cf0a0 (patch)
tree72428870b3f191c2c4695ddefcd5da47731fc44d
parent98acb0d5392f9e66e26e0ab0c4fa2894beb3a2f5 (diff)
downloadwashingtons-war-3ac604faaa4fe2c6d60f69f27a0e9885525cf0a0.tar.gz
streamline ui
-rw-r--r--play.js2
-rw-r--r--rules.js1320
2 files changed, 679 insertions, 643 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,28 +2005,132 @@ 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()
+ else if (game.move.count === 0)
+ end_move()
+}
+
+function end_move() {
+ let where = location_of_general(game.move.who)
+ if (has_general_moved(game.move.who)) {
+ mark_moved_british_cu(where, game.move.carry_british)
+ mark_moved_american_cu(where, game.move.carry_american)
+ 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) {
+ if (set_has(SPACES[from].path, to))
+ return "path"
+ if (set_has(SPACES[from].wilderness, to))
+ return "wilderness"
+ return "sea"
+}
+
+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")
+ if (game.move.carry_british < 5 && game.move.carry_british < count_unmoved_british_cu(where))
+ gen_action("pickup_british_cu")
+ } else {
+ let carry_total = game.move.carry_french + game.move.carry_american
+ if (game.move.carry_french > 0)
+ gen_action("drop_french_cu")
+ if (game.move.carry_american > 0)
+ gen_action("drop_american_cu")
+ if (carry_total < 5 && game.move.carry_french < count_unmoved_french_cu(where))
+ gen_action("pickup_french_cu")
+ if (carry_total < 5 && game.move.carry_american < count_unmoved_american_cu(where))
+ gen_action("pickup_american_cu")
+ }
+}
+
+function movement_cost(from, to) {
+ switch (path_type(from, to)) {
+ case "sea":
+ return 4 /* must be a sea connection if no direct path */
+ case "wilderness":
+ return 3
+ case "path":
+ return 1
+ default:
+ throw "IMPOSSIBLE"
+ }
+}
+
+function gen_move_general() {
+ let from = location_of_general(game.move.who)
+ let alone = game.move.carry_british + game.move.carry_american + game.move.carry_french === 0
+ for (let to of SPACES[from].adjacent) {
+ let mp = 1
+ if (path_type(from, to) === "wilderness") {
+ if ((from === QUEBEC && to === FALMOUTH) || (to === QUEBEC && from === FALMOUTH))
+ if (game.move.who !== ARNOLD)
+ continue
+ mp = 3
+ }
+
+ if (alone) {
+ if (has_enemy_cu(to))
+ continue
+ if (has_enemy_pc(to))
+ continue
+ // TODO: more robust check for not stopping (or allow undo in case he gets stuck)
+ if (has_enemy_general(to) && game.count - mp === 0)
+ continue
+ }
+
+ if (game.move.mobility && has_enemy_cu(to)) {
+ if (game.move.count - mp >= 1)
+ gen_action_space(to)
+ } else {
+ if (game.move.count - mp >= 0)
+ gen_action_space(to)
+ }
+ }
+ if (game.active === P_BRITAIN && game.count === 4) {
+ if (is_non_blockaded_port(from)) {
+ for (let to of all_spaces) {
+ if (to !== from) {
+ if (is_non_blockaded_port(to)) {
+ if (!has_american_pc(to) && !has_american_or_french_cu(to)) {
+ // don't leave alone
+ if (alone && has_enemy_general(to))
+ continue
+ // TODO: duplicate action if can move by normal road
+ gen_action_space(to)
+ }
+ }
+ }
+ }
+ }
}
}
+/* INTERCEPT */
+
function can_intercept_to(to) {
for (let space of SPACES[to].adjacent) {
if (has_american_army(space)) {
@@ -2037,7 +2160,7 @@ function goto_intercept() {
states.intercept = {
prompt() {
- view.prompt = "Intercept " + game.move.who + " in " + game.move.to + "?"
+ view.prompt = "Intercept " + game.move.who + " at " + game.move.to + "?"
view.actions.pass = 1
gen_intercept()
},
@@ -2060,6 +2183,10 @@ states.intercept = {
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
@@ -2083,99 +2210,461 @@ function end_intercept() {
resume_moving()
}
-function end_move() {
- let where = location_of_general(game.move.who)
- if (has_general_moved(game.move.who)) {
- mark_moved_british_cu(where, game.move.carry_british)
- mark_moved_american_cu(where, game.move.carry_american)
- mark_moved_french_cu(where, game.move.carry_french)
+/* RETREAT BEFORE BATTLE */
+
+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
+}
+
+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,
}
- delete game.move
+
+ if (game.active === P_BRITAIN && can_retreat_before_battle())
+ goto_retreat_before_battle()
+ else
+ goto_play_attacker_battle_card()
}
-function path_type(from, to) {
- if (set_has(SPACES[from].path, to))
- return "path"
- if (set_has(SPACES[from].wilderness, to))
- return "wilderness"
- return "sea"
+function goto_retreat_before_battle() {
+ game.active = P_AMERICA
+ game.state = "retreat_before_battle"
}
-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")
- if (game.move.carry_british < 5 && game.move.carry_british < count_unmoved_british_cu(where))
- gen_action("pickup_british_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()
+ },
+}
+
+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 {
- let carry_total = game.move.carry_french + game.move.carry_american
- if (game.move.carry_french > 0)
- gen_action("drop_french_cu")
- if (game.move.carry_american > 0)
- gen_action("drop_american_cu")
- if (carry_total < 5 && game.move.carry_french < count_unmoved_french_cu(where))
- gen_action("pickup_french_cu")
- if (carry_total < 5 && game.move.carry_american < count_unmoved_american_cu(where))
- gen_action("pickup_american_cu")
+ end_remove_general_after_retreat_before_battle()
}
}
-function movement_cost(from, to) {
- switch (path_type(from, to)) {
- case "sea":
- return 4 /* must be a sea connection if no direct path */
- case "wilderness":
- return 3
- case "path":
- return 1
- default:
- throw "IMPOSSIBLE"
+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()
+ },
+}
+
+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()
}
}
-function gen_move_general() {
- let from = location_of_general(game.move.who)
- let alone = game.move.carry_british + game.move.carry_american + game.move.carry_french === 0
- for (let to of SPACES[from].adjacent) {
- let mp = 1
- if (path_type(from, to) === "wilderness") {
- if ((from === QUEBEC && to === FALMOUTH) || (to === QUEBEC && from === FALMOUTH))
- if (game.move.who !== ARNOLD)
- continue
- mp = 3
+/* BATTLE CARDS */
+
+function goto_play_attacker_battle_card() {
+ game.active = game.combat.attacker
+ game.state = "play_attacker_battle_card"
+}
+
+states.play_attacker_battle_card = {
+ prompt() {
+ view.prompt = "Attack: Play or discard event for DRM."
+ 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
+ }
+ 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() {
+ goto_play_defender_battle_card()
+ },
+}
- if (alone) {
- if (has_enemy_cu(to))
- continue
- if (has_enemy_pc(to))
- continue
- // TODO: more robust check for not stopping (or allow undo in case he gets stuck)
- if (has_enemy_general(to) && game.count - mp === 0)
- continue
+function goto_play_defender_battle_card() {
+ game.state = "play_defender_battle_card"
+ game.active = ENEMY[game.combat.attacker]
+}
+
+states.play_defender_battle_card = {
+ prompt() {
+ view.prompt = "Defend: Play or discard event for DRM."
+ 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()
+ },
+ 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() {
+ resolve_battle()
+ },
+}
- if (game.move.mobility && has_enemy_cu(to)) {
- if (game.move.count - mp >= 1)
- gen_action_space(to)
+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 {
- if (game.move.count - mp >= 0)
- gen_action_space(to)
+ 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
+ }
}
}
- if (game.active === P_BRITAIN && game.count === 4) {
+}
+
+/* RESOLVE BATTLE */
+
+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
+}
+
+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
+}
+
+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 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
+}
+
+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
+}
+
+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
+}
+
+function resolve_battle() {
+ let a_log = []
+ let b_log = []
+
+ 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
+
+ if (b_g !== NOBODY)
+ b_log.push("General: " + b_g)
+ if (a_g !== NOBODY)
+ a_log.push("General: " + a_g)
+
+ 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)
+ }
+
+ 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)
+ }
+
+ 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")
+
+ 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
+ }
+ }
+ 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")
+
+ 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
+ }
+
+ 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))
+
+ 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
+
+ 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)
+ }
+
+ game.combat.british_losses = apply_british_combat_losses(b_lost_cu)
+ let american_losses = apply_american_and_french_combat_losses(a_lost_cu)
+
+ b_log.push("Losses: " + game.combat.british_losses + " CU")
+ a_log.push("Losses: " + american_losses + " CU")
+
+ // 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)
+ }
+
+ 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)
+
+ goto_retreat_after_battle(victor)
+}
+
+/* RETREAT AFTER BATTLE */
+
+function can_defender_retreat(to) {
+ if (to === game.move.from)
+ return false
+ if (has_enemy_pc(to))
+ return false
+ if (has_enemy_cu(to))
+ return false
+ return true
+}
+
+function can_attacker_retreat() {
+ let to = game.move.from
+ if (has_enemy_pc(to))
+ return false
+ if (has_enemy_cu(to))
+ return false
+ return true
+}
+
+function gen_defender_retreat() {
+ let from = game.move.to
+ for (let to of SPACES[from].adjacent) {
+ if (can_defender_retreat(to))
+ gen_action_space(to)
+ }
+ if (game.active === P_BRITAIN) {
+ let can_sea_retreat = false
if (is_non_blockaded_port(from)) {
+ if (is_fortified_port(from)) {
+ if (has_british_pc(from) && is_non_blockaded_port(from))
+ can_sea_retreat = true
+ } else {
+ can_sea_retreat = true
+ }
+ }
+ if (can_sea_retreat) {
for (let to of all_spaces) {
- if (to !== from) {
- if (is_non_blockaded_port(to)) {
- if (!has_american_pc(to) && !has_american_or_french_cu(to)) {
- // don't leave alone
- if (alone && has_enemy_general(to))
- continue
- // TODO: duplicate action if can move by normal road
- gen_action_space(to)
- }
+ if (to !== from && is_non_blockaded_port(to)) {
+ if (!has_american_pc(to) && !has_american_or_french_cu(to)) {
+ gen_action_space(to)
}
}
}
@@ -2183,6 +2672,84 @@ function gen_move_general() {
}
}
+function gen_attacker_retreat() {
+ if (can_attacker_retreat())
+ gen_action_space(game.move.from)
+}
+
+function end_retreat_before_battle() {
+ goto_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.retreat_after_battle = {
+ prompt() {
+ view.prompt = "Retreat after battle."
+ gen_action("surrender")
+ if (game.active === game.combat.attacker)
+ gen_attacker_retreat()
+ else
+ gen_defender_retreat()
+ },
+ 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()
+ },
+ surrender() {
+ logp("surrendered")
+
+ if (game.active === P_BRITAIN)
+ surrender_british_army(game.move.to)
+ else
+ surrender_american_army(game.move.to)
+
+ end_battle()
+ },
+}
+
+/* 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()
+}
+
/* CAMPAIGN */
function goto_campaign(c) {
@@ -2219,6 +2786,7 @@ events.remove_random_american_card = function (c, card) {
}
function remove_random_card(hand) {
+ clear_undo()
if (hand.length > 0) {
let i = random(hand.length)
let c = hand[i]
@@ -2280,7 +2848,7 @@ events.remove_british_pc_from = function (c, card) {
states.remove_british_pc_from = {
prompt() {
- view.prompt = "Remove British PC markers from " + game.where.join(", ") + ". " + game.count + " left."
+ view.prompt = "Remove British PC markers from " + game.where.map(x=>data.colony_name[x]).join(", ") + ". " + game.count + " left."
view.actions.pass = 1
gen_remove_british_pc_from(game.where)
},
@@ -2679,540 +3247,6 @@ function do_event(c) {
throw new Error("Event not implemented yet: " + card.event)
}
-/* BATTLE */
-
-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
-}
-
-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()
-}
-
-function goto_retreat_before_battle() {
- game.active = P_AMERICA
- game.state = "retreat_before_battle"
-}
-
-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()
- },
-}
-
-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_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()
- },
-}
-
-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()
- }
-}
-
-function can_defender_retreat(to) {
- if (to === game.move.from)
- return false
- if (has_enemy_pc(to))
- return false
- if (has_enemy_cu(to))
- return false
- return true
-}
-
-function can_attacker_retreat() {
- let to = game.move.from
- if (has_enemy_pc(to))
- return false
- if (has_enemy_cu(to))
- return false
- return true
-}
-
-function gen_defender_retreat() {
- let from = game.move.to
- for (let to of SPACES[from].adjacent) {
- if (can_defender_retreat(to))
- gen_action_space(to)
- }
- if (game.active === P_BRITAIN) {
- let can_sea_retreat = false
- if (is_non_blockaded_port(from)) {
- if (is_fortified_port(from)) {
- if (has_british_pc(from) && is_non_blockaded_port(from))
- can_sea_retreat = true
- } else {
- can_sea_retreat = true
- }
- }
- if (can_sea_retreat) {
- for (let to of all_spaces) {
- if (to !== from && is_non_blockaded_port(to)) {
- if (!has_american_pc(to) && !has_american_or_french_cu(to)) {
- gen_action_space(to)
- }
- }
- }
- }
- }
-}
-
-function gen_attacker_retreat() {
- if (can_attacker_retreat())
- gen_action_space(game.move.from)
-}
-
-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"
-}
-
-states.play_attacker_battle_card = {
- prompt() {
- view.prompt = "Attack: Play or discard event for DRM."
- 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
- }
- 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() {
- goto_play_defender_battle_card()
- },
-}
-
-function goto_play_defender_battle_card() {
- game.state = "play_defender_battle_card"
- game.active = ENEMY[game.combat.attacker]
-}
-
-states.play_defender_battle_card = {
- prompt() {
- view.prompt = "Defend: Play or discard event for DRM."
- 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()
- },
- 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() {
- resolve_battle()
- },
-}
-
-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
- }
- }
- }
-}
-
-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
-}
-
-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
-}
-
-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 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
-}
-
-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
-}
-
-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
-}
-
-function resolve_battle() {
- let a_log = []
- let b_log = []
-
- 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
-
- if (b_g !== NOBODY)
- b_log.push("General: " + b_g)
- if (a_g !== NOBODY)
- a_log.push("General: " + a_g)
-
- 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)
- }
-
- 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)
- }
-
- 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")
-
- 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
- }
- }
- 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")
-
- 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
- }
-
- 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))
-
- 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
-
- 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)
- }
-
- game.combat.british_losses = apply_british_combat_losses(b_lost_cu)
- let american_losses = apply_american_and_french_combat_losses(a_lost_cu)
-
- b_log.push("Losses: " + game.combat.british_losses + " CU")
- a_log.push("Losses: " + american_losses + " CU")
-
- // 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)
- }
-
- 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)
-
- goto_retreat_after_battle(victor)
-}
-
-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.retreat_after_battle = {
- prompt() {
- view.prompt = "Retreat after battle."
- gen_action("surrender")
- if (game.active === game.combat.attacker)
- gen_attacker_retreat()
- else
- gen_defender_retreat()
- },
- 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()
- },
- 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)
- },
-}
-
-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()
- end_strategy_card()
-}
-
/* WINTER ATTRITION PHASE */
function apply_single_winter_attrition(owner, space) {