"use strict" // TODO: clean up push/clear_undo // TODO: clean up prompts // TODO: event prompts // TODO: add logging const COMMUNE = "Commune"; const VERSAILLES = "Versailles"; var game, view, states = {} exports.scenarios = [ "Standard", "Censorship" ] exports.roles = [ COMMUNE, VERSAILLES ] const first_commune_cube = 0 const last_commune_cube = 17 const first_versailles_cube = 18 const last_versailles_cube = 35 const first_commune_disc = 36 const last_commune_disc = 37 const first_versailles_disc = 38 const last_versailles_disc = 39 const crisis_names = [ "Escalation", "Tension", "Final Crisis", ] const card_names = [ "Initiative", "Jules Ducatel", "The Murder of Vincenzini", "Brassardiers", "Jules Ferry", "Le Figaro", "Général Louis Valentin", "Général Espivent", "Les Amis de l'Ordre", "Socialist Newspaper Ban", "Fortification of Mont-Valérien", "Adolphe Thiers", "Otto von Bismarck", "Général Ernest de Cissey", "Colonel de Lochner", "Jules Favre", "Hostage Decree", "Maréchal Macmahon", "Paule Minck", "Walery Wroblewski", "Banque de France", "Le Réveil", "Execution of Generals", "Les Cantinières", "Eugène Protot", "Paul Cluseret", "Gaston Crémieux", "Luise Michel", "Jaroslav Dombrowski", "Raoul Rigault", "Karl Marx", "Blanquists", "Général Lullier", "Jules Vallès", "Charles Delescluze", "Conciliation", "Georges Clemenceau", "Archbishop Georges Darboy", "Victor Hugo", "Léon Gambetta", "Elihu Washburne", "Freemason Parade", "Paris Cannons", "Aux Barricades!", "Commune's Stronghold", "Fighting in Issy Village", "Battle of Mont-Valérien", "Raid on Château de Vincennes", "Revolution in the Press", "Pius IX", "Socialist International", "Royalists Dissension", "Rise of Republicanism", "Legitimacy", ] const card_ops = [ 0, // Commune 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 4, // Versailles 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 4, // Neutral 3, 3, 3, 3, 3, 3, 3, // Objective 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ] const space_names = [ "National Assembly", "Royalists", "Republicans", "Press", "Catholic Church", "Social Movements", "Mont-Valérien", "Fort D'Issy", "Château de Vincennes", "Butte Montmartre", "Butte-Aux-Cailles", "Père Lachaise", "Prussian Occupied Territory", "Versailles HQ", "Red Cube Pool 1", "Red Cube Pool 2", "Red Cube Pool 3", "Red Crisis Track Start", "Red Crisis Track Escalation", "Red Crisis Track Tension", "Red Crisis Track Final Crisis", "Red Bonus Cubes 1", "Red Bonus Cubes 2", "Red Bonus Cubes 3", "Blue Cube Pool", "Blue Crisis Track Start", "Blue Crisis Track Escalation", "Blue Crisis Track Tension", "Blue Crisis Track Final Crisis", "Blue Bonus Cubes 1", "Blue Bonus Cubes 2", "Blue Bonus Cubes 3", "Prussian Collaboration 1", "Prussian Collaboration 2", "Prussian Collaboration 3", ] const RED_CUBE_POOL = [14, 15, 16] const RED_CRISIS_TRACK = [17, 18, 19, 20] const RED_BONUS_CUBES = [21, 22, 23] const BLUE_CUBE_POOL = 24 const BLUE_CRISIS_TRACK = [25, 26, 27, 28] const BLUE_BONUS_CUBES = [29, 30, 31] const PRUSSIAN_COLLABORATION = [32, 33, 34] const OUT_OF_PLAY = -1 const NATIONAL_ASSEMBLY = 0 const ROYALISTS = 1 const REPUBLICANS = 2 const PRESS = 3 const CATHOLIC_CHURCH = 4 const SOCIAL_MOVEMENTS = 5 const MONT_VALERIEN = 6 const FORT_D_ISSY = 7 const CHATEAU_DE_VINCENNES = 8 const BUTTE_MONTMARTRE = 9 const BUTTE_AUX_CAILLES = 10 const PERE_LACHAISE = 11 const PRUSSIAN_OCCUPIED_TERRITORY = 12 const VERSAILLES_HQ = 13 const first_space = NATIONAL_ASSEMBLY const last_space = PERE_LACHAISE const space_count = last_space + 1 const ANY = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ] const POLITICAL = [ NATIONAL_ASSEMBLY, ROYALISTS, REPUBLICANS, PRESS, CATHOLIC_CHURCH, SOCIAL_MOVEMENTS, ] const MILITARY = [ MONT_VALERIEN, FORT_D_ISSY, CHATEAU_DE_VINCENNES, BUTTE_MONTMARTRE, BUTTE_AUX_CAILLES, PERE_LACHAISE, ] const INSTITUTIONAL = [ NATIONAL_ASSEMBLY, ROYALISTS, REPUBLICANS ] const PUBLIC_OPINION = [ PRESS, CATHOLIC_CHURCH, SOCIAL_MOVEMENTS ] const FORTS = [ MONT_VALERIEN, FORT_D_ISSY, CHATEAU_DE_VINCENNES ] const PARIS = [ BUTTE_MONTMARTRE, BUTTE_AUX_CAILLES, PERE_LACHAISE ] const PIVOTAL = [ NATIONAL_ASSEMBLY, PRESS, MONT_VALERIEN, BUTTE_MONTMARTRE ] const DIMENSION_SPACES = [ INSTITUTIONAL, INSTITUTIONAL, INSTITUTIONAL, PUBLIC_OPINION, PUBLIC_OPINION, PUBLIC_OPINION, FORTS, FORTS, FORTS, PARIS, PARIS, PARIS, ] const DIMENSION_NAME = [ "Institutional", "Institutional", "Institutional", "Public Opinion", "Public Opinion", "Public Opinion", "Forts", "Forts", "Forts", "Paris", "Paris", "Paris", ] const ADJACENT_TO = [ [ ROYALISTS, REPUBLICANS ], [ PRESS, CATHOLIC_CHURCH ], [ PRESS, SOCIAL_MOVEMENTS ], [ ROYALISTS, REPUBLICANS, CATHOLIC_CHURCH, SOCIAL_MOVEMENTS ], [ ROYALISTS, PRESS ], [ REPUBLICANS, PRESS ], [ BUTTE_MONTMARTRE, VERSAILLES_HQ ], [ CHATEAU_DE_VINCENNES, BUTTE_AUX_CAILLES, VERSAILLES_HQ ], [ FORT_D_ISSY, PERE_LACHAISE, PRUSSIAN_OCCUPIED_TERRITORY ], [ MONT_VALERIEN, BUTTE_AUX_CAILLES, PERE_LACHAISE ], [ FORT_D_ISSY, BUTTE_MONTMARTRE, PERE_LACHAISE ], [ CHATEAU_DE_VINCENNES, BUTTE_MONTMARTRE, BUTTE_AUX_CAILLES, PRUSSIAN_OCCUPIED_TERRITORY ], [ CHATEAU_DE_VINCENNES, PERE_LACHAISE ], [ MONT_VALERIEN, FORT_D_ISSY ], ] const ADJACENT_FROM = [ [ ], [ NATIONAL_ASSEMBLY, PRESS, CATHOLIC_CHURCH ], [ NATIONAL_ASSEMBLY, PRESS, SOCIAL_MOVEMENTS ], [ ROYALISTS, REPUBLICANS, CATHOLIC_CHURCH, SOCIAL_MOVEMENTS ], [ ROYALISTS, PRESS ], [ REPUBLICANS, PRESS ], [ BUTTE_MONTMARTRE, VERSAILLES_HQ ], [ CHATEAU_DE_VINCENNES, BUTTE_AUX_CAILLES, VERSAILLES_HQ ], [ FORT_D_ISSY, PERE_LACHAISE, PRUSSIAN_OCCUPIED_TERRITORY ], [ MONT_VALERIEN, BUTTE_AUX_CAILLES, PERE_LACHAISE ], [ FORT_D_ISSY, BUTTE_MONTMARTRE, PERE_LACHAISE ], [ CHATEAU_DE_VINCENNES, BUTTE_MONTMARTRE, BUTTE_AUX_CAILLES, PRUSSIAN_OCCUPIED_TERRITORY ], [ CHATEAU_DE_VINCENNES, PERE_LACHAISE ], [ MONT_VALERIEN, FORT_D_ISSY ], ] function is_political_space(s) { return ( s === NATIONAL_ASSEMBLY || s === ROYALISTS || s === REPUBLICANS || s === PRESS || s === CATHOLIC_CHURCH || s === SOCIAL_MOVEMENTS ) } function is_military_space(s) { return ( s === MONT_VALERIEN || s === FORT_D_ISSY || s === CHATEAU_DE_VINCENNES || s === BUTTE_MONTMARTRE || s === BUTTE_AUX_CAILLES || s === PERE_LACHAISE ) } const OBJECTIVE_SPACE = [ BUTTE_MONTMARTRE, BUTTE_AUX_CAILLES, PERE_LACHAISE, FORT_D_ISSY, MONT_VALERIEN, CHATEAU_DE_VINCENNES, PRESS, CATHOLIC_CHURCH, SOCIAL_MOVEMENTS, ROYALISTS, REPUBLICANS, NATIONAL_ASSEMBLY, ] function objective_card_space(c) { return OBJECTIVE_SPACE[c - 42] } function commune_objective_card() { return game.red_objective[0] } function versailles_objective_card() { return game.blue_objective[0] } function commune_objective_space() { return objective_card_space(commune_objective_card()) } function versailles_objective_space() { return objective_card_space(versailles_objective_card()) } // === GAME STATE === function discard_card(c) { array_remove_item(player_hand(), c) game.discard = c } function recycle_card(c) { array_remove_item(player_hand(), c) game.strategy_deck.unshift(c) } function add_political_vp(side, amount) { if (side === COMMUNE) game.political_vp += amount else game.political_vp -= amount game.political_vp = Math.min(5, Math.max(-5, game.political_vp)) } function add_military_vp(side, amount) { if (side === COMMUNE) game.military_vp += amount else game.military_vp -= amount game.military_vp = Math.min(5, Math.max(-5, game.military_vp)) } function is_objective_card(c) { return c >= 42 && c <= 53 } function is_commune_card(c) { return c >= 18 && c <= 34 } function is_versailles_card(c) { return c >= 1 && c <= 17 } function is_neutral_card(c) { return c >= 35 && c <= 41 } function enemy_player() { if (game.active === COMMUNE) return VERSAILLES return COMMUNE } function player_hand() { if (game.active === COMMUNE) return game.red_hand return game.blue_hand } function is_space(s) { return s >= first_space && s <= last_space } function can_advance_momentum() { if (game.active === COMMUNE) return game.red_momentum < 3 return game.blue_momentum < 3 } function can_play_event(c) { if (game.active === COMMUNE && is_versailles_card(c)) return false if (game.active === VERSAILLES && is_commune_card(c)) return false // Cards 38 through 41 can only be played if you have the initiative if (c >= 38 && c <= 41) if (game.active !== game.initiative) return false // Les Amis de l'Ordre - must place 2 cubes in Paris if (c === 8) { if (count_available_versailles_cubes() < 2) return false let n = count_versailles_cubes(BUTTE_MONTMARTRE) + count_versailles_cubes(BUTTE_AUX_CAILLES) + count_versailles_cubes(PERE_LACHAISE) if (n > 12 - 2) return false } // Otto von Bismarck - must remove blue cube from National Assembly if (c === 12) if (!has_versailles_cube(NATIONAL_ASSEMBLY)) return false // Le Reveil - cannot be played if Socialist Newspaper Ban if (c === 21) if (game.snb) return false // Victor Hugo - must decrease both player momentums if (c === 38) if (game.red_momentum < 1 || game.blue_momentum < 1) return false // Freemason Parade - must remove red & blue cubes from one Military space if (c === 41) if (!can_play_freemason_parade()) return false return true } var c_count = new Array(space_count).fill(0) var v_count = new Array(space_count).fill(0) function update_presence_and_control() { c_count.fill(0) v_count.fill(0) for (let p = first_commune_cube; p <= last_commune_cube; ++p) { let s = game.pieces[p] if (is_space(s)) c_count[s] += 1 } for (let p = first_versailles_cube; p <= last_versailles_cube; ++p) { let s = game.pieces[p] if (is_space(s)) v_count[s] += 1 } for (let p = first_commune_disc; p <= last_commune_disc; ++p) { let s = game.pieces[p] if (is_space(s)) c_count[s] += 1 } for (let p = first_versailles_disc; p <= last_versailles_disc; ++p) { let s = game.pieces[p] if (is_space(s)) v_count[s] += 1 } game.presence = 0 game.control = 0 // Permanent Presence game.presence |= 1 << PERE_LACHAISE game.presence |= 1 << SOCIAL_MOVEMENTS game.presence |= 1 << (ROYALISTS + space_count) for (let s = first_space; s <= last_space; ++s) { let c_bit = 1 << s let v_bit = 1 << (s + space_count) if (c_count[s] > 0) game.presence |= c_bit if (v_count[s] > 0) game.presence |= v_bit if (c_count[s] > v_count[s]) game.control |= c_bit if (v_count[s] > c_count[s]) game.control |= v_bit } } function is_present(s) { if (game.active === COMMUNE) return game.presence & (1 << (s)) return game.presence & (1 << (s + space_count)) } function is_commune_control(s) { if (s === VERSAILLES_HQ) return false if (s === PRUSSIAN_OCCUPIED_TERRITORY) return false return game.control & (1 << (s)) } function is_versailles_control(s) { if (s === VERSAILLES_HQ) return true if (s === PRUSSIAN_OCCUPIED_TERRITORY) return game.blue_momentum === 3 return game.control & (1 << (s + space_count)) } function is_control(s) { if (game.active === COMMUNE) return is_commune_control(s) return is_versailles_control(s) } function is_adjacent_to_control(here) { for (let s of ADJACENT_TO[here]) if (is_control(s)) return true return false } function is_commune_control_dimension(dim) { for (let s of dim) if (!is_commune_control(s)) return false return true } function is_versailles_control_dimension(dim) { for (let s of dim) if (!is_versailles_control(s)) return false return true } 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) if (game.pieces[p] === s) ++n return n } function count_versailles_cubes(s) { let n = 0 for (let p = first_versailles_cube; p <= last_versailles_cube; ++p) if (game.pieces[p] === s) ++n return n } function count_commune_discs(s) { let n = 0 for (let p = first_commune_disc; p <= last_commune_disc; ++p) if (game.pieces[p] === s) ++n return n } function count_versailles_discs(s) { let n = 0 for (let p = first_versailles_disc; p <= last_versailles_disc; ++p) if (game.pieces[p] === s) ++n 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) return count_versailles_cubes(s) } function count_friendly_discs(s) { if (game.active === COMMUNE) return count_commune_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) return p return -1 } function find_versailles_cube(s) { for (let p = first_versailles_cube; p <= last_versailles_cube; ++p) if (game.pieces[p] === s) return p return -1 } function find_commune_disc(s) { for (let p = first_commune_disc; p <= last_commune_disc; ++p) if (game.pieces[p] === s) return p return -1 } function find_versailles_disc(s) { for (let p = first_versailles_disc; p <= last_versailles_disc; ++p) if (game.pieces[p] === s) return p return -1 } function find_enemy_cube(s) { if (game.active === COMMUNE) return find_versailles_cube(s) return find_commune_cube(s) } function find_enemy_disc(s) { if (game.active === COMMUNE) return find_versailles_disc(s) return find_commune_disc(s) } function find_friendly_cube(s) { if (game.active === COMMUNE) return find_commune_cube(s) return find_versailles_cube(s) } function find_friendly_disc(s) { if (game.active === COMMUNE) return find_commune_disc(s) return find_versailles_disc(s) } function find_available_disc() { return find_friendly_disc(OUT_OF_PLAY) } function has_commune_cube(s) { return find_commune_cube(s) >= 0 } function has_versailles_cube(s) { return find_versailles_cube(s) >= 0 } function has_enemy_cube(s) { return find_enemy_cube(s) >= 0 } function has_friendly_cube(s) { return find_friendly_cube(s) >= 0 } 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 } function is_versailles_cube(p) { return p >= first_versailles_cube && p <= last_versailles_cube } function is_disc(p) { return p >= 36 } function can_place_cube_in_any(list) { for (let s of list) if (can_place_cube(s)) return true return false } function can_replace_cube_in_any(list) { for (let s of list) if (can_replace_cube(s)) return true return false } function can_place_cube(s, removed=0) { return find_available_cube(removed) >= 0 && count_friendly_cubes(s) < 4 } function can_place_disc(s) { return count_friendly_discs(s) < 1 } function can_replace_cube(s) { return has_enemy_cube(s) && can_place_cube(s) } function can_remove_cube(s) { return has_enemy_cube(s) } function remove_piece(p) { if (is_commune_cube(p)) { if (game.red_momentum >= 1 && count_commune_cubes(RED_CUBE_POOL[0]) < 2) game.pieces[p] = RED_CUBE_POOL[0] else if (game.red_momentum >= 2 && count_commune_cubes(RED_CUBE_POOL[1]) < 1) game.pieces[p] = RED_CUBE_POOL[1] else if (game.red_momentum >= 3 && count_commune_cubes(RED_CUBE_POOL[2]) < 1) game.pieces[p] = RED_CUBE_POOL[2] else game.pieces[p] = OUT_OF_PLAY } else if (is_versailles_cube(p)) { game.pieces[p] = BLUE_CUBE_POOL } else { game.pieces[p] = OUT_OF_PLAY } } function remove_piece_from_play(p) { game.pieces[p] = OUT_OF_PLAY } function move_piece(p, s) { game.pieces[p] = s } function place_cube(s, removed=0) { game.pieces[find_available_cube(removed)] = s } function place_disc(s) { game.pieces[find_available_disc()] = s } function replace_cube(p) { let s = game.pieces[p] remove_piece(p) place_cube(s) } function find_available_cube(removed=0) { let p = -1 if (game.active === COMMUNE) { if (removed) { p = find_commune_cube(OUT_OF_PLAY) if (p >= 0) return p } for (let i = 0; i < 3; ++i) { p = find_commune_cube(RED_CUBE_POOL[i]) if (p >= 0) return p } for (let i = 0; i < 4; ++i) { p = find_commune_cube(RED_CRISIS_TRACK[i]) if (p >= 0) return p } } else { p = find_versailles_cube(BLUE_CUBE_POOL) if (p >= 0) return p for (let i = 0; i < 4; ++i) { p = find_versailles_cube(BLUE_CRISIS_TRACK[i]) if (p >= 0) return p } } return -1 } function count_available_versailles_cubes() { return ( count_versailles_cubes(BLUE_CUBE_POOL) + count_versailles_cubes(BLUE_CRISIS_TRACK[0]) + count_versailles_cubes(BLUE_CRISIS_TRACK[1]) + count_versailles_cubes(BLUE_CRISIS_TRACK[2]) + count_versailles_cubes(BLUE_CRISIS_TRACK[3]) ) } function for_each_enemy_cube(s, f) { if (game.active === COMMUNE) for_each_versailles_cube(s, f) else for_each_commune_cube(s, f) } function for_each_friendly_cube(s, f) { if (game.active === COMMUNE) for_each_commune_cube(s, f) else for_each_versailles_cube(s, f) } function for_each_enemy_disc(s, f) { if (game.active === COMMUNE) for_each_versailles_disc(s, f) else 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) f(p) } function for_each_versailles_cube(s, f) { for (let p = first_versailles_cube; p <= last_versailles_cube; ++p) if (game.pieces[p] === s) f(p) } function for_each_commune_disc(s, f) { for (let p = first_commune_disc; p <= last_commune_disc; ++p) if (game.pieces[p] === s) f(p) } function for_each_versailles_disc(s, f) { for (let p = first_versailles_disc; p <= last_versailles_disc; ++p) if (game.pieces[p] === s) f(p) } function commune_political_vp() { return game.political_vp } function versailles_political_vp() { return -game.political_vp } function commune_military_vp() { return game.military_vp } function versailles_military_vp() { return -game.military_vp } function where_present(list) { return list.filter(s => is_present(s)) } // === START ROUND === function start_round() { if (game.round === 3) { goto_final_crisis() return } if (count_cubes(RED_CRISIS_TRACK[3]) < 2 && count_cubes(BLUE_CRISIS_TRACK[3]) < 2) { goto_final_crisis() return } game.round += 1 log_h1("Round " + game.round) let n = 4 if (game.scenario === "Censorship") n = 5 for (let i = 0; i < n; ++i) { game.red_hand.push(game.strategy_deck.pop()) game.blue_hand.push(game.strategy_deck.pop()) } for (let i = 0; i < 2; ++i) { game.red_objective.push(game.objective_deck.pop()) game.blue_objective.push(game.objective_deck.pop()) } game.active = "Both" game.state = "choose_objective_card" } // === CHOOSE OBJECTIVE CARD === states.choose_objective_card = { inactive: "choose an objective card", prompt(player) { view.prompt = "Choose an Objective card to keep." if (player === COMMUNE) for (let c of game.red_objective) gen_action_card(c) else for (let c of game.blue_objective) gen_action_card(c) }, card(c, player) { if (player === COMMUNE) game.red_objective = [ c ] else game.blue_objective = [ c ] if (game.red_objective.length === 1 && game.blue_objective.length === 1) end_choose_objective_card() else if (game.red_objective.length === 1) game.active = VERSAILLES else if (game.blue_objective.length === 1) game.active = COMMUNE else game.active = "Both" }, } function end_choose_objective_card() { goto_initiative_phase() } // === INITIATIVE PHASE === function goto_initiative_phase() { let c_level = commune_political_vp() - game.red_momentum let v_level = versailles_political_vp() - game.blue_momentum if (c_level >= v_level) game.active = game.initiative = COMMUNE else game.active = game.initiative = VERSAILLES game.state = "initiative_phase" } states.initiative_phase = { inactive: "decide initiative", prompt() { view.prompt = "Decide who will have the initiative." view.actions.commune = 1 view.actions.versailles = 1 }, commune() { log("Initiative: Commune") game.initiative = COMMUNE end_initiative_phase() }, versailles() { log("Initiative: Versailles") game.initiative = VERSAILLES end_initiative_phase() }, } function end_initiative_phase() { game.active = game.initiative if (game.round === 4) goto_final_crisis_events() else if (game.scenario === "Censorship") goto_censorship_phase() else goto_strategy_phase() } // === CENSORSHIP PHASE === function goto_censorship_phase() { game.state = "censorship_phase" } states.censorship_phase = { inactive: "censorship phase", prompt() { view.prompt = "Discard a card from your hand." for (let c of player_hand()) gen_action("card", c) }, card(c) { log(`Discarded C${c}.`) discard_card(c) if (game.active === game.initiative) game.active = enemy_player() else goto_strategy_phase() }, } // === PLAYING STRATEGY CARDS === function goto_strategy_phase() { clear_undo() log_h2(game.active) game.state = "strategy_phase" } function resume_strategy_phase() { if (game.red_hand.length === 1 && game.blue_hand.length === 1) { goto_set_aside_cards() } else { game.active = enemy_player() goto_strategy_phase() } } function player_final_crisis_card() { if (game.active === COMMUNE) return game.red_final return game.blue_final } states.strategy_phase = { inactive: "play a card", prompt() { view.prompt = "Play a card." for (let c of player_hand()) gen_action_card(c) if (player_hand().length > 1) { let final = player_final_crisis_card() if (final > 0) gen_action_card(final) if (game.discard > 0 && !is_neutral_card(game.discard)) if (can_play_event(game.discard)) gen_action_card(game.discard) } }, card(c) { push_undo() game.what = c if (c === 17 || c === 34) { game.state = "play_final_discard" } else if (c === game.discard) { game.state = "play_discard" } else { log(`Played C${c}.`) game.state = "play_card" } }, } states.play_card = { prompt() { let c = game.what view.prompt = card_names[game.what] + ": Play for Event, Operations, or Momentum." view.selected_card = game.what view.actions.political = 1 view.actions.military = 1 if (can_play_event(c)) view.actions.event = 1 else view.actions.event = 0 if (can_advance_momentum()) { // view.actions.momentum = 1 if (game.active === COMMUNE) view.actions.red_momentum = 1 else view.actions.blue_momentum = 1 } }, event() { push_undo() log("Event.") discard_card(game.what) goto_play_event(game.what) }, political() { push_undo() log(card_ops[game.what] + " Ops.") discard_card(game.what) goto_operations(card_ops[game.what], POLITICAL) }, military() { push_undo() log(card_ops[game.what] + " Ops.") discard_card(game.what) goto_operations(card_ops[game.what], MILITARY) }, momentum() { push_undo() log(`Momentum.`) if (game.scenario === "Censorship") recycle_card(game.what) else discard_card(game.what) if (game.active === COMMUNE) advance_revolutionary_momentum(1) else advance_prussian_collaboration(1) }, red_momentum() { this.momentum() }, blue_momentum() { this.momentum() }, } states.play_discard = { prompt() { view.prompt = card_names[game.what] + ": Discard a card." for (let c of player_hand()) gen_action_card(c) view.selected_card = game.what }, card(c) { push_undo() log("Discarded C" + c + " to play C" + game.what + ".") discard_card(c) goto_play_event(game.what) }, } states.play_final_discard = { prompt() { view.prompt = card_names[game.what] + ": Discard a card." for (let c of player_hand()) gen_action_card(c) view.selected_card = game.what }, card(c) { push_undo() log("Discarded C" + c + " to play C" + game.what + ".") discard_card(c) game.state = "play_final_ops" }, } states.play_final_ops = { prompt() { view.prompt = card_names[game.what] + ": Use up to 4 Operations Points." view.selected_card = game.what view.actions.political = 1 view.actions.military = 1 }, political() { push_undo() discard_final() log("4 Ops.") goto_operations(4, POLITICAL) }, military() { push_undo() discard_final() log("4 Ops.") goto_operations(4, MILITARY) }, } function discard_final() { if (game.active === COMMUNE) game.red_final = 0 else game.blue_final = 0 } // === PLAYER MOMENTUM TRACKS === function advance_revolutionary_momentum(x) { game.red_momentum += x for (let i = game.red_momentum; i < 3; ++i) for_each_commune_cube(RED_CUBE_POOL[i], remove_piece_from_play) game.momentum_active = game.active game.active = VERSAILLES if (x > 0 && game.red_momentum >= 2 && can_place_cube_in_any(INSTITUTIONAL)) game.state = "revolutionary_momentum_trigger" else end_momentum_trigger() } function advance_prussian_collaboration(x) { game.blue_momentum += x for (let i = 0; i < game.blue_momentum; ++i) for_each_versailles_cube(PRUSSIAN_COLLABORATION[i], remove_piece) game.momentum_active = game.active game.active = COMMUNE if (x > 0 && game.blue_momentum >= 2 && can_place_cube_in_any(PUBLIC_OPINION)) game.state = "prussian_collaboration_trigger" else end_momentum_trigger() } states.revolutionary_momentum_trigger = { prompt() { view.prompt = "Revolutionary Momentum: Place a cube in an Institutional space." for (let s of INSTITUTIONAL) if (can_place_cube(s)) gen_action_space(s) view.actions.skip = 1 }, space(s) { place_cube(s) end_momentum_trigger() }, skip() { end_momentum_trigger() }, } states.prussian_collaboration_trigger = { prompt() { view.prompt = "Prussian Collaboration: Place a cube in a Public Opinion space." for (let s of PUBLIC_OPINION) if (can_place_cube(s)) gen_action_space(s) view.actions.skip = 1 }, space(s) { place_cube(s) end_momentum_trigger() }, skip() { end_momentum_trigger() }, } function end_momentum_trigger() { game.active = game.momentum_active delete game.momentum_active if (game.vm) vm_next() else end_card_play() } // === CRISIS TRACK & CUBE POOLS === function end_card_play() { assess_crisis_breach_all() if (game.round === 4) resume_final_crisis() else resume_strategy_phase() } function assess_crisis_breach_all() { assess_crisis_breach(COMMUNE, 0, RED_CRISIS_TRACK[1], RED_BONUS_CUBES[0], 2, 0) assess_crisis_breach(COMMUNE, 1, RED_CRISIS_TRACK[2], RED_BONUS_CUBES[1], 2, 0) assess_crisis_breach(COMMUNE, 2, RED_CRISIS_TRACK[3], RED_BONUS_CUBES[2], 2, BLUE_CRISIS_TRACK[3]) assess_crisis_breach(VERSAILLES, 0, BLUE_CRISIS_TRACK[1], BLUE_BONUS_CUBES[0], 2, 0) assess_crisis_breach(VERSAILLES, 1, BLUE_CRISIS_TRACK[2], BLUE_BONUS_CUBES[1], 1, 0) assess_crisis_breach(VERSAILLES, 2, BLUE_CRISIS_TRACK[3], BLUE_BONUS_CUBES[2], 2, RED_CRISIS_TRACK[3]) } function assess_crisis_breach(side, i, crisis_track, bonus_cubes, start_count, enemy_final_crisis) { let count = count_cubes(crisis_track) if (count < start_count && count_cubes(bonus_cubes) > 0) { log(side + " Breached " + crisis_names[i] + "!") if (enemy_final_crisis) { if (count_cubes(enemy_final_crisis) === 2) { for_each_cube(bonus_cubes, remove_piece) log("-1 Political VP") add_political_vp(side, -1) } else { for_each_cube(bonus_cubes, remove_piece_from_play) } } else { for_each_cube(bonus_cubes, remove_piece) } } } // === OPERATIONS === function goto_operations(count, spaces) { game.count = count game.spaces = spaces goto_operations_remove() } function goto_operations_remove() { update_presence_and_control() if (can_operations_remove()) game.state = "operations_remove" else goto_operations_place() } function can_operations_remove() { for (let s of game.spaces) if (can_operations_remove_space(s)) return true return false } function can_operations_remove_space(s) { if (is_present(s) || is_adjacent_to_control(s)) { let c = has_enemy_cube(s) let d = has_enemy_disc(s) if (c || d) { if (is_political_space(s)) return true if (is_military_space(s)) if (!d || game.count >= 2) return true } } return false } function military_strength(s) { let str = 0 for (let next of ADJACENT_FROM[s]) if (is_control(next)) str += 1 if (is_present(s)) str += 1 if (is_control(s)) str += 1 return str } states.operations_remove = { prompt() { view.prompt = "Operations: Remove opponent's pieces." for (let s of game.spaces) { if (can_operations_remove_space(s)) { if (has_enemy_cube(s)) for_each_enemy_cube(s, gen_action_piece) else for_each_enemy_disc(s, gen_action_piece) } } view.actions.place = 1 }, piece(p) { push_undo() let s = game.pieces[p] if (has_enemy_disc(s)) game.count -= 2 else game.count -= 1 if (is_military_space(s)) { let str = military_strength(s) if (str >= 3) { log("Military strength " + str + ".") remove_piece(p) resume_operations_remove() } else if (game.count >= 1) { log("Military strength " + str + ".") game.who = p game.state = "operations_remove_spend" } else { log("Military strength " + str + ".") game.who = p game.state = "operations_remove_draw" } } else { remove_piece(p) resume_operations_remove() } }, place() { push_undo() goto_operations_place() }, } states.operations_remove_spend = { prompt() { view.prompt = "Operations: Spend extra Operations Point before drawing?" view.actions.spend = 1 view.actions.draw = 1 }, spend() { log("Spent 1 ops.") game.count -= 1 attempt_remove_piece(1) }, draw() { attempt_remove_piece(0) }, } states.operations_remove_draw = { prompt() { view.prompt = "Operations: Draw card for Military removal." view.actions.draw = 1 }, draw() { attempt_remove_piece(0) }, } function attempt_remove_piece(extra) { clear_undo() let p = game.who let s = game.pieces[p] let c = game.strategy_deck.pop() let str = military_strength(s) + extra let ops = card_ops[c] log("Military strength " + str + ".") log("Removed card C" + c + " for " + ops + " strength.") if (str >= ops) remove_piece(p) game.who = -1 resume_operations_remove() } function resume_operations_remove() { if (game.count === 0) goto_operations_done() else if (!can_operations_remove()) goto_operations_place() else game.state = "operations_remove" } function goto_operations_place() { update_presence_and_control() if (can_operations_place()) game.state = "operations_place" else game.state = "operations_done" } function can_operations_place() { if (find_available_cube() < 0) return false for (let s of game.spaces) if (can_operations_place_space(s)) return true return false } function can_operations_place_space(s) { if (is_present(s) || is_adjacent_to_control(s)) { if (can_place_cube(s)) { let d = has_enemy_disc(s) if (!d || game.count >= 2) return true } } return false } states.operations_place = { prompt() { view.prompt = "Operations: Place cubes." for (let s of game.spaces) if (can_operations_place_space(s)) gen_action_space(s) view.actions.end_ops = 1 }, space(s) { push_undo() if (has_enemy_disc(s)) game.count -= 2 else game.count -= 1 place_cube(s) resume_operations_place() }, end_ops() { end_operations() }, } function resume_operations_place() { if (game.count === 0 || !can_operations_place()) goto_operations_done() } function goto_operations_done() { game.state = "operations_done" } states.operations_done = { prompt() { view.prompt = "Operations: All done." view.actions.end_ops = 1 }, end_ops() { end_operations() }, } function end_operations() { if (game.vm) vm_next() else end_card_play() } // === SET ASIDE CARDS === function goto_set_aside_cards() { for (let c of game.red_hand) game.red_set_aside.push(c) game.red_hand = [] for (let c of game.blue_hand) game.blue_set_aside.push(c) game.blue_hand = [] goto_pivotal_space_bonus_actions() } // === PIVOTAL SPACE BONUS ACTIONS === function goto_pivotal_space_bonus_actions() { update_presence_and_control() game.spaces = PIVOTAL.filter(s => is_commune_control(s) || is_versailles_control(s)) resume_pivotal_space_bonus_actions() } function resume_pivotal_space_bonus_actions() { assess_crisis_breach_all() game.active = game.initiative if (game.spaces.length > 0) game.state = "pivotal_space_bonus_actions" else goto_crisis_dimension_scoring() } states.pivotal_space_bonus_actions = { prompt() { view.prompt = "Perform Pivotal Space bonus actions." for (let s of game.spaces) gen_action_space(s) }, space(s) { goto_bonus_action(s) }, } function goto_bonus_action(s) { log_h2(space_names[s] + " Bonus Action") array_remove_item(game.spaces, s) game.where = s game.state = "bonus_action" if (is_commune_control(s)) game.active = COMMUNE else game.active = VERSAILLES } states.bonus_action = { prompt() { let dimension = DIMENSION_SPACES[game.where] view.prompt = "Bonus Action in " + DIMENSION_NAME[game.where] + "." view.where = game.where view.actions.de_escalate = 1 view.actions.spread_influence = 1 if (can_replace_cube_in_any(dimension)) view.actions.turncoat = 1 else view.actions.turncoat = 0 }, de_escalate() { push_undo() game.state = "de_escalate_1" }, spread_influence() { push_undo() game.who = -1 game.count = 2 game.state = "spread_influence" }, turncoat() { push_undo() game.state = "turncoat" }, } states.de_escalate_1 = { prompt() { let dimension = DIMENSION_SPACES[game.where] view.prompt = "De-escalate in " + DIMENSION_NAME[game.where] + ": Remove your own cube." for (let s of dimension) for_each_friendly_cube(s, gen_action_piece) }, piece(p) { push_undo() remove_piece(p) game.state = "de_escalate_2" }, } states.de_escalate_2 = { prompt() { let dimension = DIMENSION_SPACES[game.where] view.prompt = "De-escalate in " + DIMENSION_NAME[game.where] + ": Remove your own or opponent's cube." for (let s of dimension) { for_each_enemy_cube(s, gen_action_piece) for_each_friendly_cube(s, gen_action_piece) } }, piece(p) { push_undo() remove_piece(p) resume_pivotal_space_bonus_actions() }, } states.spread_influence = { prompt() { let dimension = DIMENSION_SPACES[game.where] view.prompt = "Spread Influence in " + DIMENSION_NAME[game.where] + "." view.selected_cube = game.who if (game.who < 0) { // TODO: don't move same cube twice! for (let s of dimension) for_each_friendly_cube(s, gen_action_piece) } else { for (let s of dimension) if (game.pieces[game.who] !== s) gen_action_space(s) } view.actions.skip = 1 }, piece(p) { push_undo() game.who = p }, space(s) { move_piece(game.who, s) game.who = -1 if (--game.count === 0) resume_pivotal_space_bonus_actions() }, skip() { resume_pivotal_space_bonus_actions() } } states.turncoat = { prompt() { let dimension = DIMENSION_SPACES[game.where] view.prompt = "Turncoat in " + DIMENSION_NAME[game.where] + ": Replace an opponent's cube." for (let s of dimension) for_each_enemy_cube(s, gen_action_piece) }, piece(p) { push_undo() replace_cube(p) resume_pivotal_space_bonus_actions() }, } // === CRISIS DIMENSION SCORING === function goto_crisis_dimension_scoring() { update_presence_and_control() game.spaces = PIVOTAL.slice() game.active = game.initiative game.state = "crisis_dimension_scoring" } states.crisis_dimension_scoring = { prompt() { view.prompt = "Crisis Dimension Scoring!" for (let s of game.spaces) gen_action_space(s) }, space(s) { array_remove_item(game.spaces, s) let dimension = DIMENSION_SPACES[s] if (is_political_space(s)) { if (is_commune_control_dimension(dimension)) add_political_vp(COMMUNE, 1) else if (is_versailles_control_dimension(dimension)) add_political_vp(VERSAILLES, 1) else log("Nobody controlled " + DIMENSION_NAME[s] + ".") } else { if (is_commune_control_dimension(dimension)) add_military_vp(COMMUNE, 1) else if (is_versailles_control_dimension(dimension)) add_military_vp(VERSAILLES, 1) else log("Nobody controlled " + DIMENSION_NAME[s] + ".") } if (game.spaces.length === 0) end_crisis_dimension_scoring() }, } function end_crisis_dimension_scoring() { if (game.round === 4) goto_final_victory() else goto_objective_card_scoring() } // === OBJECTIVE CARD SCORING === function goto_objective_card_scoring() { update_presence_and_control() game.active = game.initiative game.count = 3 game.state = "objective_card_scoring" log("Commune Objective:") logi("C" + commune_objective_card()) log("Versailles Objective:") logi("C" + versailles_objective_card()) } states.objective_card_scoring = { prompt() { view.prompt = "Objective Card Scoring!" if (game.active === COMMUNE) view.objective = [ commune_objective_card(), versailles_objective_card() ] else view.objective = [ versailles_objective_card(), commune_objective_card() ] if (game.count & 1) gen_action_space(commune_objective_space()) if (game.count & 2) gen_action_space(versailles_objective_space()) }, space(s) { if (is_political_space(s)) { if (is_commune_control(s)) add_political_vp(COMMUNE, 1) else if (is_versailles_control(s)) add_political_vp(VERSAILLES, 1) else log("Nobody controlled S" + s + ".") } else { if (is_commune_control(s)) add_military_vp(COMMUNE, 1) else if (is_versailles_control(s)) add_military_vp(VERSAILLES, 1) else log("Nobody controlled S" + s + ".") } if (s === commune_objective_space()) game.count ^= 1 if (s === versailles_objective_space()) game.count ^= 2 if (game.count === 0) game.state = "objective_card_events" }, } function resume_objective_card_events() { let c = commune_objective_card() let v = versailles_objective_card() if (c || v) game.state = "objective_card_events" else start_round() } states.objective_card_events = { prompt() { view.prompt = "Objective Card Events!" let c = commune_objective_card() let v = versailles_objective_card() if (c && v) { if (game.active === COMMUNE) view.objective = [ c, v ] else view.objective = [ v, c ] } else if (c) view.objective = [ c ] else if (v) view.objective = [ v ] else view.objective = [ ] if (c) gen_action_card(c) if (v) gen_action_card(v) }, card(c) { let s = objective_card_space(c) if (c === commune_objective_card()) { game.red_objective = [] if (is_commune_control(s)) { game.red_fulfilled += 1 game.active = COMMUNE goto_play_event(c) } else { resume_objective_card_events() } } if (c === versailles_objective_card()) { game.blue_objective = [] if (is_versailles_control(s)) { game.blue_fulfilled += 1 game.active = VERSAILLES goto_play_event(c) } else { resume_objective_card_events() } } }, } // === FINAL CRISIS === function goto_final_crisis() { log_h1("Final Crisis") game.red_hand = game.red_set_aside if (game.red_final) game.red_hand.push(game.red_final) game.red_set_aside = [] game.blue_hand = game.blue_set_aside if (game.blue_final) game.blue_hand.push(game.blue_final) game.blue_set_aside = [] game.active = "Both" game.state = "final_crisis_discard" } states.final_crisis_discard = { prompt(player) { view.prompt = "Discard down to " + game.round + " cards." if (player === COMMUNE) { if (game.red_hand.length > game.round) for (let c of game.red_hand) gen_action_card(c) } if (player === VERSAILLES) { if (game.blue_hand.length > game.round) for (let c of game.blue_hand) gen_action_card(c) } }, card(c, player) { push_undo() if (player === COMMUNE) array_remove_item(game.red_hand, c) if (player === VERSAILLES) array_remove_item(game.blue_hand, c) if (game.red_hand.length > game.round && game.blue_hand.length > game.round) game.active = "Both" else if (game.red_hand.length > game.round) game.active = COMMUNE else if (game.blue_hand.length > game.round) game.active = VERSAILLES else { clear_undo() game.round = 4 goto_initiative_phase() } }, } function goto_final_crisis_events() { clear_undo() log_h2(game.active) game.state = "final_crisis_events" } function resume_final_crisis() { if (game.red_hand.length === 0 && game.blue_hand.length === 0) { goto_pivotal_space_bonus_actions() } else { game.active = game.final_active game.active = enemy_player() } } states.final_crisis_events = { inactive: "play an event", prompt() { view.prompt = "Play a card." for (let c of player_hand()) gen_action_card(c) }, card(c) { game.final_active = game.active discard_card(c) if (game.active === VERSAILLES && is_commune_card(c)) { game.what = c game.active = COMMUNE game.state = "final_crisis_opponent_event" } else if (game.active === COMMUNE && is_versailles_card(c)) { game.what = c game.active = VERSAILLES game.state = "final_crisis_opponent_event" } else { goto_play_event(c) } }, } states.final_crisis_opponent_event = { inactive: "play an event", prompt() { view.prompt = card_names[game.what] + ": Play or skip event." view.selected_card = game.what view.actions.event = 1 view.actions.skip = 1 }, event() { goto_play_event(game.what) }, skip() { end_card_play() }, } function goto_final_victory() { update_presence_and_control() if (game.red_momentum === 3) add_political_vp(COMMUNE, 1) if (game.blue_momentum === 3) add_political_vp(VERSAILLES, 1) if (versailles_military_vp() > commune_military_vp()) return goto_game_over(VERSAILLES, "Versailles won!") if (commune_political_vp() > versailles_political_vp()) return goto_game_over(COMMUNE, "Commune won!") let v = 0 let c = 0 if (versailles_military_vp() + versailles_political_vp() > commune_military_vp() + commune_political_vp()) ++v if (commune_military_vp() + commune_political_vp() > versailles_military_vp() + versailles_political_vp()) ++c if (game.blue_fulfilled > game.red_fulfilled) ++v if (game.red_fulfilled > game.blue_fulfilled) ++c let nv = 0 let nc = 0 for (let s of PIVOTAL) { if (is_commune_control(s)) nc++ if (is_versailles_control(s)) nv++ } if (nv > nc) v++ if (nc > nv) c++ if (game.initiative === VERSAILLES) v++ else c++ if (v > c) return goto_game_over(VERSAILLES, "Versailles won!") return goto_game_over(COMMUNE, "Commune won!") } // === EVENTS === function goto_play_event(c) { update_presence_and_control() goto_vm(c) } function end_event() { if (is_objective_card(game.vm.fp)) { game.vm = null resume_objective_card_events() } else { game.vm = null end_card_play() } } function goto_vm(proc) { game.state = "vm" game.vm = { prompt: 0, fp: proc, ip: 0, } vm_exec() } function event_prompt(str) { if (typeof str === "undefined") str = CODE[game.vm.fp][game.vm.prompt][1] if (typeof str === "function") str = str() view.prompt = card_names[game.vm.fp] + ": " + str } function vm_inst(a) { return CODE[game.vm.fp][game.vm.ip][a] } function vm_operand(a) { let x = CODE[game.vm.fp][game.vm.ip][a] if (a > 0 && typeof x === "function") return x() return x } function vm_exec() { console.log("VM", game.vm.fp, game.vm.ip, vm_inst(0).name) vm_inst(0)() } function vm_next() { game.vm.ip ++ vm_exec() } function vm_asm() { vm_operand(1) vm_next() } function vm_prompt() { game.vm.prompt = game.vm.ip vm_next() } function vm_goto() { game.state = vm_operand(1) } function vm_return() { game.state = "vm_return" } states.vm_return = { prompt() { event_prompt("All done.") view.actions.end_event = 1 }, end_event, } function vm_if() { if (!vm_operand(1)) { let balance = 1 while (balance > 0) { ++game.vm.ip switch (vm_operand(0)) { case vm_if: ++balance break case vm_endif: --balance break case vm_else: if (balance === 1) --balance break } if (game.vm.ip < 0 || game.vm.ip > CODE[game.vm.fp].length) throw "ERROR" } } vm_next() } function vm_else() { vm_jump(vm_endif, vm_if, 1, 1) } function vm_jump(op, nop, dir, step) { let balance = 1 while (balance > 0) { game.vm.ip += dir if (vm_inst(0) === op) --balance if (vm_inst(0) === nop) ++balance if (game.vm.ip < 0 || game.vm.ip > CODE[game.vm.fp].length) throw "ERROR" } game.vm.ip += step vm_exec() } function vm_endif() { vm_next() } function vm_endswitch() { vm_next() } function vm_switch() { game.vm.choice = null game.state = "vm_switch" } function vm_case() { if (game.vm.choice === vm_operand(1)) { vm_next() } else { do ++game.vm.ip while (vm_inst(0) !== vm_case && vm_inst(0) !== vm_endswitch) vm_exec() } } function vm_player() { // TODO: pause for undo? game.active = vm_operand(1) vm_next() } function vm_ops() { goto_operations(vm_operand(1), vm_operand_spaces(2)) } function vm_increase_revolutionary_momentum() { if (game.red_momentum < 3) game.state = "vm_increase_revolutionary_momentum" else vm_next() } function vm_increase_prussian_collaboration() { if (game.blue_momentum < 3) game.state = "vm_increase_prussian_collaboration" else vm_next() } function vm_decrease_revolutionary_momentum() { if (game.red_momentum > 1) game.state = "vm_decrease_revolutionary_momentum" else vm_next() } function vm_decrease_prussian_collaboration() { if (game.blue_momentum > 1) game.state = "vm_decrease_prussian_collaboration" else vm_next() } function vm_operand_spaces(x) { let s = vm_operand(x) if (typeof s === "number") return [ s ] return s } function vm_remove_up_to() { game.vm.upto = 1 game.vm.count = vm_operand(1) game.vm.spaces = vm_operand_spaces(2) goto_vm_remove() } function vm_remove() { game.vm.count = vm_operand(1) game.vm.spaces = vm_operand_spaces(2) goto_vm_remove() } function vm_remove_own() { game.vm.spaces = vm_operand_spaces(1) game.state = "vm_remove_own" } function vm_replace_up_to() { game.vm.upto = 1 game.vm.count = vm_operand(1) game.vm.spaces = vm_operand_spaces(2) game.state = "vm_replace" 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) goto_vm_replace() } function vm_place_removed_up_to() { game.vm.removed = 1 game.vm.upto = 1 game.vm.count = vm_operand(1) game.vm.spaces = vm_operand_spaces(2) goto_vm_place() } function vm_place_up_to() { game.vm.upto = 1 game.vm.count = vm_operand(1) game.vm.spaces = vm_operand_spaces(2) goto_vm_place() } function vm_place() { game.vm.count = vm_operand(1) game.vm.spaces = vm_operand_spaces(2) goto_vm_place() } function vm_may_place_disc() { game.vm.upto = 1 game.vm.count = 1 game.vm.spaces = vm_operand_spaces(1) game.state = "vm_place_disc" goto_vm_place_disc() } function vm_place_disc() { game.vm.count = 1 game.vm.spaces = vm_operand_spaces(1) goto_vm_place_disc() } function vm_move_up_to() { game.vm.count = vm_operand(1) game.vm.a = vm_operand_spaces(2) game.vm.b = vm_operand_spaces(3) goto_vm_move() } // === EVENT STATES === states.vm_switch = { prompt() { event_prompt() for (let choice of vm_operand(1)) { if (choice === "mont_valerien") gen_action_space(MONT_VALERIEN) else if (choice === "butte_montmartre") gen_action_space(BUTTE_MONTMARTRE) else view.actions[choice] = 1 } }, place() { push_undo() game.vm.choice = "place" vm_next() }, replace() { push_undo() game.vm.choice = "replace" vm_next() }, remove() { push_undo() game.vm.choice = "remove" vm_next() }, momentum() { push_undo() game.vm.choice = "momentum" vm_next() }, ops() { push_undo() game.vm.choice = "ops" vm_next() }, public_opinion() { push_undo() game.vm.choice = "public_opinion" vm_next() }, paris() { push_undo() game.vm.choice = "paris" vm_next() }, space(s) { push_undo() if (s === MONT_VALERIEN) game.vm.choice = "mont_valerien" if (s === BUTTE_MONTMARTRE) game.vm.choice = "butte_montmartre" vm_next() }, } states.vm_increase_revolutionary_momentum = { prompt() { event_prompt("Increase Revolutionary Momentum.") view.actions.red_momentum = 1 }, red_momentum() { push_undo() advance_revolutionary_momentum(1) }, } states.vm_increase_prussian_collaboration = { prompt() { event_prompt("Increase Prussian Collaboration.") view.actions.blue_momentum = 1 }, blue_momentum() { push_undo() advance_prussian_collaboration(1) }, } states.vm_decrease_revolutionary_momentum = { prompt() { event_prompt("Decrease Revolutionary Momentum.") view.actions.red_momentum = 1 }, red_momentum() { push_undo() advance_revolutionary_momentum(-1) }, } states.vm_decrease_prussian_collaboration = { prompt() { event_prompt("Decrease Prussian Collaboration.") view.actions.blue_momentum = 1 }, blue_momentum() { push_undo() advance_prussian_collaboration(-1) }, } function can_vm_place() { for (let s of game.vm.spaces) if (can_place_cube(s, game.vm.removed)) return true return false } function goto_vm_place() { if (can_vm_place(game.vm.removed)) game.state = "vm_place" else vm_next() } states.vm_place = { prompt() { event_prompt("Place " + game.vm.count + " cube.") if (game.vm.upto) view.actions.skip = 1 for (let s of game.vm.spaces) if (can_place_cube(s)) gen_action_space(s) }, space(s) { push_undo() place_cube(s, game.vm.removed) if (--game.vm.count === 0 || !can_vm_place(game.vm.removed)) vm_next() }, skip() { push_undo() vm_next() }, } function can_vm_place_disc() { for (let s of game.vm.spaces) if (can_place_disc(s)) return true return false } function goto_vm_place_disc() { if (can_vm_place_disc()) { if (find_available_disc() < 0) game.state = "vm_move_disc" else game.state = "vm_place_disc" } else { vm_next() } } states.vm_move_disc = { prompt() { event_prompt("Remove a disc to place it elsewhere.") if (game.vm.upto) view.actions.skip = 1 if (game.active === COMMUNE) for (let p = first_commune_disc; p <= last_commune_disc; ++p) gen_action_piece(p) else for (let p = first_versailles_disc; p <= last_versailles_disc; ++p) gen_action_piece(p) }, piece(p) { push_undo() remove_piece(p) game.state = "vm_place_disc" }, skip() { push_undo() vm_next() }, } states.vm_place_disc = { prompt() { event_prompt("Place a disc.") if (game.vm.upto) view.actions.skip = 1 for (let s of game.vm.spaces) if (can_place_disc(s)) gen_action_space(s) }, space(s) { push_undo() place_disc(s) vm_next() }, skip() { push_undo() vm_next() }, } function can_vm_replace() { for (let s of game.vm.spaces) if (can_replace_cube(s)) return true return false } function goto_vm_replace() { if (can_vm_replace()) game.state = "vm_replace" else vm_next() } states.vm_replace = { prompt() { event_prompt("Replace " + game.vm.count + " cubes.") if (game.vm.upto) view.actions.skip = 1 for (let s of game.vm.spaces) if (can_replace_cube(s)) for_each_enemy_cube(s, gen_action_piece) }, piece(p) { push_undo() replace_cube(p) if (--game.vm.count === 0 || !can_vm_replace()) vm_next() }, skip() { push_undo() vm_next() }, } function can_vm_remove() { for (let s of game.vm.spaces) if (can_remove_cube(s)) return true return false } function goto_vm_remove() { if (can_vm_remove()) game.state = "vm_remove" else vm_next() } states.vm_remove = { prompt() { event_prompt("Remove " + game.vm.count + " cubes.") if (game.vm.upto) view.actions.skip = 1 for (let s of game.vm.spaces) if (can_remove_cube(s)) for_each_enemy_cube(s, gen_action_piece) }, piece(p) { push_undo() remove_piece(p) if (--game.vm.count === 0 || !can_vm_remove()) vm_next() }, skip() { push_undo() vm_next() }, } function can_vm_move() { let from = false let to = false for (let s of game.vm.a) if (!game.vm.b.includes(s) && has_friendly_cube(s)) from = true for (let s of game.vm.b) if (count_friendly_cubes(s) < 4) to = true return from && to } function goto_vm_move() { game.who = -1 if (can_vm_move()) game.state = "vm_move" else vm_next() } states.vm_move = { prompt() { event_prompt("Move up to " + game.vm.count + " cubes.") view.actions.skip = 1 if (game.who < 0) { for (let s of game.vm.a) if (!game.vm.b.includes(s)) for_each_friendly_cube(s, gen_action_piece) } else { game.selected_cube = game.who for (let s of game.vm.b) if (count_friendly_cubes(s) < 4) gen_action_space(s) } }, piece(p) { push_undo() game.who = p }, space(s) { move_piece(game.who, s) game.who = -1 if (--game.vm.count === 0 || !can_vm_move()) vm_next() }, skip() { push_undo() vm_next() }, } // === COMPLICATED EVENT STATES === function can_play_freemason_parade() { for (let s of MILITARY) if (has_commune_cube(s) && has_versailles_cube(s)) return true return false } // === SETUP === exports.setup = function (seed, scenario, options) { game = { seed: seed, scenario: scenario, log: [], undo: [], active: null, state: null, round: 0, initiative: null, political_vp: 0, military_vp: 0, red_momentum: 0, blue_momentum: 0, strategy_deck: [], objective_deck: [], discard: 0, red_final: 34, red_hand: [], red_set_aside: [], red_objective: [], red_fulfilled: 0, blue_final: 17, blue_hand: [], blue_set_aside: [], blue_objective: [], blue_fulfilled: 0, presence: 0, control: 0, pieces: [ // Commune cubes RED_CRISIS_TRACK[0], RED_CRISIS_TRACK[0], RED_CRISIS_TRACK[0], RED_CRISIS_TRACK[1], RED_CRISIS_TRACK[1], RED_CRISIS_TRACK[2], RED_CRISIS_TRACK[2], RED_CRISIS_TRACK[3], RED_CRISIS_TRACK[3], RED_BONUS_CUBES[0], RED_BONUS_CUBES[0], RED_BONUS_CUBES[1], RED_BONUS_CUBES[1], RED_BONUS_CUBES[2], RED_BONUS_CUBES[2], PRESS, SOCIAL_MOVEMENTS, PERE_LACHAISE, // Versailles cubes BLUE_CRISIS_TRACK[0], BLUE_CRISIS_TRACK[1], BLUE_CRISIS_TRACK[1], BLUE_CRISIS_TRACK[2], BLUE_CRISIS_TRACK[3], BLUE_CRISIS_TRACK[3], BLUE_BONUS_CUBES[0], BLUE_BONUS_CUBES[1], BLUE_BONUS_CUBES[2], BLUE_BONUS_CUBES[2], PRUSSIAN_COLLABORATION[0], PRUSSIAN_COLLABORATION[1], PRUSSIAN_COLLABORATION[1], PRUSSIAN_COLLABORATION[2], PRUSSIAN_COLLABORATION[2], PRUSSIAN_COLLABORATION[2], ROYALISTS, PRESS, // Commune discs OUT_OF_PLAY, OUT_OF_PLAY, // Versailles discs OUT_OF_PLAY, OUT_OF_PLAY, ], count: 0, spaces: null, who: -1, } log_h1("Red Flag Over Paris") for (let c = 1; c <= 41; ++c) if (c !== 17 && c !== 34) game.strategy_deck.push(c) for (let c = 42; c <= 53; ++c) game.objective_deck.push(c) shuffle(game.strategy_deck) shuffle(game.objective_deck) start_round() return game } // === VIEW === exports.is_checkpoint = function (a, b) { return a.round !== b.round } exports.view = function(state, player) { game = state view = { log: game.log, prompt: null, actions: null, round: game.round, initiative: game.initiative, political_vp: game.political_vp, military_vp: game.military_vp, red_hand: game.red_hand.length, blue_hand: game.blue_hand.length, red_momentum: game.red_momentum, blue_momentum: game.blue_momentum, pieces: game.pieces, discard: game.discard, hand: 0, final: 0, set_aside: 0, objective: 0 } if (player === COMMUNE) { view.hand = game.red_hand view.final = game.red_final view.set_aside = game.red_set_aside view.objective = game.red_objective } if (player === VERSAILLES) { view.hand = game.blue_hand view.final = game.blue_final view.set_aside = game.blue_set_aside view.objective = game.blue_objective } if (game.state === "game_over") { view.prompt = game.victory } else if (player === "Observer" || (game.active !== player && game.active !== "Both")) { if (states[game.state]) { let inactive = states[game.state].inactive || game.state view.prompt = `Waiting for ${game.active} to ${inactive}...` } else { view.prompt = "Unknown state: " + game.state } } else { view.actions = {} if (states[game.state]) states[game.state].prompt(player) else view.prompt = "Unknown state: " + game.state if (view.actions.undo === undefined) { if (game.undo && game.undo.length > 0) view.actions.undo = 1 else view.actions.undo = 0 } } return view } exports.action = function (state, player, action, arg) { game = state if (states[game.state] && action in states[game.state]) { states[game.state][action](arg, player) } else { if (action === "undo" && game.undo && game.undo.length > 0) pop_undo() else throw new Error("Invalid action: " + action) } return game } // === GAME OVER === exports.resign = function (state, player) { game = state if (game.state !== "game_over") { if (player === COMMUNE) goto_game_over(VERSAILLES, "Commune resigned."); if (player === VERSAILLES) goto_game_over(COMMUNE, "Versailles resigned."); } return game } function goto_game_over(result, victory) { game.state = "game_over" game.active = "None" game.result = result game.victory = victory log_br() log(game.victory) } states.game_over = { get inactive() { return game.victory }, prompt() { view.prompt = game.victory }, } // === ACTIONS === function gen_action(action, argument) { if (!(action in view.actions)) view.actions[action] = [] view.actions[action].push(argument) } function gen_action_card(c) { gen_action("card", c) } function gen_action_piece(p) { gen_action("piece", p) } function gen_action_space(s) { gen_action("space", s) } // === LOGGING === function log(msg) { game.log.push(msg) } function log_br() { if (game.log.length > 0 && game.log[game.log.length - 1] !== "") game.log.push("") } function logi(msg) { game.log.push(">" + msg) } function log_h1(msg) { log_br() log(".h1 " + msg) log_br() } function log_h2(msg) { log_br() log(".h2 " + msg) log_br() } // === COMMON LIBRARY === function clear_undo() { if (game.undo.length > 0) game.undo = [] } function push_undo() { let copy = {} for (let k in game) { let v = game[k] if (k === "undo") continue else if (k === "log") v = v.length else if (typeof v === "object" && v !== null) v = object_copy(v) copy[k] = v } game.undo.push(copy) } function pop_undo() { let save_log = game.log let save_undo = game.undo game = save_undo.pop() save_log.length = game.log game.log = save_log game.undo = save_undo } function random(range) { // An MLCG using integer arithmetic with doubles. // https://www.ams.org/journals/mcom/1999-68-225/S0025-5718-99-00996-5/S0025-5718-99-00996-5.pdf // m = 2**35 − 31 return (game.seed = game.seed * 200105 % 34359738337) % range } function shuffle(list) { // Fisher-Yates shuffle for (let i = list.length - 1; i > 0; --i) { let j = random(i + 1) let tmp = list[j] list[j] = list[i] list[i] = tmp } } // Fast deep copy for objects without cycles function object_copy(original) { if (Array.isArray(original)) { let n = original.length let copy = new Array(n) for (let i = 0; i < n; ++i) { let v = original[i] if (typeof v === "object" && v !== null) copy[i] = object_copy(v) else copy[i] = v } return copy } else { let copy = {} for (let i in original) { let v = original[i] if (typeof v === "object" && v !== null) copy[i] = object_copy(v) else copy[i] = v } return copy } } // Array remove and insert (faster than splice) function array_remove_item(array, item) { let n = array.length for (let i = 0; i < n; ++i) if (array[i] === item) return array_remove(array, i) } function array_remove(array, index) { let n = array.length for (let i = index + 1; i < n; ++i) array[i - 1] = array[i] array.length = n - 1 } // === GENERATED EVENT CODE === const CODE = [] CODE[1] = [ // Jules Ducatel [ vm_if, ()=>(game.round === 4) ], [ vm_goto, "reveal_commune_hand" ], [ vm_else ], [ vm_goto, "reveal_commune_objective" ], [ vm_endif ], [ vm_ops, 1, MILITARY ], [ vm_return ], ] CODE[2] = [ // The Murder of Vincenzini [ vm_increase_revolutionary_momentum ], [ vm_remove_up_to, 3, POLITICAL ], [ vm_return ], ] CODE[3] = [ // Brassardiers [ vm_replace, 1, ()=>(where_present(MILITARY)) ], [ vm_return ], ] CODE[4] = [ // Jules Ferry [ vm_prompt, "Place up to 2 in Republicans or replace 1 in National Assembly." ], [ vm_switch, ["place","replace"] ], [ vm_case, "place" ], [ vm_place_up_to, 2, REPUBLICANS ], [ vm_case, "replace" ], [ vm_replace, 1, NATIONAL_ASSEMBLY ], [ vm_endswitch ], [ vm_return ], ] CODE[5] = [ // Le Figaro [ vm_prompt, "Place up to 2 in Press or replace 1 in Public Opinion." ], [ vm_switch, ["place","replace"] ], [ vm_case, "place" ], [ vm_place_up_to, 2, PRESS ], [ vm_case, "replace" ], [ vm_replace, 1, PUBLIC_OPINION ], [ vm_endswitch ], [ vm_return ], ] CODE[6] = [ // Général Louis Valentin [ vm_goto, "general_louis_valentin" ], [ vm_return ], ] CODE[7] = [ // Général Espivent [ vm_remove_up_to, 2, SOCIAL_MOVEMENTS ], [ vm_return ], ] CODE[8] = [ // Les Amis de l'Ordre [ vm_place, 2, PARIS ], [ vm_return ], ] CODE[9] = [ // Socialist Newspaper Ban [ vm_increase_revolutionary_momentum ], [ vm_remove_up_to, 2, PRESS ], [ vm_asm, ()=>game.snb=1 ], [ vm_return ], ] CODE[10] = [ // Fortification of Mont-Valérien [ vm_place_up_to, 1, MONT_VALERIEN ], [ vm_if, ()=>(is_control(MONT_VALERIEN)) ], [ vm_may_place_disc, MONT_VALERIEN ], [ vm_endif ], [ vm_return ], ] CODE[11] = [ // Adolphe Thiers [ vm_increase_prussian_collaboration ], [ vm_place_up_to, 2, ROYALISTS ], [ vm_return ], ] CODE[12] = [ // Otto von Bismarck [ vm_remove_own, NATIONAL_ASSEMBLY ], [ vm_increase_prussian_collaboration ], [ vm_ops, 2, MILITARY ], [ vm_return ], ] CODE[13] = [ // Général Ernest de Cissey [ vm_ops, 4, FORT_D_ISSY ], [ vm_return ], ] CODE[14] = [ // Colonel de Lochner [ vm_if, ()=>(is_versailles_control(MONT_VALERIEN)) ], [ vm_prompt, "Use 3 Operation Points in Mont-Valérien or Butte Montmartre." ], [ vm_switch, ["mont_valerien","butte_montmartre"] ], [ vm_case, "mont_valerien" ], [ vm_ops, 3, MONT_VALERIEN ], [ vm_case, "butte_montmartre" ], [ vm_ops, 3, BUTTE_MONTMARTRE ], [ vm_endswitch ], [ vm_else ], [ vm_ops, 3, MONT_VALERIEN ], [ vm_endif ], [ vm_return ], ] CODE[15] = [ // Jules Favre [ vm_increase_prussian_collaboration ], [ vm_place_up_to, 1, REPUBLICANS ], [ vm_return ], ] CODE[16] = [ // Hostage Decree [ vm_remove, 20, CATHOLIC_CHURCH ], [ vm_return ], ] CODE[17] = [ // Maréchal Macmahon [ vm_ops, 4, PARIS ], [ vm_return ], ] CODE[18] = [ // Paule Minck [ vm_replace, 1, ()=>(where_present(MILITARY)) ], [ vm_return ], ] CODE[19] = [ // Walery Wroblewski [ vm_place_disc, ()=>(where_present(PARIS)) ], [ vm_return ], ] CODE[20] = [ // Banque de France [ vm_decrease_revolutionary_momentum ], [ vm_place_up_to, 1, PARIS ], [ vm_may_place_disc, PARIS ], [ vm_return ], ] CODE[21] = [ // Le Réveil [ vm_increase_revolutionary_momentum ], [ vm_place_up_to, 1, PRESS ], [ vm_return ], ] CODE[22] = [ // Execution of Generals [ vm_increase_prussian_collaboration ], [ vm_replace_up_to, 2, ()=>(where_present(PARIS)) ], [ vm_return ], ] CODE[23] = [ // Les Cantinières [ vm_place_removed_up_to, 2, ()=>(where_present(MILITARY)) ], [ vm_return ], ] CODE[24] = [ // Eugène Protot [ vm_ops, 3, INSTITUTIONAL ], [ vm_return ], ] CODE[25] = [ // Paul Cluseret [ vm_ops, 2, PARIS ], [ vm_return ], ] CODE[26] = [ // Gaston Crémieux [ vm_increase_revolutionary_momentum ], [ vm_place_up_to, 2, SOCIAL_MOVEMENTS ], [ vm_return ], ] CODE[27] = [ // Luise Michel [ vm_increase_revolutionary_momentum ], [ vm_prompt, "Use 1 Operations Point in Public Opinion or Paris." ], [ vm_switch, ["public_opinion","paris"] ], [ vm_case, "public_opinion" ], [ vm_ops, 1, PUBLIC_OPINION ], [ vm_case, "paris" ], [ vm_ops, 1, PARIS ], [ vm_endswitch ], [ vm_return ], ] CODE[28] = [ // Jaroslav Dombrowski [ vm_place_up_to, 1, PARIS ], [ vm_may_place_disc, PARIS ], [ vm_return ], ] CODE[29] = [ // Raoul Rigault [ vm_increase_revolutionary_momentum ], [ vm_move_up_to, 2, CATHOLIC_CHURCH, PARIS ], [ vm_return ], ] CODE[30] = [ // Karl Marx [ vm_goto, "karl_marx" ], [ vm_return ], ] CODE[31] = [ // Blanquists [ vm_increase_revolutionary_momentum ], [ vm_ops, 2, MILITARY ], [ vm_return ], ] CODE[32] = [ // Général Lullier [ vm_ops, 4, CHATEAU_DE_VINCENNES ], [ vm_return ], ] CODE[33] = [ // Jules Vallès [ vm_increase_revolutionary_momentum ], [ vm_ops, 2, PUBLIC_OPINION ], [ vm_return ], ] CODE[34] = [ // Charles Delescluze [ vm_prompt, "Use 3 Operations Point in Military or 1 in Political." ], [ vm_switch, ["military","political"] ], [ vm_case, "military" ], [ vm_ops, 3, MILITARY ], [ vm_case, "political" ], [ vm_ops, 1, POLITICAL ], [ vm_endswitch ], [ vm_return ], ] CODE[35] = [ // Conciliation [ vm_prompt, "Move up to 2 from Public Opinion or Paris to the other." ], [ vm_switch, ["public_opinion","paris"] ], [ vm_case, "public_opinion" ], [ vm_move_up_to, 2, PUBLIC_OPINION, PARIS ], [ vm_case, "paris" ], [ vm_move_up_to, 2, PARIS, PUBLIC_OPINION ], [ vm_endswitch ], [ vm_return ], ] CODE[36] = [ // Georges Clemenceau [ vm_move_up_to, 3, PARIS, INSTITUTIONAL ], [ vm_return ], ] CODE[37] = [ // Archbishop Georges Darboy [ vm_move_up_to, 4, ANY, CATHOLIC_CHURCH ], [ vm_return ], ] CODE[38] = [ // Victor Hugo [ vm_decrease_revolutionary_momentum ], [ vm_decrease_prussian_collaboration ], [ vm_ops, 4, PUBLIC_OPINION ], [ vm_return ], ] CODE[39] = [ // Léon Gambetta [ vm_replace_up_to, 1, INSTITUTIONAL ], [ vm_if, ()=>(game.active === COMMUNE) ], [ vm_decrease_prussian_collaboration ], [ vm_else ], [ vm_decrease_revolutionary_momentum ], [ vm_endif ], [ vm_return ], ] CODE[40] = [ // Elihu Washburne [ vm_if, ()=>(game.active === COMMUNE) ], [ vm_increase_revolutionary_momentum ], [ vm_else ], [ vm_increase_prussian_collaboration ], [ vm_endif ], [ vm_ops, 2, INSTITUTIONAL ], [ vm_return ], ] CODE[41] = [ // Freemason Parade [ vm_goto, "freemason_parade" ], [ vm_prompt, "Place up to 2 or replace 1 in Institutional." ], [ vm_switch, ["place","replace"] ], [ vm_case, "place" ], [ vm_place_up_to, 2, INSTITUTIONAL ], [ vm_case, "replace" ], [ vm_replace, 1, INSTITUTIONAL ], [ vm_endswitch ], [ vm_return ], ] CODE[42] = [ // Paris Cannons [ vm_prompt, "Use 3 Operations Points in Paris or increase your Player Momentum." ], [ vm_switch, ["momentum","ops"] ], [ vm_case, "momentum" ], [ vm_if, ()=>(game.active === COMMUNE) ], [ vm_increase_revolutionary_momentum ], [ vm_else ], [ vm_increase_prussian_collaboration ], [ vm_endif ], [ vm_case, "ops" ], [ vm_ops, 3, PARIS ], [ vm_endswitch ], [ vm_return ], ] CODE[43] = [ // Aux Barricades! [ vm_ops, 2, PARIS ], [ vm_player, COMMUNE ], [ vm_may_place_disc, BUTTE_AUX_CAILLES ], [ vm_return ], ] CODE[44] = [ // Commune's Stronghold [ vm_prompt, "Use 2 Operations Points in Military or remove 1 from any space." ], [ vm_switch, ["ops","remove"] ], [ vm_case, "ops" ], [ vm_ops, 2, MILITARY ], [ vm_case, "remove" ], [ vm_remove, 1, ANY ], [ vm_endswitch ], [ vm_return ], ] CODE[45] = [ // Fighting in Issy Village [ vm_ops, 2, FORTS ], [ vm_player, VERSAILLES ], [ vm_may_place_disc, FORT_D_ISSY ], [ vm_return ], ] CODE[46] = [ // Battle of Mont-Valérien [ vm_ops, 3, FORTS ], [ vm_if, ()=>(game.active === COMMUNE) ], [ vm_decrease_prussian_collaboration ], [ vm_else ], [ vm_increase_prussian_collaboration ], [ vm_endif ], [ vm_return ], ] CODE[47] = [ // Raid on Château de Vincennes [ vm_prompt, "Use 2 Operations Points in Military or remove 1 from any space." ], [ vm_switch, ["ops","remove"] ], [ vm_case, "ops" ], [ vm_ops, 2, MILITARY ], [ vm_case, "remove" ], [ vm_remove, 1, ANY ], [ vm_endswitch ], [ vm_return ], ] CODE[48] = [ // Revolution in the Press [ vm_prompt, "Use 3 Operations Points in Public Opinion or replace 1 in any space." ], [ vm_switch, ["ops","replace"] ], [ vm_case, "ops" ], [ vm_ops, 3, PUBLIC_OPINION ], [ vm_case, "replace" ], [ vm_replace, 1, ANY ], [ vm_endswitch ], [ vm_return ], ] CODE[49] = [ // Pius IX [ vm_prompt, "Replace in 2 different Political or remove 1 from any space." ], [ vm_switch, ["replace","remove"] ], [ vm_case, "replace" ], [ vm_replace_different, 2, POLITICAL ], [ vm_case, "remove" ], [ vm_remove, 1, ANY ], [ vm_endswitch ], [ vm_return ], ] CODE[50] = [ // Socialist International [ vm_ops, 2, PUBLIC_OPINION ], [ vm_if, ()=>(game.active === COMMUNE) ], [ vm_increase_revolutionary_momentum ], [ vm_else ], [ vm_decrease_revolutionary_momentum ], [ vm_endif ], [ vm_return ], ] CODE[51] = [ // Royalists Dissension [ vm_prompt, "Use 2 Operations Points in Institutional or remove 1 from any space." ], [ vm_switch, ["ops","remove"] ], [ vm_case, "ops" ], [ vm_ops, 2, INSTITUTIONAL ], [ vm_case, "remove" ], [ vm_remove, 1, ANY ], [ vm_endswitch ], [ vm_return ], ] CODE[52] = [ // Rise of Republicanism [ vm_prompt, "Use 3 Operations Points in Institutional or replace 1 in Military." ], [ vm_switch, ["ops","replace"] ], [ vm_case, "ops" ], [ vm_ops, 3, INSTITUTIONAL ], [ vm_case, "replace" ], [ vm_replace, 1, MILITARY ], [ vm_endswitch ], [ vm_return ], ] CODE[53] = [ // Legitimacy [ vm_ops, 3, POLITICAL ], [ vm_if, ()=>(game.active === COMMUNE) ], [ vm_increase_revolutionary_momentum ], [ vm_else ], [ vm_increase_prussian_collaboration ], [ vm_endif ], [ vm_return ], ]