"use strict" // TODO: clean up push/clear_undo // TODO: clean up prompts // TODO: event prompts // TODO: clean up 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 ] function is_same_list(a, b) { if (a !== b) return true if (a.length !== b.length) return false for (let i = 0; i < a.length; ++i) if (a[i] !== b[i]) return false return true } function space_list_name(list) { if (list.length === 1) return space_names[list[0]] if (list.length === 12) return "any space" if (is_same_list(list, POLITICAL)) return "Political" if (is_same_list(list, MILITARY)) return "Military" if (is_same_list(list, INSTITUTIONAL)) return "Institutional" if (is_same_list(list, PUBLIC_OPINION)) return "Public Opinion" if (is_same_list(list, FORTS)) return "Forts" if (is_same_list(list, PARIS)) return "Paris" if (is_same_list(list, PIVOTAL)) return "Pivotal" return "???" } 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 } function versailles_objective_card() { return game.blue_objective } 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 = 2; i >= 0; --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.censorship) n = 5 for (let i = 0; i < n; ++i) { game.red_hand.push(draw_strategy_card()) game.blue_hand.push(draw_strategy_card()) } game.red_objective = [ game.objective_deck.pop(), game.objective_deck.pop() ] game.blue_objective = [ game.objective_deck.pop(), 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 (typeof game.red_objective === "number" && typeof game.blue_objective === "number") end_choose_objective_card() else if (typeof game.red_objective === "number") game.active = VERSAILLES else if (typeof game.blue_objective === "number") 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.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] + " OP.") discard_card(game.what) goto_operations(card_ops[game.what], POLITICAL) }, military() { push_undo() log(card_ops[game.what] + " OP.") discard_card(game.what) goto_operations(card_ops[game.what], MILITARY) }, momentum() { push_undo() if (game.censorship) recycle_card(game.what) else discard_card(game.what) if (game.active === COMMUNE) { log("Increased Revolutionary Momentum.") advance_revolutionary_momentum(1) } else { log("Increased Prussian Momentum.") 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_final() discard_card(c) game.state = "play_final_ops" }, } states.play_final_ops = { prompt() { view.prompt = card_names[game.what] + ": Use up to 4 Ops." view.selected_card = game.what view.actions.political = 1 view.actions.military = 1 }, political() { push_undo() log("4 Ops.") goto_operations(4, POLITICAL) }, military() { push_undo() 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.pass = 1 }, space(s) { log("Placed red cube in S" + s + ".") place_cube(s) end_momentum_trigger() }, pass() { 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.pass = 1 }, space(s) { log("Placed blue cube in S" + s + ".") place_cube(s) end_momentum_trigger() }, pass() { 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() { clear_undo() 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() { let prompt = "Use " + game.count + " OP in " + space_list_name(game.spaces) + " to remove." //let prompt = "Use " + game.count + " OP to remove in " + space_list_name(game.spaces) + "." if (game.vm) event_prompt(prompt) else view.prompt = "Operations: " + prompt 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) { logi("Remove from S" + s + ".") remove_piece(p) resume_operations_remove() } else { log("Remove from S" + s + ":") game.who = p game.state = "operations_remove_spend" } } else { log("Removed from S" + s + ".") remove_piece(p) resume_operations_remove() } }, place() { push_undo() goto_operations_place() }, } states.operations_remove_spend = { prompt() { let p = game.who let s = game.pieces[p] let str = military_strength(s) view.prompt = "Operations: Military strength " + str + " \u2014 spend extra OP?" view.selected_cube = p if (game.count > 0) view.actions.spend = 1 else view.actions.spend = 0 view.actions.pass = 1 }, spend() { logi("Spent extra OP") game.count -= 1 attempt_remove_piece(1) }, pass() { 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 = draw_strategy_card() let str = military_strength(s) + extra let ops = card_ops[c] logi("Drew C" + c) logi("Strength " + str + " vs " + ops) if (str >= ops) { remove_piece(p) logi("Success") } else { logi("Failure") } 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() { //let prompt = "Use " + game.count + " OP to place in " + space_list_name(game.spaces) + "." let prompt = "Use " + game.count + " OP in " + space_list_name(game.spaces) + " to place." if (game.vm) event_prompt(prompt) else view.prompt = "Operations: " + prompt for (let s of game.spaces) if (can_operations_place_space(s)) gen_action_space(s) view.actions.pass = 1 }, space(s) { push_undo() if (has_enemy_disc(s)) game.count -= 2 else game.count -= 1 log("Placed in S" + s + ".") place_cube(s) resume_operations_place() }, pass() { 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() { if (game.vm) event_prompt("No OP left to spend in " + space_list_name(game.spaces) + ".") else view.prompt = "Operations: All done." view.actions.done = 1 }, done() { end_operations() }, } function end_operations() { if (game.vm) { vm_next() // Fast-forward past "All done" if Ops was the last thing in an event. if (game.state === "vm_return") end_event() } 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() { clear_undo() log_h2("Pivotal 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_h3("S" + s) 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.pass = 1 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() log("De-escalated") game.state = "de_escalate_1" }, spread_influence() { push_undo() log("Spread Influence") game.who = -1 game.count = 2 game.state = "spread_influence" }, turncoat() { push_undo() log("Turncoat") game.state = "turncoat" }, pass() { log("Passed") resume_pivotal_space_bonus_actions() }, } 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() let s = game.pieces[p] if (is_commune_cube(p)) logi("Commune S" + s) else logi("Versailles S" + s) 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() let s = game.pieces[p] if (is_commune_cube(p)) logi("Commune S" + s) else logi("Versailles S" + s) 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.done = 1 }, piece(p) { push_undo() game.who = p }, space(s) { let from = game.pieces[game.who] logi("S" + from + " to S" + s) move_piece(game.who, s) game.who = -1 if (--game.count === 0) resume_pivotal_space_bonus_actions() }, done() { 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) { let s = game.pieces[p] push_undo() logi("Replaced in S" + s) replace_cube(p) resume_pivotal_space_bonus_actions() }, } // === CRISIS DIMENSION SCORING === function score_control(s, name, cf, vf, arg) { if (is_political_space(s)) { if (cf(arg)) { log(name + ": Commune") add_political_vp(COMMUNE, 1) } else if (vf(arg)) { log(name + ": Versailles") add_political_vp(VERSAILLES, 1) } else { log(name + ": Nobody") } } else { if (cf(arg)) { log(name + ": Commune") add_military_vp(COMMUNE, 1) } else if (vf(arg)) { log(name + ": Versailles") add_military_vp(VERSAILLES, 1) } else { log(name + ": Nobody") } } } function goto_crisis_dimension_scoring() { clear_undo() log_h2("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) score_control(s, DIMENSION_NAME[s], is_commune_control_dimension, is_versailles_control_dimension, DIMENSION_SPACES[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() { log_h2("Objective Card Scoring") update_presence_and_control() game.reveal_objectives = 1 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()) log_br() } states.objective_card_scoring = { prompt() { view.prompt = "Objective Card Scoring!" if (game.count & 1) gen_action_space(commune_objective_space()) if (game.count & 2) gen_action_space(versailles_objective_space()) }, space(s) { score_control(s, "S" + s, is_commune_control, is_versailles_control, s) if (s === commune_objective_space()) game.count ^= 1 if (s === versailles_objective_space()) game.count ^= 2 if (game.count === 0) goto_objective_card_events() }, } function goto_objective_card_events() { if (!is_commune_control(commune_objective_space())) { log("Removed C" + commune_objective_card()) game.red_objective = 0 } if (!is_versailles_control(versailles_objective_space())) { log("Removed C" + versailles_objective_card()) game.blue_objective = 0 } resume_objective_card_events() } function resume_objective_card_events() { clear_undo() log_br() let c = commune_objective_card() let v = versailles_objective_card() if (c || v) { game.state = "objective_card_events" } else { delete game.reveal_objectives start_round() } } states.objective_card_events = { prompt() { view.prompt = "Objective Card Events!" let c = commune_objective_card() if (c) gen_action_card(c) let v = versailles_objective_card() if (v) gen_action_card(v) }, card(c) { if (c === commune_objective_card()) { game.red_objective = 0 game.red_fulfilled += 1 game.active = COMMUNE log_h2(COMMUNE) log("Played C" + c + ".") goto_play_event(c) } if (c === versailles_objective_card()) { game.blue_objective = 0 game.blue_fulfilled += 1 game.active = VERSAILLES log_h2(VERSAILLES) log("Played C" + c + ".") goto_play_event(c) } }, } // === 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_final = 0 game.red_set_aside = [] game.blue_hand = game.blue_set_aside if (game.blue_final) game.blue_hand.push(game.blue_final) game.blue_final = 0 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() 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.state = "final_crisis_events" 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 log_h2(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 { log("Played C" + c + ".") goto_play_event(c) } }, } states.final_crisis_opponent_event = { inactive: "play an event", prompt() { view.prompt = card_names[game.what] + ": Play or ignore event." view.selected_card = game.what view.actions.event = 1 view.actions.pass = 1 }, event() { log("Played C" + game.what + ".") goto_play_event(game.what) }, pass() { 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)) { assess_crisis_breach_all() 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.done = 1 }, done() { 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 > 0) game.state = "vm_decrease_revolutionary_momentum" else vm_next() } function vm_decrease_prussian_collaboration() { if (game.blue_momentum > 0) 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() }, political() { push_undo() game.vm.choice = "political" vm_next() }, military() { push_undo() game.vm.choice = "military" 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() log("Increased Revolutionary Momentum.") advance_revolutionary_momentum(1) }, } states.vm_increase_prussian_collaboration = { prompt() { event_prompt("Increase Prussian Collaboration.") view.actions.blue_momentum = 1 }, blue_momentum() { push_undo() log("Increased Prussian Collaboration.") advance_prussian_collaboration(1) }, } states.vm_decrease_revolutionary_momentum = { prompt() { event_prompt("Decrease Revolutionary Momentum.") view.actions.red_momentum = 1 }, red_momentum() { push_undo() log("Decreased Revolutionary Momentum.") advance_revolutionary_momentum(-1) }, } states.vm_decrease_prussian_collaboration = { prompt() { event_prompt("Decrease Prussian Collaboration.") view.actions.blue_momentum = 1 }, blue_momentum() { push_undo() log("Decreased Prussian Collaboration.") 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() 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) log("Placed in S" + s + ".") 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() if (game.current === COMMUNE) log("Moved Barricade from S" + s + ".") else log("Moved Fortification from S" + s + ".") remove_piece(p) game.state = "vm_place_disc" }, skip() { push_undo() vm_next() }, } states.vm_place_disc = { prompt() { event_prompt() 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() if (game.current === COMMUNE) log("Placed Barricade in S" + s + ".") else log("Placed Fortification in S" + s + ".") 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() 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() let s = game.pieces[p] replace_cube(p) log("Replaced in S" + s + ".") 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() 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() let s = game.pieces[p] log("Removed from S" + s + ".") remove_piece(p) if (--game.vm.count === 0 || !can_vm_remove()) vm_next() }, skip() { push_undo() vm_next() }, } states.vm_remove_own = { prompt() { event_prompt() for (let s of game.vm.spaces) for_each_friendly_cube(s, gen_action_piece) }, piece(p) { push_undo() let s = game.pieces[p] log("Removed own from S" + s + ".") remove_piece(p) 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() 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) { let old_s = game.pieces[game.who] move_piece(game.who, s) log("Moved from S" + old_s + " to S" + s + ".") game.who = -1 if (--game.vm.count === 0 || !can_vm_move()) vm_next() }, skip() { push_undo() vm_next() }, } // === COMPLICATED EVENT STATES === states.reveal_commune_objective = { prompt() { event_prompt("Revealing Commune player's Objective Card.") view.red_objective = game.red_objective view.actions.done = 1 }, done() { vm_next() }, } states.reveal_commune_hand = { prompt() { event_prompt("Revealing Commune player's hand.") view.hand = game.red_hand view.actions.done = 1 }, done() { vm_next() }, } states.general_louis_valentin = { prompt() { event_prompt("Remove 1 from up to 2 different Paris spaces where present.") 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() let s = game.pieces[p] array_remove_item(game.vm.spaces, s) log("Removed from S" + s + ".") remove_piece(p) if (--game.count === 0 || !can_vm_remove()) vm_next() }, skip() { vm_next() }, } function init_karl_marx() { clear_undo() // Draw cards into hand in case a reshuffle is triggered. game.red_hand.push(draw_strategy_card()) game.red_hand.push(draw_strategy_card()) game.red_hand.push(draw_strategy_card()) game.vm.cards = [ game.red_hand.pop(), game.red_hand.pop(), game.red_hand.pop(), ] } states.karl_marx_discard = { prompt() { event_prompt("Discard 2 cards, then play or discard the last card.") view.hand = game.vm.cards for (let c of game.vm.cards) gen_action_card(c) }, card(c) { push_undo() log("Discarded C" + c + ".") array_remove_item(game.vm.cards, c) game.discard = c if (game.vm.cards.length === 1) game.state = "karl_marx_play" }, } states.karl_marx_play = { prompt() { event_prompt("Play or discard the last card.") view.hand = game.vm.cards if (can_play_event(game.vm.cards[0])) view.actions.event = 1 else view.actions.event = 0 gen_action_card(game.vm.cards[0]) }, event() { let c = game.vm.cards[0] push_undo() log("Played C" + c + ".") game.discard = c goto_play_event(c) }, card(c) { push_undo() log("Discarded C" + c + ".") game.discard = c vm_next() }, } function can_play_freemason_parade() { for (let s of MILITARY) if (has_commune_cube(s) && has_versailles_cube(s)) return true return false } states.freemason_parade = { prompt() { event_prompt("Remove a cube from both factions in a single Military space.") for (let s of MILITARY) if (has_commune_cube(s) && has_versailles_cube(s)) gen_action_space(s) }, space(s) { game.vm.freemason_parade = s vm_next() }, } // === SETUP === exports.setup = function (seed, scenario, options) { game = { seed: seed, 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") if (scenario === "Censorship") { log_h2("Censorship") game.censorship = 1 } 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 } function reshuffle_strategy_deck() { game.discard = 0 game.strategy_deck = [] for (let c = 1; c <= 41; ++c) if (c !== 17 && c !== 34) if (!game.red_hand.includes(c) && !game.blue_hand.includes(c)) game.strategy_deck.push(c) shuffle(game.strategy_deck) } function draw_strategy_card() { if (game.strategy_deck.length === 0) { log("Reshuffled.") reshuffle_strategy_deck() } return game.strategy_deck.pop() } // === 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, red_final: game.red_final, blue_final: game.blue_final, red_objective: 0, blue_objective: 0, pieces: game.pieces, discard: game.discard, hand: 0, final: 0, set_aside: 0, } if (game.censorship) view.censorship = 1 if (typeof game.red_objective === "object") view.red_objective = 2 else if (game.red_objective > 0) view.red_objective = 1 if (typeof game.blue_objective === "object") view.blue_objective = 2 else if (game.blue_objective > 0) view.blue_objective = 1 if (player === COMMUNE) { view.hand = game.red_hand view.set_aside = game.red_set_aside view.red_objective = game.red_objective } if (player === VERSAILLES) { view.hand = game.blue_hand view.set_aside = game.blue_set_aside view.blue_objective = game.blue_objective } if (game.reveal_objectives) { view.red_objective = game.red_objective view.blue_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() } function log_h3(msg) { log_br() log(".h3 " + 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_asm, ()=>clear_undo() ], [ vm_if, ()=>(game.round < 4) ], [ vm_asm, ()=>log("Commune Objective:") ], [ vm_asm, ()=>logi("C" + game.red_objective) ], [ vm_goto, "reveal_commune_objective" ], [ vm_else ], [ vm_asm, ()=>log("Commune hand:") ], [ vm_asm, ()=>{ for (let c of game.red_hand) logi("C" + c) } ], [ vm_goto, "reveal_commune_hand" ], [ vm_endif ], [ vm_ops, 1, MILITARY ], [ vm_return ], ] CODE[2] = [ // The Murder of Vincenzini [ vm_increase_revolutionary_momentum ], [ vm_prompt, "Remove up to 3 from Political." ], [ vm_remove_up_to, 3, POLITICAL ], [ vm_return ], ] CODE[3] = [ // Brassardiers [ vm_prompt, "Replace 1 in Military where present." ], [ 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_prompt, "Place up to 2 in Republicans." ], [ vm_place_up_to, 2, REPUBLICANS ], [ vm_case, "replace" ], [ vm_prompt, "Replace 1 in National Assembly." ], [ 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_prompt, "Place up to 2 in Press." ], [ vm_place_up_to, 2, PRESS ], [ vm_case, "replace" ], [ vm_prompt, "Replace 1 in Public Opinion." ], [ vm_replace, 1, PUBLIC_OPINION ], [ vm_endswitch ], [ vm_return ], ] CODE[6] = [ // Général Louis Valentin [ vm_asm, ()=>game.count = 2 ], [ vm_asm, ()=>game.vm.spaces = where_present(PARIS) ], [ vm_if, ()=>(can_vm_remove()) ], [ vm_goto, "general_louis_valentin" ], [ vm_endif ], [ vm_return ], ] CODE[7] = [ // Général Espivent [ vm_prompt, "Remove up to 2 from Social Movements." ], [ vm_remove_up_to, 2, SOCIAL_MOVEMENTS ], [ vm_return ], ] CODE[8] = [ // Les Amis de l'Ordre [ vm_prompt, "Place 2 in Paris." ], [ vm_place, 2, PARIS ], [ vm_return ], ] CODE[9] = [ // Socialist Newspaper Ban [ vm_increase_revolutionary_momentum ], [ vm_prompt, "Remove up to 2 from Press." ], [ vm_remove_up_to, 2, PRESS ], [ vm_asm, ()=>game.snb=1 ], [ vm_return ], ] CODE[10] = [ // Fortification of Mont-Valérien [ vm_prompt, "Place up to 1 in Mont-Valérien." ], [ vm_place_up_to, 1, MONT_VALERIEN ], [ vm_asm, ()=>update_presence_and_control() ], [ vm_if, ()=>(is_control(MONT_VALERIEN)) ], [ vm_prompt, "You may place a Fortification in Mont-Valérien." ], [ vm_may_place_disc, MONT_VALERIEN ], [ vm_endif ], [ vm_return ], ] CODE[11] = [ // Adolphe Thiers [ vm_increase_prussian_collaboration ], [ vm_prompt, "Place up to 2 in Royalists." ], [ vm_place_up_to, 2, ROYALISTS ], [ vm_return ], ] CODE[12] = [ // Otto von Bismarck [ vm_prompt, "Remove 1 from National Assembly." ], [ 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 OP 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_prompt, "Remove all from Catholic Church." ], [ vm_remove, 20, CATHOLIC_CHURCH ], [ vm_return ], ] CODE[17] = [ // Maréchal Macmahon [ vm_ops, 4, PARIS ], [ vm_return ], ] CODE[18] = [ // Paule Minck [ vm_prompt, "Replace 1 in Military where present." ], [ vm_replace, 1, ()=>(where_present(MILITARY)) ], [ vm_return ], ] CODE[19] = [ // Walery Wroblewski [ vm_prompt, "Place one Barricade in Paris where present." ], [ vm_place_disc, ()=>(where_present(PARIS)) ], [ vm_return ], ] CODE[20] = [ // Banque de France [ vm_decrease_revolutionary_momentum ], [ vm_prompt, "Place up to 1 in Paris." ], [ vm_place_up_to, 1, PARIS ], [ vm_prompt, "You may place a Barricade in Paris." ], [ vm_may_place_disc, PARIS ], [ vm_return ], ] CODE[21] = [ // Le Réveil [ vm_increase_revolutionary_momentum ], [ vm_prompt, "Place up to 1 in Press." ], [ vm_place_up_to, 1, PRESS ], [ vm_return ], ] CODE[22] = [ // Execution of Generals [ vm_increase_prussian_collaboration ], [ vm_prompt, "Replace up to 2 in Paris where present." ], [ vm_replace_up_to, 2, ()=>(where_present(PARIS)) ], [ vm_return ], ] CODE[23] = [ // Les Cantinières [ vm_prompt, "Place up to 2 in Military where present (use removed cubes)." ], [ 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 OP 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_prompt, "Place up to 1 in Paris." ], [ vm_place_up_to, 1, PARIS ], [ vm_prompt, "You may place a Barricade in Paris." ], [ vm_may_place_disc, PARIS ], [ vm_return ], ] CODE[29] = [ // Raoul Rigault [ vm_increase_revolutionary_momentum ], [ vm_prompt, "Move up to 2 from Catholic Church to Paris." ], [ vm_move_up_to, 2, CATHOLIC_CHURCH, PARIS ], [ vm_return ], ] CODE[30] = [ // Karl Marx [ vm_asm, ()=>init_karl_marx() ], [ vm_goto, "karl_marx_discard" ], [ 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_ops, 3, MILITARY ], [ vm_ops, 1, POLITICAL ], [ 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_prompt, "Move up to 2 from Public Opinion to Paris." ], [ vm_move_up_to, 2, PUBLIC_OPINION, PARIS ], [ vm_case, "paris" ], [ vm_prompt, "Move up to 2 from Paris to Public Opinion." ], [ vm_move_up_to, 2, PARIS, PUBLIC_OPINION ], [ vm_endswitch ], [ vm_return ], ] CODE[36] = [ // Georges Clemenceau [ vm_prompt, "Move up to 3 from Paris to Institutional." ], [ vm_move_up_to, 3, PARIS, INSTITUTIONAL ], [ vm_return ], ] CODE[37] = [ // Archbishop Georges Darboy [ vm_prompt, "Move up to 4 from any spaces to Catholic Church." ], [ 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, "Remove a cube from both factions." ], [ vm_remove_own, ()=>(game.vm.freemason_parade) ], [ vm_remove, 1, ()=>(game.vm.freemason_parade) ], [ vm_prompt, "Place up to 2 or replace 1 in Institutional." ], [ vm_switch, ["place","replace"] ], [ vm_case, "place" ], [ vm_prompt, "Place up to 2 in Institutional." ], [ vm_place_up_to, 2, INSTITUTIONAL ], [ vm_case, "replace" ], [ vm_prompt, "Replace 1 in Institutional." ], [ vm_replace, 1, INSTITUTIONAL ], [ vm_endswitch ], [ vm_return ], ] CODE[42] = [ // Paris Cannons [ vm_if, ()=>(can_advance_momentum()) ], [ vm_prompt, "Use 3 OP 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_else ], [ vm_ops, 3, PARIS ], [ vm_endif ], [ vm_return ], ] CODE[43] = [ // Aux Barricades! [ vm_ops, 2, PARIS ], [ vm_player, COMMUNE ], [ vm_prompt, "You may place a Barricade in Butte-aux-Cailles." ], [ vm_may_place_disc, BUTTE_AUX_CAILLES ], [ vm_return ], ] CODE[44] = [ // Commune's Stronghold [ vm_prompt, "Use 2 OP in Military or remove 1 from any space." ], [ vm_switch, ["ops","remove"] ], [ vm_case, "ops" ], [ vm_ops, 2, MILITARY ], [ vm_case, "remove" ], [ vm_prompt, "Remove 1 from any space." ], [ vm_remove, 1, ANY ], [ vm_endswitch ], [ vm_return ], ] CODE[45] = [ // Fighting in Issy Village [ vm_ops, 2, FORTS ], [ vm_player, VERSAILLES ], [ vm_prompt, "You may place a Fortification in Fort d'Issy." ], [ 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 OP 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 OP 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 OP 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 OP 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 ], ]