diff options
author | Tor Andersson <tor@ccxvii.net> | 2023-12-15 17:48:41 +0100 |
---|---|---|
committer | Tor Andersson <tor@ccxvii.net> | 2024-01-08 16:36:48 +0100 |
commit | 0cafa345c23c1bea2bc95d7df99e1d07d90bf14f (patch) | |
tree | dd4a043c2e7b92d69679c7c03419a6d39d8a7a72 /rules.js | |
parent | 096d811c5835c0d97dd687a68465e6d68adac675 (diff) | |
download | table-battles-0cafa345c23c1bea2bc95d7df99e1d07d90bf14f.tar.gz |
Prague. Breslau. Leuthen.
Diffstat (limited to 'rules.js')
-rw-r--r-- | rules.js | 223 |
1 files changed, 196 insertions, 27 deletions
@@ -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.": |