summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2023-12-15 17:48:41 +0100
committerTor Andersson <tor@ccxvii.net>2024-01-08 16:36:48 +0100
commit0cafa345c23c1bea2bc95d7df99e1d07d90bf14f (patch)
treedd4a043c2e7b92d69679c7c03419a6d39d8a7a72
parent096d811c5835c0d97dd687a68465e6d68adac675 (diff)
downloadtable-battles-0cafa345c23c1bea2bc95d7df99e1d07d90bf14f.tar.gz
Prague. Breslau. Leuthen.
-rw-r--r--rules.js223
1 files changed, 196 insertions, 27 deletions
diff --git a/rules.js b/rules.js
index 9455fc0..e82cf86 100644
--- a/rules.js
+++ b/rules.js
@@ -338,6 +338,27 @@ const S46_ROCOUX = find_scenario(46)
const S46_AUSTRIANS = find_card(46, "Austrians")
const S46_THE_MOUTH_OF_HELL = find_card(46, "The Mouth of Hell")
+const S47_PRAGUE = find_scenario(47)
+const S47_BROWNE = find_card(47, "Browne")
+const S47_SCHWERIN = find_card(47, "Schwerin")
+const S47_CHARLES_LORRAINE = find_card(47, "Charles Lorraine")
+
+const S48_BRESLAU = find_scenario(48)
+const S48_AUSTRIAN_GUNS = find_card(48, "Austrian Guns")
+const S48_PRUSSIAN_GUNS = find_card(48, "Prussian Guns")
+const S48_BEVERN = find_card(48, "Bevern")
+const S48_GRENZERS = find_card(48, "Grenzers")
+
+const S49_LEUTHEN = find_scenario(49)
+const S49_LUCCHESI = find_card(49, "Lucchesi")
+const S49_RETZOW = find_card(49, "Retzow")
+const S49_COLLOREDO = find_card(49, "Colloredo")
+const S49_NADASDY = find_card(49, "Nadasdy")
+const S49_BORNE = find_card(49, "Borne")
+const S49_FEINT = find_card(49, "Feint")
+const S49_POOR_CHARLES = find_card(49, "Poor Charles :-(")
+const S49_THE_LEUTHEN_CHORALE = find_card(49, "The Leuthen Chorale")
+
// === SETUP ===
exports.setup = function (seed, scenario, options) {
@@ -591,7 +612,8 @@ function take_wild_die(from, to) {
function eliminate_card(c) {
remove_dice(c)
- remove_cubes(c, 3)
+ map_delete(game.cubes, c)
+ map_delete(game.sticks, c)
set_delete(game.front[0], c)
set_delete(game.front[1], c)
set_delete(game.reserve[0], c)
@@ -607,20 +629,30 @@ function rout_card(c) {
}
function pursue_card(c) {
+ let p = find_card_owner(c)
log(c + " pursued.")
game.lost[p] += get_shift_sticks(c) // TODO ?
eliminate_card(c)
}
function retire_card(c) {
+ let p = find_card_owner(c)
log(c + " retired.")
game.lost[p] += get_shift_sticks(c) // TODO ?
eliminate_card(c)
}
function remove_card(c) {
+ let p = find_card_owner(c)
log(c + " removed.")
+
game.lost[p] += get_shift_sticks(c) // TODO ?
+
+ if (game.scenario === S49_LEUTHEN) {
+ if (c === S49_BORNE)
+ game.lost[0] += get_sticks(S49_BORNE)
+ }
+
eliminate_card(c)
}
@@ -753,6 +785,11 @@ function get_tactical_victory_points(p) {
n += get_sticks(S46_AUSTRIANS)
}
+ if (game.scenario === S47_PRAGUE) {
+ if (p === 1)
+ n += get_sticks(S47_CHARLES_LORRAINE)
+ }
+
return n
}
@@ -771,6 +808,11 @@ function check_victory() {
return goto_game_over(P1, "Eugene is able to cross!")
}
+ if (game.scenario === S49_LEUTHEN) {
+ if (is_removed_from_play(S49_NADASDY) && is_card_in_reserve(S49_COLLOREDO))
+ return goto_game_over(P1, "Nadasdy routed before Colloredo entered play.")
+ }
+
if (game.morale[0] === 0)
return goto_game_over(P2, player_name(0) + " has run out of morale!")
if (game.morale[1] === 0)
@@ -804,6 +846,10 @@ function is_pool_die_range(i, lo, hi) {
return false
}
+function placed_any_dice() {
+ return game.placed.length > 0
+}
+
function placed_any_dice_on_wing(w) {
for (let i = 0; i < game.placed.length; i += 2) {
let c = game.placed[i]
@@ -820,12 +866,21 @@ function is_straight_4_or_3(c) {
else
return 3
}
+
if (game.scenario === S31_NEWBURY_1ST) {
if (is_card_in_play(S31_SKIPPON))
return 4
else
return 3
}
+
+ if (game.scenario === S48_BRESLAU) {
+ if (game.rolled >= 5)
+ return 4
+ else
+ return 3
+ }
+
throw new Error("Missing rule for Straight 3/4 choice")
}
@@ -1376,7 +1431,7 @@ states.skip_action = {
prompt() {
view.prompt = "Skipped action phase; roll the dice in your pool."
- if (can_shift_any_infantry() || can_shift_any_cavalry())
+ if (can_shift())
view.actions.shift = 1
view.actions.roll = 1
@@ -1388,6 +1443,7 @@ states.skip_action = {
},
roll() {
clear_undo()
+ goto_roll_phase()
roll_dice_in_pool()
},
}
@@ -1524,6 +1580,44 @@ function end_roll_phase() {
}
}
+ if (game.scenario === S47_PRAGUE) {
+ if (player_index() === 0) {
+ if (is_card_in_play(S47_SCHWERIN) && !placed_any_dice()) {
+ game.target2 = S47_SCHWERIN
+ game.state = "s47_browne_and_schwerin"
+ return
+ }
+ }
+ if (player_index() === 1) {
+ if (is_card_in_play(S47_BROWNE) && !placed_any_dice()) {
+ game.target2 = S47_BROWNE
+ game.state = "s47_browne_and_schwerin"
+ return
+ }
+ }
+ }
+
+ if (game.scenario === S48_BRESLAU) {
+ if (player_index() === 0) {
+ if (is_card_in_play(S48_AUSTRIAN_GUNS)) {
+ if (get_cubes(S48_AUSTRIAN_GUNS) === 0 && get_cubes(S48_PRUSSIAN_GUNS) === 2) {
+ game.target2 = S48_AUSTRIAN_GUNS
+ game.state = "s48_artillery_duel"
+ return
+ }
+ }
+ }
+ if (player_index() === 1) {
+ if (is_card_in_play(S48_PRUSSIAN_GUNS)) {
+ if (get_cubes(S48_PRUSSIAN_GUNS) === 0 && get_cubes(S48_AUSTRIAN_GUNS) === 2) {
+ game.target2 = S48_PRUSSIAN_GUNS
+ game.state = "s48_artillery_duel"
+ return
+ }
+ }
+ }
+ }
+
end_turn()
}
@@ -1585,6 +1679,30 @@ states.s41_clerambault = {
},
}
+states.s47_browne_and_schwerin = {
+ prompt() {
+ view.prompt = "Remove " + card_name(game.target2) + "."
+ gen_action_card(game.target2)
+ },
+ card(c) {
+ remove_card(game.target2)
+ game.target2 = -1
+ end_turn()
+ },
+}
+
+states.s48_artillery_duel = {
+ prompt() {
+ view.prompt = "Artillery Duel: Remove " + card_name(game.target2) + "."
+ gen_action_card(game.target2)
+ },
+ card(c) {
+ remove_card(game.target2)
+ game.target2 = -1
+ end_turn()
+ },
+}
+
function end_turn() {
clear_undo()
@@ -1789,6 +1907,14 @@ function can_take_action(c, a, ix) {
}
}
+ if (game.scenario === S49_LEUTHEN) {
+ if (c === S49_POOR_CHARLES) {
+ // Cannot command if dice on Feint
+ if (has_any_dice_on_card(S49_FEINT))
+ return false
+ }
+ }
+
if (a.type === "Bombard" || a.type === "Attack" || a.type === "Command") {
if (data.cards[c].special)
return check_cube_requirement(c, a.requirement)
@@ -1836,9 +1962,7 @@ function can_take_any_action() {
}
}
- if (can_shift_any_infantry())
- return true
- if (can_shift_any_cavalry())
+ if (can_shift())
return true
return false
@@ -1973,7 +2097,7 @@ states.action = {
}
}
- if (can_shift_any_infantry() || can_shift_any_cavalry())
+ if (can_shift())
view.actions.shift = 1
if (game.scenario === S40_CHIARI) {
@@ -2056,34 +2180,42 @@ states.action = {
}
}
+function can_shift() {
+ if (!game.shift)
+ return false
+
+ if (game.scenario === S49_LEUTHEN) {
+ if (player_index() === 1) {
+ if (has_any_dice_on_card(S49_FEINT))
+ return false
+ }
+ }
+
+ return can_shift_any_infantry() || can_shift_any_cavalry()
+}
+
function can_shift_any_infantry() {
- if (game.shift) {
- let n = 0, m = 0
- for (let c of game.front[player_index()]) {
- if (is_infantry(c)) {
- if (get_sticks(c) > 1)
- ++m
- ++n
- }
+ let n = 0, m = 0
+ for (let c of game.front[player_index()]) {
+ if (is_infantry(c)) {
+ if (get_sticks(c) > 1)
+ ++m
+ ++n
}
- return n > 1 && m > 0
}
- return false
+ return n > 1 && m > 0
}
function can_shift_any_cavalry() {
- if (game.shift) {
- let n = 0, m = 0
- for (let c of game.front[player_index()]) {
- if (is_cavalry(c)) {
- if (get_sticks(c) > 1)
- ++m
- ++n
- }
+ let n = 0, m = 0
+ for (let c of game.front[player_index()]) {
+ if (is_cavalry(c)) {
+ if (get_sticks(c) > 1)
+ ++m
+ ++n
}
- return n > 1 && m > 0
}
- return false
+ return n > 1 && m > 0
}
states.shift_from = {
@@ -2472,6 +2604,12 @@ function update_attack1(direct) {
}
}
+ if (game.scenario === S49_LEUTHEN) {
+ if (direct)
+ if (game.selected === S49_LUCCHESI && game.target === S49_RETZOW)
+ game.hits *= 2
+ }
+
// Oblique Attack (CAL expansion rule)
if (is_infantry(game.selected)) {
if (get_sticks(game.selected) >= get_sticks(game.target) + 3)
@@ -2540,6 +2678,11 @@ states.attack = {
}
}
+ if (game.scenario === S49_LEUTHEN) {
+ if (player_index() === 0)
+ gen_action_dice_on_card(S49_THE_LEUTHEN_CHORALE)
+ }
+
view.actions.attack = 1
},
attack() {
@@ -2591,6 +2734,16 @@ states.attack = {
}
}
+ if (game.scenario === S49_LEUTHEN) {
+ if (from === S49_THE_LEUTHEN_CHORALE) {
+ take_all_dice(from, game.selected)
+ game.target2 = S49_THE_LEUTHEN_CHORALE
+ update_attack1(true)
+ update_attack2()
+ return
+ }
+ }
+
throw new Error("no handler for taking dice from other card")
}
}
@@ -2600,7 +2753,7 @@ function resume_attack() {
remove_sticks(game.selected, game.self)
remove_sticks(game.target, game.hits)
- if (game.target2 >= 0)
+ if (game.target2 >= 0 && game.hits2 > 0)
remove_sticks(game.target2, game.hits2)
if (game.scenario === S44_HOHENFRIEDBERG) {
@@ -2610,6 +2763,13 @@ function resume_attack() {
}
}
+ if (game.scenario === S49_LEUTHEN) {
+ // remove after using
+ if (game.target2 === S49_THE_LEUTHEN_CHORALE) {
+ remove_card(S49_THE_LEUTHEN_CHORALE)
+ }
+ }
+
end_action_phase()
}
@@ -3020,6 +3180,13 @@ function goto_counterattack(c, a) {
break
}
+ if (game.scenario === S48_BRESLAU) {
+ if (player_index() === 1) {
+ if (has_any_dice_on_card(S48_BEVERN))
+ game.self += 1
+ }
+ }
+
update_attack2()
game.state = "counterattack"
@@ -3065,6 +3232,7 @@ function get_attack_hits(c, a) {
case "1 hit per die. 1 self per action (but see Bayonets!).":
case "1 hit per die (2 hits per die vs. Blenheim). 1 self per action.":
case "1 hit per die (two per die vs. Villars's Left). 1 self per action.":
+ case "1 hit per die versus Driesen. Two hits per die versus Retzow.":
case "1 hit per die. 1 self per action. If reduced to one stick, no self hits.":
case "1 hit per die. 1 self per action. You CHOOSE the target.":
return count_dice_on_card(c)
@@ -3117,6 +3285,7 @@ function get_attack_self(c, a) {
case "1 hit per die. 1 self per action (but see Bayonets!).":
case "1 hit per die (2 hits per die vs. Blenheim). 1 self per action.":
case "1 hit per die (two per die vs. Villars's Left). 1 self per action.":
+ case "1 hit per die versus Driesen. Two hits per die versus Retzow.":
case "1 hit per die. 1 self per action. You CHOOSE the target.":
case "1 hit per pair. 1 self per action.":
case "1 hit, PLUS 1 hit per die. 1 self per action.":