"use strict" // TODO: no card menu 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 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 ) } // === 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 player_set_aside(current) { if (game.active === COMMUNE) return game.red_set_aside return game.blue_set_aside } 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 // 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_control_dimension(dim) { for (let s of dim) if (!is_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_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_place_cube(s) { return find_available_cube() >= 0 && count_friendly_cubes(s) < 4 } function can_place_disc(s) { return find_available_disc() >= 0 && 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 place_piece(p, s) { game.pieces[p] = s } function place_cube(s) { game.pieces[find_available_cube()] = s } function place_disc(s) { game.pieces[find_available_disc()] = s } function find_available_cube() { let p = -1 if (game.active === COMMUNE) { 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 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 where_present(list) { return list.filter(s => is_present(s)) } // === CHOOSE OBJECTIVE CARD === states.choose_objective_card = { inactive: "choose an objective card", prompt(current) { view.prompt = "Choose an Objective card to keep." if (current === 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, current) { if (current === 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.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 > 0) { let final = player_final_crisis_card() if (final > 0) gen_action_card(final) if (game.discard > 0) 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() 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 has_breached_final_crisis_track(track) { if (side === COMMUNE) return count_cubes(RED_BONUS_CUBES[2]) else return !has_versailles_cube(BLUE_BONUS_CUBES[2]) } 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_final_crisis_discard(c, spaces) { log("Played C" + c + ".") game.state = "discard_final_crisis" game.count = 4 game.spaces = spaces } states.discard_final_crisis = { inactive: "discard a card to play final crisis", prompt() { view.prompt = "Discard a card to play Final Crisis card for ops." let hand = player_hand() for (let c of hand) gen_action("card", c) }, card(c) { push_undo() log(`Discarded C${c} to play Final Crisis card for ops.`) array_remove_item(player_hand(), c) game.discard = c goto_operations_remove() }, } // 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) } 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() } // OPERATIONS: PLACE 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() } // 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() { clear_undo() 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() } function goto_pivotal_space_bonus_actions() { game.state = "pivotal_space_bonus_actions" game.active = game.initiative } // === EVENTS === function goto_play_event(c) { update_presence_and_control() goto_vm(c) } function end_event() { 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_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_goto(vm_endif, vm_if, 1, 1) } 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" } function vm_place_disc() { game.vm.count = 1 game.vm.spaces = vm_operand_spaces(1) game.state = "vm_place_disc" } function vm_move_up_to() { game.vm.count = 1 game.vm.a = vm_operand_spaces(1) game.vm.b = vm_operand_spaces(2) game.state = "vm_move" } function vm_move_between_up_to() { game.vm.count = 1 game.vm.a = vm_operand_spaces(1) game.vm.b = vm_operand_spaces(2) game.state = "vm_move_between" } // === 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)) return true return false } function goto_vm_place() { if (can_vm_place()) 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) if (--game.vm.count === 0 || !can_vm_place()) 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()) game.state = "vm_place_disc" else 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() let s = game.pieces[p] remove_piece(p) place_cube(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("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() }, } // === 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: "Both", state: "choose_objective_card", round: 1, 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: [], blue_final: 17, blue_hand: [], blue_set_aside: [], blue_objective: [], 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") log_h1("Round 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) 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()) } 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 (current === COMMUNE) goto_game_over(VERSAILLES, "Commune resigned."); if (current === VERSAILLES) goto_game_over(COMMON, "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] = [] set_add(view.actions[action], 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 random_bigint(range) { // Largest MLCG that will fit its state in a double. // Uses BigInt for arithmetic, so is an order of magnitude slower. // https://www.ams.org/journals/mcom/1999-68-225/S0025-5718-99-00996-5/S0025-5718-99-00996-5.pdf // m = 2**53 - 111 return (game.seed = Number(BigInt(game.seed) * 5667072534355537n % 9007199254740881n)) % 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 } } function shuffle_bigint(list) { // Fisher-Yates shuffle for (let i = list.length - 1; i > 0; --i) { let j = random_bigint(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 } function array_insert(array, index, item) { for (let i = array.length; i > index; --i) array[i] = array[i - 1] array[index] = item } function array_remove_pair(array, index) { let n = array.length for (let i = index + 2; i < n; ++i) array[i - 2] = array[i] array.length = n - 2 } function array_insert_pair(array, index, key, value) { for (let i = array.length; i > index; i -= 2) { array[i] = array[i-2] array[i+1] = array[i-1] } array[index] = key array[index+1] = value } // Set as plain sorted array function set_clear(set) { set.length = 0 } function set_has(set, item) { let a = 0 let b = set.length - 1 while (a <= b) { let m = (a + b) >> 1 let x = set[m] if (item < x) b = m - 1 else if (item > x) a = m + 1 else return true } return false } function set_add(set, item) { let a = 0 let b = set.length - 1 while (a <= b) { let m = (a + b) >> 1 let x = set[m] if (item < x) b = m - 1 else if (item > x) a = m + 1 else return } array_insert(set, a, item) } function set_delete(set, item) { let a = 0 let b = set.length - 1 while (a <= b) { let m = (a + b) >> 1 let x = set[m] if (item < x) b = m - 1 else if (item > x) a = m + 1 else { array_remove(set, m) return } } } function set_toggle(set, item) { let a = 0 let b = set.length - 1 while (a <= b) { let m = (a + b) >> 1 let x = set[m] if (item < x) b = m - 1 else if (item > x) a = m + 1 else { array_remove(set, m) return } } array_insert(set, a, item) } // Map as plain sorted array of key/value pairs function map_clear(map) { map.length = 0 } function map_has(map, key) { let a = 0 let b = (map.length >> 1) - 1 while (a <= b) { let m = (a + b) >> 1 let x = map[m<<1] if (key < x) b = m - 1 else if (key > x) a = m + 1 else return true } return false } function map_get(map, key, missing) { let a = 0 let b = (map.length >> 1) - 1 while (a <= b) { let m = (a + b) >> 1 let x = map[m<<1] if (key < x) b = m - 1 else if (key > x) a = m + 1 else return map[(m<<1)+1] } return missing } function map_set(map, key, value) { let a = 0 let b = (map.length >> 1) - 1 while (a <= b) { let m = (a + b) >> 1 let x = map[m<<1] if (key < x) b = m - 1 else if (key > x) a = m + 1 else { map[(m<<1)+1] = value return } } array_insert_pair(map, a<<1, key, value) } function map_delete(map, item) { let a = 0 let b = (map.length >> 1) - 1 while (a <= b) { let m = (a + b) >> 1 let x = map[m<<1] if (item < x) b = m - 1 else if (item > x) a = m + 1 else { array_remove_pair(map, m<<1) return } } } // === 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_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_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_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_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_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_switch, ["military","political"] ], [ vm_case, "military" ], [ vm_ops, 3, MILITARY ], [ vm_case, "political" ], [ vm_ops, 3, POLITICAL ], [ vm_endswitch ], [ vm_return ], ] CODE[35] = [ // Conciliation [ vm_move_between_up_to, 2, PUBLIC_OPINION, PARIS ], [ 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_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_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_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.current === COMMUNE) ], [ vm_decrease_prussian_collaboration ], [ vm_else ], [ vm_increase_prussian_collaboration ], [ vm_endif ], [ vm_return ], ] CODE[47] = [ // Raid on Château de Vincennes [ 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_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_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.current === COMMUNE) ], [ vm_increase_revolutionary_momentum ], [ vm_else ], [ vm_decrease_revolutionary_momentum ], [ vm_endif ], [ vm_return ], ] CODE[51] = [ // Royalists Dissension [ 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_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 ], ]