diff options
author | Tor Andersson <tor@ccxvii.net> | 2023-12-15 02:58:17 +0100 |
---|---|---|
committer | Tor Andersson <tor@ccxvii.net> | 2024-01-08 16:36:48 +0100 |
commit | b83115ce40d88c789fbbd5a891057e14d4cf553b (patch) | |
tree | fbccd0059aa41bc05ff7e97b2a5aaa3c2980aca1 /rules.js | |
parent | 4640a9affe7633628ce95de0bb5640e30dd71039 (diff) | |
download | table-battles-b83115ce40d88c789fbbd5a891057e14d4cf553b.tar.gz |
stick shift special
Diffstat (limited to 'rules.js')
-rw-r--r-- | rules.js | 192 |
1 files changed, 182 insertions, 10 deletions
@@ -138,6 +138,9 @@ exports.view = function (state, player) { view.hits2 = game.hits2 } + if (game.shift) + view.shift = game.shift + if (game.state === "game_over") { view.prompt = game.victory } else if (player !== game.active) { @@ -316,19 +319,19 @@ const S43_VILLARS_LEFT = find_card(43, "Villars's Left") const S43_BROGLIE = find_card(43, "Broglie") const S43_PRINCE_DE_TINGRY = find_card(43, "Prince de Tingry") -const S44_HOTHENFRIEDBERG = find_scenario(44) +const S44_HOHENFRIEDBERG = find_scenario(44) const S44_CHARLES = find_card(44, "Charles") const S44_FREDERICK_II = find_card(44, "Frederick II") const S44_BAYREUTH_DRAGOONS = find_card(44, "Bayreuth Dragoons") const S44_LEOPOLDS_L = find_card(44, "Leopold's Left") const S44_LEOPOLDS_C = find_card(44, "Leopold's Center") const S44_LEOPOLDS_R = find_card(44, "Leopold's Right") +const S44_DU_MOULIN = find_card(44, "Du Moulin") +const S44_SAXON_HORSE = find_card(44, "Saxon Horse") // === SETUP === exports.setup = function (seed, scenario, options) { - // TODO: "Random" - scenario = parseInt(scenario) scenario = data.scenarios.findIndex(s => s.number === scenario) if (scenario < 0) @@ -381,6 +384,10 @@ exports.setup = function (seed, scenario, options) { hits2: 0, } + // Charles Alexander of Lorraine -- shift special + if (info.number >= 44 && info.number <= 49) + game.shift = [] + function setup_formation(front, reserve, c) { let card = data.cards[c] if (card.reserve) @@ -509,6 +516,21 @@ function set_sticks(c, n) { map_set(game.sticks, c, n) } +function get_shift_sticks(c) { + if (game.shift) + return map_get(game.shift, c, 0) + return 0 +} + +function set_shift_sticks(c, n) { + if (game.shift) { + if (n) + map_set(game.shift, c, n) + else + map_delete(game.shift, c) + } +} + function remove_sticks(c, n) { let p = find_card_owner(c) let old = get_sticks(c) @@ -570,22 +592,26 @@ function eliminate_card(c) { function rout_card(c) { let p = find_card_owner(c) game.lost[p] += get_sticks(c) + game.lost[p] += get_shift_sticks(c) log(c + " routed.") eliminate_card(c) } function pursue_card(c) { log(c + " pursued.") + game.lost[p] += get_shift_sticks(c) // TODO ? eliminate_card(c) } function retire_card(c) { log(c + " retired.") + game.lost[p] += get_shift_sticks(c) // TODO ? eliminate_card(c) } function remove_card(c) { log(c + " removed.") + game.lost[p] += get_shift_sticks(c) // TODO ? eliminate_card(c) } @@ -1654,6 +1680,12 @@ function is_reaction(c, a) { } function is_mandatory_reaction(c, a) { + + if (game.scenario === S44_HOHENFRIEDBERG) { + if (c === S44_DU_MOULIN && game.selected === S44_SAXON_HORSE) + return false + } + return ( a.requirement !== "Voluntary" && a.requirement !== "Pair, Voluntary" @@ -1746,6 +1778,11 @@ function can_take_any_action() { } } + if (can_shift_any_infantry()) + return true + if (can_shift_any_cavalry()) + return true + return false } @@ -1767,12 +1804,25 @@ function count_cards_remaining_from_wing(w) { } function goto_start_turn() { + let p = player_index() + if (check_impossible_to_attack_victory()) return + // TODO: manual step to shift? + if (game.shift) { + for (let c of game.front[p]) { + let n = get_shift_sticks(c) + if (n > 0) { + set_sticks(c, get_sticks(c) + n) + set_shift_sticks(c, 0) + } + } + } + if (game.scenario === S25_WHEATFIELD) { // Rout Stony Hill at start of Union turn if it is the only Blue card left. - if (player_index() === 1) { + if (p === 1) { if (is_card_in_play(S25_STONY_HILL)) { if (count_cards_remaining_from_wing(BLUE) === 1) { game.state = "s25_stony_hill" @@ -1782,8 +1832,8 @@ function goto_start_turn() { } } - if (game.scenario === S44_HOTHENFRIEDBERG) { - if (player_index() === 1) { + if (game.scenario === S44_HOHENFRIEDBERG) { + if (p === 1) { let have_inf_or_cav = false for (let c of game.front[1]) if (is_infantry(c) || is_cavalry(c)) @@ -1865,6 +1915,9 @@ states.action = { } } + if (can_shift_any_infantry() || can_shift_any_cavalry()) + view.actions.shift = 1 + if (game.scenario === S40_CHIARI) { if (player_index() === 1) { if (s40_can_take_cassines_action(S40_CASSINES_I, S40_NIGRELLI, S40_KRIECHBAUM)) @@ -1915,6 +1968,10 @@ states.action = { goto_roll_phase() roll_dice_in_pool() }, + shift() { + push_undo() + game.state = "shift_from" + }, card(c) { push_undo() @@ -1941,6 +1998,87 @@ states.action = { } } +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 + } + } + return n > 1 && m > 0 + } + return false +} + +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 + } + } + return n > 1 && m > 0 + } + return false +} + +states.shift_from = { + prompt() { + view.prompt = "Shift sticks from one Formation to another." + let p = player_index() + if (can_shift_any_infantry()) + for (let c of game.front[p]) + if (is_infantry(c) && get_sticks(c) > 1) + gen_action_card(c) + if (can_shift_any_cavalry()) + for (let c of game.front[p]) + if (is_cavalry(c) && get_sticks(c) > 1) + gen_action_card(c) + }, + card(c) { + game.selected = c + game.target2 = -1 + game.state = "shift_to" + }, +} + +states.shift_to = { + prompt() { + view.prompt = "Shift sticks from " + card_name(game.selected) + "." + let p = player_index() + if (game.target2 < 0) { + if (is_infantry(game.selected)) + for (let c of game.front[p]) + if (c !== game.selected && is_infantry(c)) + gen_action_card(c) + if (is_cavalry(game.selected)) + for (let c of game.front[p]) + if (c !== game.selected && is_cavalry(c)) + gen_action_card(c) + } else { + gen_action_card(game.target2) + view.actions.next = 1 + } + }, + card(c) { + game.target2 = c + set_sticks(game.selected, get_sticks(game.selected) - 1) + set_shift_sticks(game.target2, get_shift_sticks(game.target2) + 1) + if (get_sticks(game.selected) === 1) + this.next() + }, + next() { + // TODO: skip action phase? + end_action_phase() + }, +} + states.s40_cassines = { prompt() { view.prompt = "Cassines: Move one unit stick to this card." @@ -2066,7 +2204,7 @@ function find_first_target_of_command(c, a) { return S37_THE_FOG } - if (game.scenario === S44_HOTHENFRIEDBERG) { + if (game.scenario === S44_HOHENFRIEDBERG) { if (c === S44_CHARLES) { if (game.reserve[1].length > 0) return game.reserve[1] @@ -2092,7 +2230,7 @@ function find_first_target_of_command(c, a) { function find_all_targets_of_command(c, a) { - if (game.scenario === S44_HOTHENFRIEDBERG) { + if (game.scenario === S44_HOHENFRIEDBERG) { if (c === S44_CHARLES) { return game.reserve[1].slice() } @@ -2268,7 +2406,7 @@ function update_attack1(direct) { game.hits *= 2 } - if (game.scenario === S44_HOTHENFRIEDBERG) { + if (game.scenario === S44_HOHENFRIEDBERG) { if (game.target === S44_CHARLES) { if (game.selected === S44_LEOPOLDS_L || game.selected === S44_LEOPOLDS_C || game.selected === S44_LEOPOLDS_R) game.self = 0 @@ -2406,6 +2544,13 @@ function resume_attack() { if (game.target2 >= 0) remove_sticks(game.target2, game.hits2) + if (game.scenario === S44_HOHENFRIEDBERG) { + // remove after first attack + if (game.selected === S44_BAYREUTH_DRAGOONS) { + remove_card(S44_BAYREUTH_DRAGOONS) + } + } + end_action_phase() } @@ -2465,7 +2610,7 @@ states.command = { } } - if (game.scenario === S44_HOTHENFRIEDBERG) { + if (game.scenario === S44_HOHENFRIEDBERG) { // one at a time if (game.selected === S44_CHARLES || game.selected === S44_FREDERICK_II) { pay_for_action(game.selected) @@ -2872,6 +3017,8 @@ function get_attack_hits(c, a) { return 1 + count_dice_on_card(c) case "2 hits, PLUS 1 hit per die. 1 self per action.": return 2 + count_dice_on_card(c) + case "Two hits per die.": + return 2 * count_dice_on_card(c) case "2 hits.": return 2 case "5 hits.": @@ -2890,6 +3037,7 @@ function get_attack_self(c, a) { case "1 hit per die. Ignore first target until it comes out of Reserve.": case "1 hit per die (plus dice from E. Phalanx).": case "1 hit per pair.": + case "Two hits per die.": case "2 hits.": case "5 hits.": return 0 @@ -3296,6 +3444,13 @@ function array_insert(array, index, item) { array[index] = item } +function array_remove_pair(array, index) { + let n = array.length + for (let i = index + 2; i < n; ++i) + array[i - 2] = array[i] + array.length = n - 2 +} + function array_insert_pair(array, index, key, value) { for (let i = array.length; i > index; i -= 2) { array[i] = array[i-2] @@ -3411,3 +3566,20 @@ function map_set(map, key, value) { } array_insert_pair(map, a<<1, key, value) } + +function map_delete(map, item) { + let a = 0 + let b = (map.length >> 1) - 1 + while (a <= b) { + let m = (a + b) >> 1 + let x = map[m<<1] + if (item < x) + b = m - 1 + else if (item > x) + a = m + 1 + else { + array_remove_pair(map, m<<1) + return + } + } +} |