From 189491d205c840fde568f3806b1358e15411459d Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Mon, 6 May 2024 01:12:43 +0200 Subject: bonus cards --- rules.js | 449 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 385 insertions(+), 64 deletions(-) (limited to 'rules.js') diff --git a/rules.js b/rules.js index 72165e7..aeecf6f 100644 --- a/rules.js +++ b/rules.js @@ -5,10 +5,11 @@ const VERSAILLES = "Versailles" var game, view, states = {} -exports.scenarios = [ "Standard" ] - +exports.scenarios = [ "Censorship", "Standard" ] exports.roles = [ COMMUNE, VERSAILLES ] +// === DATA === + const first_commune_cube = 0 const last_commune_cube = 17 const first_versailles_cube = 18 @@ -19,10 +20,14 @@ const last_commune_disc = 37 const first_versailles_disc = 38 const last_versailles_disc = 39 +const BONUS = 53 // offset to bonus cards + const crisis_names = [ "Escalation", "Tension", "Final Crisis" ] const card_names = [ "Initiative", + + // Strategy "Jules Ducatel", "The Murder of Vincenzini", "Brassardiers", @@ -64,6 +69,8 @@ const card_names = [ "Léon Gambetta", "Elihu Washburne", "Freemason Parade", + + // Objective "Paris Cannons", "Aux Barricades!", "Commune's Stronghold", @@ -76,6 +83,18 @@ const card_names = [ "Royalists Dissension", "Rise of Republicanism", "Legitimacy", + + // Censorship Strategy + "Louis Rossel", + "Gustave Flourens", + "Jean-Baptiste Clément", + "Elizabeth Dimitrieff", + "Paris Bombarded", + "Général Gallifet", + "Sapper Tactics", + "Georges Vaysset", + "Colonne Vendôme", + "Versailles' Left", ] const card_ops = [ @@ -88,6 +107,12 @@ const card_ops = [ 3, 3, 3, 3, 3, 3, 3, // Objective 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + // Commune - Censorship + 2, 1, 1, 1, + // Versailles - Censorship + 2, 1, 1, 1, + // Neutral - Censorship + 3, 3, ] const space_names = [ @@ -383,15 +408,15 @@ function is_objective_card(c) { } function is_commune_card(c) { - return c >= 18 && c <= 34 + return (c >= 18 && c <= 34) || (c >= (BONUS+1) && c <= (BONUS+4)) } function is_versailles_card(c) { - return c >= 1 && c <= 17 + return (c >= 1 && c <= 17) || (c >= (BONUS+5) && c <= (BONUS+8)) } function is_neutral_card(c) { - return c >= 35 && c <= 41 + return (c >= 35 && c <= 41) || (c >= (BONUS+9) && c <= (BONUS+10)) } function enemy_player() { @@ -423,7 +448,8 @@ function can_play_event(c) { return false // Cards 38 through 41 can only be played if you have the initiative - if (c >= 38 && c <= 41) + // Cards 62 through 63 + if ((c >= 38 && c <= 41) || (c >= 62 && c <= 63)) if (game.active !== game.initiative) return false @@ -454,6 +480,11 @@ function can_play_event(c) { if (game.snb) return false + // General Lullier - cannot be played if Louis Rossel has been played + if (c === 32) + if (game.louis_rossel) + return false + // Victor Hugo - must decrease both player momentums if (c === 38) if (game.red_momentum < 1 || game.blue_momentum < 1) @@ -562,6 +593,16 @@ function is_adjacent_to_control(here) { return false } +function is_adjacent_to_control_in_dimension(here, dim) { + console.log("CHK", space_names[here]) + for (let s of ADJACENT_TO[here]) { + console.log(" =>", space_names[s], is_control(s), dim.includes(s)) + if (is_control(s) && dim.includes(s)) + return true + } + return false +} + function is_commune_control_dimension(dim) { for (let s of dim) if (!is_commune_control(s)) @@ -580,11 +621,6 @@ function count_cubes(s) { return count_commune_cubes(s) + count_versailles_cubes(s) } -function for_each_cube(s, f) { - for_each_commune_cube(s, f) - for_each_versailles_cube(s, f) -} - function count_commune_cubes(s) { let n = 0 for (let p = first_commune_cube; p <= last_commune_cube; ++p) @@ -617,14 +653,6 @@ function count_versailles_discs(s) { return n } -function count_commune_pieces(s) { - return count_commune_cubes(s) + count_commune_discs(s) -} - -function count_versailles_pieces(s) { - return count_versailles_cubes(s) + count_versailles_discs(s) -} - function count_friendly_cubes(s) { if (game.active === COMMUNE) return count_commune_cubes(s) @@ -637,12 +665,6 @@ function count_friendly_discs(s) { return count_versailles_discs(s) } -function count_enemy_pieces(s) { - if (game.active === COMMUNE) - return count_versailles_pieces(s) - return count_commune_pieces(s) -} - function find_commune_cube(s) { for (let p = first_commune_cube; p <= last_commune_cube; ++p) if (game.pieces[p] === s) @@ -719,10 +741,6 @@ function has_enemy_disc(s) { return find_enemy_disc(s) >= 0 } -function has_enemy_piece(s) { - return has_enemy_cube(s) || has_enemy_disc(s) -} - function is_commune_cube(p) { return p >= first_commune_cube && p <= last_commune_cube } @@ -739,10 +757,6 @@ function is_versailles_disc(p) { return p >= first_versailles_disc && p <= last_versailles_disc } -function is_disc(p) { - return p >= 36 -} - function can_place_cube_in_any(list) { for (let s of list) if (can_place_cube(s)) @@ -779,6 +793,10 @@ function can_place_disc(s) { return count_friendly_discs(s) < 1 } +function can_remove_disc(s) { + return has_enemy_disc(s) +} + function can_replace_cube(s) { return has_enemy_cube(s) && can_place_cube(s) } @@ -888,13 +906,6 @@ function for_each_enemy_disc(s, f) { for_each_commune_disc(s, f) } -function for_each_friendly_disc(s, f) { - if (game.active === COMMUNE) - for_each_commune_disc(s, f) - else - for_each_versailles_disc(s, f) -} - function for_each_commune_cube(s, f) { for (let p = first_commune_cube; p <= last_commune_cube; ++p) if (game.pieces[p] === s) @@ -1498,15 +1509,15 @@ function end_crisis_breach() { delete game.breach_active delete game.breach_next switch (next) { - case "strategy": - end_crisis_breach_strategy() - break - case "pivotal": - end_crisis_breach_pivotal() - break - case "objective": - end_crisis_breach_objective() - break + case "strategy": + end_crisis_breach_strategy() + break + case "pivotal": + end_crisis_breach_pivotal() + break + case "objective": + end_crisis_breach_objective() + break } } @@ -1518,7 +1529,8 @@ function assess_commune_crisis_track(i, an) { game.red_breach = i + 1 log_h3("Commune - " + crisis_names[i]) if (i === 2 && game.blue_breach < 3) { - add_political_vp(COMMUNE, -1) + if (game.versailles_left !== COMMUNE) + add_political_vp(COMMUNE, -1) } } return b > 0 @@ -1534,7 +1546,8 @@ function assess_versailles_crisis_track(i, an) { game.blue_breach = i + 1 log_h3("Versailles - " + crisis_names[i]) if (i === 2 && game.red_breach < 3) { - add_political_vp(VERSAILLES, -1) + if (game.versailles_left !== VERSAILLES) + add_political_vp(VERSAILLES, -1) } } return b > 0 @@ -2106,7 +2119,6 @@ states.turncoat = { states.pivotal_done = { inactive: "confirm action", prompt() { - let dimension = DIMENSION_SPACES[game.where] view.prompt = "Pivotal Bonus Action in " + DIMENSION_NAME[game.where] + ": Done." view.actions.done = 1 }, @@ -2779,12 +2791,6 @@ function vm_replace_up_to() { goto_vm_replace() } -function vm_replace_different() { - game.vm.count = vm_operand(1) - game.vm.spaces = vm_operand_spaces(2).slice() // make a copy for safe mutation - game.state = "vm_replace_different" -} - function vm_replace() { game.vm.count = vm_operand(1) game.vm.spaces = vm_operand_spaces(2) @@ -2826,6 +2832,12 @@ function vm_place_disc() { goto_vm_place_disc() } +function vm_remove_disc() { + game.vm.count = 1 + game.vm.spaces = vm_operand_spaces(1) + goto_vm_remove_disc() +} + function vm_move_up_to() { game.vm.count = vm_operand(1) game.vm.a = vm_operand_spaces(2) @@ -2833,6 +2845,13 @@ function vm_move_up_to() { goto_vm_move() } +function vm_move_within() { + game.vm.spaces = vm_operand_spaces(1) + game.vm.a = [] + game.vm.b = [] + goto_vm_move_within() +} + // === EVENT STATES === states.vm_switch = { @@ -2887,6 +2906,11 @@ states.vm_switch = { game.vm.choice = "paris" vm_next() }, + forts() { + push_undo() + game.vm.choice = "forts" + vm_next() + }, } states.vm_increase_revolutionary_momentum = { @@ -3181,6 +3205,41 @@ states.vm_remove_own = { }, } +function can_vm_remove_disc() { + for (let s of game.vm.spaces) + if (can_remove_disc(s)) + return true + return false +} + +function goto_vm_remove_disc() { + if (can_vm_remove_disc()) + game.state = "vm_remove_disc" + else + vm_next() +} + +states.vm_remove_disc = { + inactive: "remove a disc", + prompt() { + event_prompt() + for (let s of game.vm.spaces) + if (can_remove_disc(s)) + for_each_enemy_disc(s, gen_action_piece) + }, + piece(p) { + push_undo() + let s = game.pieces[p] + if (game.active === COMMUNE) + log("Removed BD from S" + s + ".") + else + log("Removed RD from S" + s + ".") + remove_piece(p) + if (--game.vm.count === 0 || !can_vm_remove_disc()) + vm_next() + }, +} + function can_vm_move() { let from = false let to = false @@ -3235,6 +3294,61 @@ states.vm_move = { }, } +function can_vm_move_within() { + let max = 0 + for (let s of game.vm.spaces) { + let n = count_friendly_cubes(s) + if (n > max) + max = n + } + return max > 0 && max < 4 +} + +function goto_vm_move_within() { + game.who = -1 + if (can_vm_move_within()) + game.state = "vm_move_within" + else + vm_next() +} + +states.vm_move_within = { + inactive: "move a cube", + prompt() { + event_prompt() + view.actions.done = 1 + if (game.who < 0) { + for (let s of game.vm.spaces) + if (!game.vm.b.includes(s)) + for_each_friendly_cube(s, gen_action_piece) + } else { + view.selected_cube = game.who + for (let s of game.vm.spaces) + if (s !== game.pieces[game.who] && !game.vm.a.includes(s)) + if (count_friendly_cubes(s) < 4) + gen_action_space(s) + } + }, + piece(p) { + push_undo() + game.who = p + }, + space(to) { + let from = game.pieces[game.who] + if (!game.vm.a.includes(from)) + game.vm.a.push(from) + if (!game.vm.b.includes(to)) + game.vm.b.push(to) + move_piece(game.who, to) + log("Moved " + piece_abbr(game.who) + " from S" + from + " to S" + to + ".") + game.who = -1 + }, + done() { + push_undo() + vm_next() + }, +} + // === COMPLICATED EVENT STATES === states.reveal_commune_objective = { @@ -3377,6 +3491,122 @@ states.freemason_parade = { }, } +states.georges_vaysset = { + inactive: "look at Commune player's hand, take a card, then give them a card", + prompt() { + event_prompt("Take a card from your opponent's hand.") + view.hand = game.red_hand + for (let c of game.red_hand) + gen_action_card(c) + }, + card(c) { + push_undo() + log("Took C" + c + ".") + array_remove_item(game.red_hand, c) + game.blue_hand.push(c) + game.state = "georges_vaysset_2" + }, +} + +states.georges_vaysset_2 = { + inactive: "look at Commune player's hand, take a card, then give them a card", + prompt() { + event_prompt("Give a card to your opponent.") + for (let c of game.blue_hand) + gen_action_card(c) + }, + card(c) { + push_undo() + log("Gave C" + c + ".") + array_remove_item(game.blue_hand, c) + game.red_hand.push(c) + vm_next() + }, +} + +states.elizabeth_dimitrieff_discard = { + inactive: "discard 2 cards", + prompt() { + event_prompt("Discard 2 cards.") + for (let c of game.red_hand) + gen_action_card(c) + }, + card(c) { + push_undo() + log("Discarded C" + c + ".") + array_remove_item(game.red_hand, c) + game.discard.push(c) + if (--game.vm.count === 0) + vm_next() + }, +} + +states.versailles_left = { + inactive: "add bonus cubes", + prompt() { + event_prompt("Move bonus cubes to Pool.") + if (game.active === VERSAILLES) + for_each_versailles_cube(BLUE_BONUS_CUBES[2], gen_action_piece) + else + for_each_commune_cube(RED_BONUS_CUBES[2], gen_action_piece) + }, + piece(p) { + remove_piece(p) + if (game.active === VERSAILLES) { + log("Added BC to Pool.") + if (!has_friendly_cube(BLUE_BONUS_CUBES[2])) + vm_next() + } + if (game.active === COMMUNE) { + log("Added RC to Pool.") + if (!has_friendly_cube(RED_BONUS_CUBES[2])) + vm_next() + } + }, +} + +function init_paris_bombarded() { + game.vm.count = 3 + game.vm.spaces = [] + for (let s of PARIS) + if (is_adjacent_to_control_in_dimension(s, FORTS)) + game.vm.spaces.push(s) +} + +states.paris_bombarded = { + inactive: "attempt remove operations", + prompt() { + event_prompt("Attempt up to 3 Remove Operations with strength 2.") + for (let s of game.vm.spaces) + if (has_enemy_cube(s)) + for_each_enemy_cube(s, gen_action_piece) + view.actions.skip = 1 + }, + piece(p) { + clear_undo() + let s = game.pieces[p] + let pn = piece_abbr(p) + log("Remove " + pn + " from S" + s + ".") + let c = draw_strategy_card() + let str = 2 + let ops = card_ops[c] + logi("Drew C" + c) + logi("Strength " + str + " vs " + ops) + if (str >= ops) { + remove_piece(p) + update_presence_and_control() + logi("Success") + } else { + logi("Failure") + } + if (--game.vm.count === 0) + vm_next() + }, + skip() { + vm_next() + }, +} + // === SETUP === exports.setup = function (seed, scenario, options) { @@ -3470,8 +3700,8 @@ exports.setup = function (seed, scenario, options) { log_h1("Red Flag Over Paris") - if (options.censorship) { - log("Censorship Phase Variant.") + if (options.censorship && scenario === "Standard") { + log("Censorship Phase.") game.censorship = 1 } @@ -3482,6 +3712,13 @@ exports.setup = function (seed, scenario, options) { for (let c = 42; c <= 53; ++c) game.objective_deck.push(c) + if (scenario === "Censorship") { + log("Censorship Variant.") + game.censorship = 1 + for (let c = 54; c <= 63; ++c) + game.strategy_deck.push(c) + } + shuffle(game.strategy_deck) shuffle(game.objective_deck) @@ -3670,10 +3907,6 @@ function log_h3(msg) { log(".h3 " + msg) } -function log_sep() { - log(".hr") -} - // === COMMON LIBRARY === function clear_undo() { @@ -4231,3 +4464,91 @@ CODE[53] = [ // Legitimacy [ vm_endif ], [ vm_return ], ] + +CODE[54] = [ // Louis Rossel + [ vm_prompt, "Move up to all cubes in Forts or Paris." ], + [ vm_switch, ["forts","paris"] ], + [ vm_case, "forts" ], + [ vm_move_within, FORTS ], + [ vm_case, "paris" ], + [ vm_move_within, PARIS ], + [ vm_endswitch ], + [ vm_asm, ()=>game.louis_rossel=1 ], + [ vm_return ], +] + +CODE[55] = [ // Gustave Flourens + [ vm_increase_revolutionary_momentum ], + [ vm_prompt, "Move up to 1 from Public Opinion to Military." ], + [ vm_move_up_to, 1, PUBLIC_OPINION, MILITARY ], + [ vm_return ], +] + +CODE[56] = [ // Jean-Baptiste Clément + [ vm_prompt, "Place up to 2 in Public Opinion (use removed cubes)." ], + [ vm_place_removed_up_to, 2, PUBLIC_OPINION ], + [ vm_return ], +] + +CODE[57] = [ // Elizabeth Dimitrieff + [ vm_asm, ()=>clear_undo() ], + [ vm_asm, ()=>game.red_hand.push(draw_strategy_card()) ], + [ vm_asm, ()=>game.red_hand.push(draw_strategy_card()) ], + [ vm_asm, ()=>game.vm.count = 2 ], + [ vm_goto, "elizabeth_dimitrieff_discard" ], + [ vm_return ], +] + +CODE[58] = [ // Paris Bombarded + [ vm_asm, ()=>init_paris_bombarded() ], + [ vm_goto, "paris_bombarded" ], + [ vm_return ], +] + +CODE[59] = [ // Général Gallifet + [ vm_prompt, "Remove up to 3 in Paris where present." ], + [ vm_remove_up_to, 3, ()=>(where_present(PARIS)) ], + [ vm_asm, ()=>add_political_vp(VERSAILLES, -1) ], + [ vm_return ], +] + +CODE[60] = [ // Sapper Tactics + [ vm_prompt, "Remove one Barricade where present." ], + [ vm_remove_disc, ()=>(where_present(ANY)) ], + [ vm_return ], +] + +CODE[61] = [ // Georges Vaysset + [ vm_asm, ()=>clear_undo() ], + [ vm_asm, ()=>log("Commune hand:") ], + [ vm_asm, ()=>{ for (let c of game.red_hand) logi("C" + c) } ], + [ vm_goto, "georges_vaysset" ], + [ vm_return ], +] + +CODE[62] = [ // Colonne Vendôme + [ vm_if, ()=>(game.active === COMMUNE) ], + [ vm_decrease_prussian_collaboration ], + [ vm_else ], + [ vm_decrease_revolutionary_momentum ], + [ vm_endif ], + [ vm_prompt, "Replace up to 1 in Institutional." ], + [ vm_replace_up_to, 1, INSTITUTIONAL ], + [ vm_return ], +] + +CODE[63] = [ // Versailles' Left + [ vm_prompt, "Replace up to 1 in Republicans." ], + [ vm_replace_up_to, 1, REPUBLICANS ], + [ vm_asm, ()=>update_presence_and_control() ], + [ vm_asm, ()=>game.versailles_left = game.active ], + [ vm_if, ()=>(has_commune_cube(RED_BONUS_CUBES[2])) ], + [ vm_if, ()=>(game.active === COMMUNE && is_commune_control(REPUBLICANS)) ], + [ vm_goto, "versailles_left" ], + [ vm_endif ], + [ vm_if, ()=>(game.active === VERSAILLES && is_versailles_control(REPUBLICANS)) ], + [ vm_goto, "versailles_left" ], + [ vm_endif ], + [ vm_endif ], + [ vm_return ], +] -- cgit v1.2.3