'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.roles = exports.scenarios = void 0; exports.action = action; exports.view = game_view; exports.setup = setup; const data_1 = require("./data"); const OBSERVER = 'Observer'; const states = {}; let game = {}; var view = {}; const role_ids = [data_1.ANARCHISTS_ID, data_1.COMMUNISTS_ID, data_1.MODERATES_ID]; const faction_player_map = { a: data_1.ANARCHIST, c: data_1.COMMUNIST, m: data_1.MODERATE, }; const player_faction_map = { [data_1.ANARCHIST]: data_1.ANARCHISTS_ID, [data_1.COMMUNIST]: data_1.COMMUNISTS_ID, [data_1.MODERATE]: data_1.MODERATES_ID, }; const front_names = { a: 'Aragon Front', m: 'Madrid Front', n: 'Northern Front', s: 'Southern Front', d: 'the Front closest to Defeat', v: 'the Front closest to Victory', }; const bonus_names = ['Morale Bonus', 'Teamwork Bonus']; const { cards, medallions, tracks, } = data_1.default; const bonuses = [data_1.MORALE_BONUS, data_1.TEAMWORK_BONUS]; const faction_cards = { [data_1.ANARCHISTS_ID]: make_list(37, 54), [data_1.COMMUNISTS_ID]: make_list(19, 36), [data_1.MODERATES_ID]: make_list(1, 18), }; const fascist_decks = { 1: make_list(55, 72), 2: make_list(73, 90), 3: make_list(91, 108), }; exports.scenarios = ['Standard']; exports.roles = [data_1.ANARCHIST, data_1.COMMUNIST, data_1.MODERATE]; function gen_action(action, argument) { if (argument === undefined) { view.actions[action] = 1; } else { if (!(action in view.actions)) view.actions[action] = []; view.actions[action].push(argument); } } function gen_action_blank_marker(marker_id) { gen_action('blank_marker', marker_id); } function gen_action_bonus(bonus_id) { gen_action('bonus', bonus_id); } function gen_action_card(card_id) { gen_action('card', card_id); } function gen_action_front(front_id) { gen_action('front', front_id); } function gen_action_medallion(medallion_id) { gen_action('medallion', medallion_id); } function gen_spend_hero_points() { const faction = get_active_faction(); const can_spend_hp = game.faction_turn === faction && game.hero_points[faction] > 0; if (can_spend_hp) { gen_action('spend_hp'); } } function action(state, player, action, arg) { game = state; if (action !== 'undo' && game.state !== 'choose_card') { push_undo(); } let S = states[game.state]; if (action in S) S[action](arg, player); else if (action === 'undo' && game.undo && game.undo.length > 0) pop_undo(); else throw new Error('Invalid action: ' + action); return game; } const state_node = 'l'; const seq_node = 's'; const function_node = 'f'; const resolved = 1; function create_state_node(state, faction, args) { return { t: state_node, s: state, p: faction, a: args, r: 0, }; } function create_function_node(func_name, args) { return { t: function_node, f: func_name, a: args, r: 0, }; } function create_seq_node(children) { return { t: seq_node, c: children, }; } function checkpoint() { if (game.undo.length > 0) { insert_after_active_node(create_state_node('confirm_turn', get_active_faction())); } resolve_active_and_proceed(); } function setup_bag_of_glory() { log_h1('Bag of Glory'); game.engine = [ create_state_node('add_glory', game.initiative), create_function_node('end_of_turn'), ]; next(); } function setup_choose_card() { game.fascist = 0; game.engine = [create_state_node('choose_card', 'all')]; game.engine.push(create_function_node('setup_player_turn')); next(); } function setup_final_bid() { game.fascist = 0; log_h1('Final Bid'); const player_order = get_player_order(); game.engine = player_order.map((faction_id) => create_state_node('choose_final_bid', faction_id)); game.engine.push(create_function_node('checkpoint')); game.engine.push(create_function_node('resolve_final_bid')); game.engine.push(create_function_node('setup_choose_card')); next(); } function setup_player_turn() { game.fascist = 0; game.card_played = 0; const next_faction = game.first_player === null ? get_player_order()[0] : get_next_faction_in_player_order(get_active_faction()); if (game.first_player === null) { game.first_player = next_faction; } game.engine = [ create_function_node('start_of_player_turn', { f: next_faction }), create_state_node('player_turn', next_faction), create_function_node('end_of_player_turn', { f: next_faction }), ]; next(); } function check_end_of_year_discard() { const { f: faction } = get_active_node_args(); if (game.hands[faction].length > get_hand_limit(faction) || game.tableaus[faction].length > game.year) { insert_after_active_node(create_state_node('end_of_year_discard', faction)); } resolve_active_and_proceed(); } function end_of_player_turn() { const { f: faction } = get_active_node_args(); if (get_next_faction_in_player_order(faction) === game.first_player) { game.engine = [ create_state_node('change_active_player', game.initiative), create_function_node('resolve_fascist_test'), create_function_node('setup_bag_of_glory'), ]; } else { game.engine = [create_function_node('setup_player_turn')]; } next(); } function start_of_player_turn() { const args = get_active_node_args(); const player = faction_player_map[args.f]; game.faction_turn = args.f; game.played_card = game.selected_cards[args.f][0]; log_h2(cards[game.played_card].title, player); resolve_active_and_proceed(); } const engine_functions = { check_end_of_year_discard, checkpoint, end_of_player_turn, end_of_turn, end_of_year_cleanup, end_resolving_event_effects, setup_bag_of_glory, setup_choose_card, setup_final_bid, setup_player_turn, start_of_player_turn, start_year, resolve_fascist_test, resolve_final_bid, card1_event2, card3_event2, card10_event2, card16_event2, card17_event3, card20_event3, card22_event3, card23_event1, card26_event1, card29_event2, card35_event2, card42_event3, card45_event2, card46_event3, card50_event2, card53_event2, card54_event1, setup_return_card_from_trash, trash_card, }; function get_active(engine) { for (let i of engine) { if ((i.t === state_node || i.t === function_node) && i.r !== resolved) { return { parent: engine, node: i }; } if (i.t === seq_node) { const next_child = get_active(i.c); if (next_child !== null) { return next_child; } } } return null; } function get_active_node(engine = game.engine) { const a = get_active(engine); return a === null ? null : a.node; } function get_nodes_for_state(state, engine = game.engine) { let nodes = []; for (let i of engine) { if (i.t === state_node && i.s === state) { nodes.push(i); } if (i.t === seq_node) { nodes = nodes.concat(get_nodes_for_state(state, i.c)); } } return nodes; } function get_active_node_args() { const node = get_active_node(game.engine); if (node.t === state_node || node.t === function_node) { return node.a ?? {}; } return null; } function update_active_node_args(args) { const node = get_active_node(game.engine); if (node.t === state_node || node.t === function_node) { node.a = { ...node.a, ...args, }; } } function insert_before_or_after_active_node(node, position, engine = game.engine) { const a = get_active(engine); if (a === null) { return; } const i = a.parent.indexOf(a.node); if (i >= 0) { array_insert(a.parent, i + (position == 'after' ? 1 : 0), node); } } function insert_after_active_node(node, engine = game.engine) { insert_before_or_after_active_node(node, 'after', engine); } function insert_before_active_node(node, engine = game.engine) { insert_before_or_after_active_node(node, 'before', engine); } function get_next_active(p) { if (Array.isArray(p)) { return p.map((faction) => faction_player_map[faction]); } if (p === 'all') { return exports.roles.slice(); } if (p === 'None') { return 'None'; } else { return faction_player_map[p]; } } function next(checkpoint = false) { if (checkpoint) { clear_undo(); } const node = get_active_node(game.engine); if (node.t === function_node && engine_functions[node.f]) { const args = node.a; if (args) { engine_functions[node.f](args); } else { engine_functions[node.f](); } } else if (node.t === 'l') { game.state = node.s; const current_active = game.active; const next_active = get_next_active(node.p); if (next_active !== current_active && game.undo.length > 0) { insert_before_active_node(create_state_node('confirm_turn', get_active_faction())); game.state = 'confirm_turn'; return; } game.active = next_active; if (states[game.state].auto_resolve && states[game.state].auto_resolve()) { resolve_active_and_proceed(); } } } function resolve_active_node() { const next_node = get_active_node(game.engine); if (next_node !== null) { next_node.r = resolved; } } function resolve_active_and_proceed(checkpoint = false) { resolve_active_node(); next(checkpoint); } function game_view(state, current) { game = state; const faction = current === OBSERVER ? null : player_faction_map[current]; view = { log: game.log, prompt: null, bonuses: game.bonuses, current_events: game.current_events, first_player: game.first_player, fronts: game.fronts, glory: game.glory, hand: faction === null ? [] : game.hands[faction], hero_points: game.hero_points, initiative: game.initiative, medallions: game.medallions, played_card: game.played_card, player_order: current === OBSERVER ? get_player_order() : get_player_order_in_game(faction), selected_cards: current === OBSERVER ? [] : game.selected_cards[faction], tableaus: game.tableaus, tracks: game.tracks, triggered_track_effects: game.triggered_track_effects, used_medallions: game.used_medallions, year: game.year, fascist: game.fascist, }; if (!game.hidden_bag) view.bag_of_glory = game.bag_of_glory; if (game.state === 'game_over') { view.prompt = game.victory; } else if (current !== game.active && !game.active.includes(current)) { let inactive = states[game.state].inactive || game.state; view.prompt = Array.isArray(game.active) ? `Waiting for ${game.active.join(' and ')} to ${inactive}.` : `Waiting for ${game.active} to ${inactive}.`; } else { view.actions = {}; if (game.undo && game.undo.length > 0) view.actions.undo = 1; else view.actions.undo = 0; states[game.state].prompt(current); let node = get_active_node(); if (node && node.a && node.a.src) view.prompt = get_source_name(node.a.src) + ": " + view.prompt; } return view; } function setup(seed, _scenario, options) { game = { seed: seed, state: null, active: data_1.ANARCHIST, active_abilities: [], bag_of_glory: [data_1.ANARCHISTS_ID, data_1.COMMUNISTS_ID, data_1.MODERATES_ID], bonuses: [data_1.ON, data_1.ON], current_events: [], discard: { a: [], c: [], m: [], f: [], }, engine: [], faction_turn: null, fronts: { a: { value: -2, contributions: [], status: null, }, m: { value: -2, contributions: [], status: null, }, n: { value: -2, contributions: [], status: null, }, s: { value: -2, contributions: [], status: null, }, }, glory: [], first_player: null, hands: { a: [], c: [], m: [], }, hero_points: { a: 2, c: 2, m: 0, pool: 14, }, initiative: data_1.MODERATES_ID, medallions: { a: [], c: [], m: [], pool: [], }, played_card: null, player_order: [data_1.MODERATE], selected_cards: { a: [], c: [], m: [], }, tableaus: { a: [], c: [], m: [], }, tracks: [5, 5, 6, 3, 3], trash: { a: [], c: [], m: [], }, triggered_track_effects: [], log: [], undo: [], used_medallions: [], top_of_events_deck: null, turn: 0, year: 0, glory_current_year: null, fascist: 0, card_played: 0, }; if (options.hidden_bag) game.hidden_bag = 1; game.player_order.push(exports.roles[random(2)]); game.player_order.push(game.player_order[1] === data_1.ANARCHIST ? data_1.COMMUNIST : data_1.ANARCHIST); draw_medallions(); start_year(); return game; } function draw_hand_cards(faction_id, count) { const deck = list_deck(faction_id); if (game.medallions[faction_id].includes(data_1.INTELLIGENCE_MEDALLION_ID)) { count++; } let drawn_cards = 0; if (deck.length < count) { count = count - deck.length; drawn_cards += deck.length; game.hands[faction_id] = game.hands[faction_id].concat(deck); game.discard[faction_id] = []; } for (let i = 0; i < count; i++) { const deck = list_deck(faction_id); if (deck.length > 0) { game.hands[faction_id].push(draw_card(deck)); drawn_cards++; } } const log = drawn_cards === 1 ? `${get_player(faction_id)} draws 1 card` : `${get_player(faction_id)} draws ${drawn_cards} cards`; logi(log); } function start_year() { game.year++; game.turn = 1; game.current_events = []; role_ids.forEach((role) => { draw_hand_cards(role, 5); }); start_turn(); } function start_turn() { log_h1(`Year ${game.year} - Turn ${game.turn}`); const cardId = draw_fascist_card(); game.current_events.push(cardId); const card = cards[cardId]; log_h2(card.title, 'fascist'); game.fascist = 1; game.engine = card.effects.map((effect) => resolve_effect(effect, 'fascist_event')); game.engine.push(create_state_node('confirm_fascist_turn', game.initiative, { src: 'fascist_event' })); if (game.year === 3 && game.turn === 4) { game.engine.push(create_function_node('setup_final_bid')); } else { game.engine.push(create_function_node('setup_choose_card')); } next(); } function player_can_resolve_icon(icon) { if (icon === 'teamwork_on' && game.bonuses[data_1.TEAMWORK_BONUS] === data_1.ON) { return false; } return true; } const track_icon_to_track_id_map = { collectivization: data_1.COLLECTIVIZATION, d_collectivization: data_1.COLLECTIVIZATION, foreign_aid: data_1.FOREIGN_AID, d_foreign_aid: data_1.FOREIGN_AID, government: data_1.GOVERNMENT, d_government: data_1.GOVERNMENT, liberty: data_1.LIBERTY, d_liberty: data_1.LIBERTY, soviet_support: data_1.SOVIET_SUPPORT, d_soviet_support: data_1.SOVIET_SUPPORT, }; states.activate_icon = { inactive: 'activate an icon', prompt() { gen_spend_hero_points(); const c = cards[game.played_card]; view.prompt = 'Morale Bonus: '; view.prompt += join_oxford_comma(c.icons.map(get_icon_name), "or"); view.prompt += '.'; let can_activate_icon = false; for (const i of c.icons) { const count = get_icon_count_in_tableau(i); let direction = 0; switch (i) { case 'add_to_front': const possible_fronts = get_fronts_to_add_to(data_1.ANY); for (let f of possible_fronts) { gen_action_front(f); } if (possible_fronts.length > 0) { can_activate_icon = true; } break; case 'collectivization': case 'foreign_aid': case 'liberty': case 'soviet_support': const can_move_ss = gen_move_track(track_icon_to_track_id_map[i], game.tracks[track_icon_to_track_id_map[i]] + count); can_activate_icon = can_activate_icon || can_move_ss; break; case 'government': direction = game.active === data_1.COMMUNIST ? -1 : 1; const can_move_g = gen_move_track(track_icon_to_track_id_map[i], game.tracks[track_icon_to_track_id_map[i]] + direction * count); can_activate_icon = can_activate_icon || can_move_g; break; case 'd_collectivization': case 'd_foreign_aid': case 'd_government': case 'd_liberty': case 'd_soviet_support': const can_move_t = gen_move_track(track_icon_to_track_id_map[i], game.tracks[track_icon_to_track_id_map[i]] - count); can_activate_icon = can_activate_icon || can_move_t; break; case 'government_to_center': direction = game.tracks[data_1.GOVERNMENT] >= 6 ? -1 : 1; const can_move_gtoc = gen_move_track(track_icon_to_track_id_map[i], game.tracks[track_icon_to_track_id_map[i]] + direction * count); can_activate_icon = can_activate_icon || can_move_gtoc; break; case 'teamwork_on': if (game.bonuses[data_1.TEAMWORK_BONUS] === data_1.OFF) { gen_action_bonus(data_1.TEAMWORK_BONUS); can_activate_icon = true; } else { can_activate_icon = false; } break; default: gen_action(i); } if (!player_can_resolve_icon(i)) { view.actions[i] = 0; } if (!can_activate_icon) { gen_action('skip'); } } }, spend_hp() { resolve_spend_hp(); }, front(f) { update_front(f, get_icon_count_in_tableau('add_to_front'), get_active_faction()); resolve_active_and_proceed(); }, tr0(x) { if (can_use_medallion(data_1.ORGANIZATION_MEDALLION_ID)) { insert_use_organization_medallion_node(data_1.LIBERTY, x); } else { move_track_to(0, x); } resolve_active_and_proceed(); }, tr1(x) { if (can_use_medallion(data_1.ORGANIZATION_MEDALLION_ID)) { insert_use_organization_medallion_node(data_1.COLLECTIVIZATION, x); } else { move_track_to(1, x); } resolve_active_and_proceed(); }, tr2(x) { if (can_use_medallion(data_1.ORGANIZATION_MEDALLION_ID)) { insert_use_organization_medallion_node(data_1.GOVERNMENT, x); } else { move_track_to(2, x); } resolve_active_and_proceed(); }, tr3(x) { if (can_use_medallion(data_1.ORGANIZATION_MEDALLION_ID)) { insert_use_organization_medallion_node(data_1.SOVIET_SUPPORT, x); } else { move_track_to(3, x); } resolve_active_and_proceed(); }, tr4(x) { if (can_use_medallion(data_1.ORGANIZATION_MEDALLION_ID)) { insert_use_organization_medallion_node(data_1.FOREIGN_AID, x); } else { move_track_to(4, x); } resolve_active_and_proceed(); }, draw_card() { draw_hand_cards(get_active_faction(), get_icon_count_in_tableau('draw_card')); resolve_active_and_proceed(); }, bonus(b) { update_bonus(b, data_1.ON); resolve_active_and_proceed(); }, skip() { resolve_active_and_proceed(); }, }; states.add_card_to_tableau = { inactive: 'add a card to their tableau', prompt() { gen_spend_hero_points(); view.prompt = 'Add a card to your tableau.'; const faction = get_active_faction(); for (const c of game.hands[faction]) { gen_action_card(c); } if (game.hands[faction].length === 0) { gen_action('skip'); } }, spend_hp() { resolve_spend_hp(); }, card(c) { const faction_id = get_active_faction(); const card = cards[c]; array_remove(game.hands[faction_id], game.hands[faction_id].indexOf(c)); game.tableaus[faction_id].push(c); logi(`${faction_player_map[faction_id]} adds ${card.title} to their tableau`); resolve_active_and_proceed(); }, skip() { resolve_active_and_proceed(); }, }; states.add_glory = { inactive: 'add tokens to the Bag of Glory', prompt() { gen_spend_hero_points(); view.prompt = 'Add tokens to the Bag of Glory.'; gen_action('add_glory'); }, spend_hp() { resolve_spend_hp(); }, add_glory() { let number = 1; if (game.turn === 4) { number++; } add_glory(get_active_faction(), number); resolve_active_and_proceed(); }, }; states.add_to_front = { inactive: 'support a Front', prompt() { gen_spend_hero_points(); const args = get_active_node_args(); const possible_fronts = get_fronts_to_add_to(args.t); if (possible_fronts.length === 0) { view.prompt = 'No valid front to add strength to.'; gen_action('skip'); } else if (possible_fronts.length === 4) { view.prompt = `Support any Front.`; } else { view.prompt = `Support ${join_oxford_comma(possible_fronts.map(x => front_names[x]), 'or')}.`; } for (let f of possible_fronts) { gen_action_front(f); } }, spend_hp() { resolve_spend_hp(); }, front(f) { const value = get_active_node_args().v; update_front(f, value, get_active_faction()); resolve_active_and_proceed(); }, skip() { resolve_active_and_proceed(); }, }; states.attack_front = { inactive: 'attack a Front', prompt() { gen_spend_hero_points(); const { t: target, n } = get_active_node_args(); const possible_fronts = get_fronts_to_add_to(target, n); const number_of_fronts = possible_fronts.length; if (number_of_fronts === 0) { view.prompt = 'No valid front to attack.'; gen_action('skip'); } else if (possible_fronts.length === 4) { view.prompt = `Attack any Front.`; } else { view.prompt = `Attack ${join_oxford_comma(possible_fronts.map(x => front_names[x]), 'or')}.`; } possible_fronts.forEach((id) => gen_action('front', id)); }, spend_hp() { resolve_spend_hp(); }, front(f) { const value = get_active_node_args().v; update_front(f, value); resolve_active_and_proceed(); }, skip() { resolve_active_and_proceed(); }, }; states.break_tie_final_bid = { inactive: 'break tie for Final Bid', prompt() { view.prompt = 'Choose the winner of the Final Bid'; const { winners } = get_active_node_args(); for (const f of winners) { gen_action(faction_player_map[f]); } }, Anarchist() { win_final_bid(data_1.ANARCHISTS_ID); resolve_active_and_proceed(); }, Communist() { win_final_bid(data_1.COMMUNISTS_ID); resolve_active_and_proceed(); }, Moderate() { win_final_bid(data_1.MODERATES_ID); resolve_active_and_proceed(); }, }; states.break_tie_winner = { inactive: 'break tie for winner of the game', prompt() { view.prompt = 'Choose the winner of the game'; const { winners } = get_active_node_args(); for (const f of winners) { gen_action(faction_player_map[f]); } }, Anarchist() { const { glory } = get_active_node_args(); win_game(data_1.ANARCHIST, glory); resolve_active_and_proceed(); }, Communist() { const { glory } = get_active_node_args(); win_game(data_1.COMMUNIST, glory); resolve_active_and_proceed(); }, Moderate() { const { glory } = get_active_node_args(); win_game(data_1.MODERATE, glory); resolve_active_and_proceed(); }, }; states.change_active_player = { inactive: '', auto_resolve() { return true; }, prompt() { view.prompt = ''; }, }; states.choose_area_ap = { inactive: 'choose area to use Action Points', prompt() { gen_spend_hero_points(); view.prompt = 'Use Action Points.'; const strength = get_active_node_args().strength; let can_use_ap = false; for (const track of tracks) { can_use_ap = gen_move_track_change(track.id, strength) || can_use_ap; } const fronts = get_fronts_to_add_to(data_1.ANY); if (fronts.length > 0) { can_use_ap = true; } for (const front of fronts) { gen_action_front(front); } for (const bonus of bonuses) { if (game.bonuses[bonus] === data_1.OFF) { gen_action_bonus(bonus); can_use_ap = true; } } if (!can_use_ap) { gen_action('skip'); } }, spend_hp() { resolve_spend_hp(); }, bonus(b) { update_bonus(b, data_1.ON); const s = get_active_node_args().strength; const other_bonus = b === data_1.TEAMWORK_BONUS ? data_1.MORALE_BONUS : data_1.TEAMWORK_BONUS; if (s > 1 && game.bonuses[other_bonus] === data_1.OFF) { insert_after_active_node(resolve_effect((0, data_1.create_effect)('bonus', other_bonus, data_1.ON))); } resolve_active_and_proceed(); }, front(f) { const s = get_active_node_args().strength; update_front(f, s, get_active_faction()); resolve_active_and_proceed(); }, tr0(x) { move_track_to(0, x); resolve_active_and_proceed(); }, tr1(x) { move_track_to(1, x); resolve_active_and_proceed(); }, tr2(x) { move_track_to(2, x); resolve_active_and_proceed(); }, tr3(x) { move_track_to(3, x); resolve_active_and_proceed(); }, tr4(x) { move_track_to(4, x); resolve_active_and_proceed(); }, skip() { resolve_active_and_proceed(); }, }; states.change_bonus = { inactive: 'select Bonus', prompt() { gen_spend_hero_points(); const args = get_active_node_args(); if ((args.v === data_1.ON && game.bonuses[data_1.TEAMWORK_BONUS] === data_1.ON && game.bonuses[data_1.MORALE_BONUS] === data_1.ON) || (args.v === data_1.OFF && game.bonuses[args.t] === data_1.OFF)) { gen_action('skip'); } if (args.t === data_1.ANY && args.v === data_1.ON) { view.prompt = 'Turn on a Bonus.'; for (const bonus of bonuses) { if (game.bonuses[bonus] === data_1.OFF) { gen_action_bonus(bonus); } } } else { view.prompt = `Turn ${args.v === data_1.OFF ? 'off' : 'on'} ${bonus_names[args.t]}.`; gen_action_bonus(args.t); } }, spend_hp() { resolve_spend_hp(); }, bonus(b) { const value = get_active_node_args().v; update_bonus(b, value); resolve_active_and_proceed(); }, skip() { resolve_active_and_proceed(); }, }; states.play_card = { inactive: 'choose a card', prompt() { gen_spend_hero_points(); view.prompt = 'Play a card.'; const faction = get_active_faction(); const hand = game.hands[faction]; for (let c of hand) { if (!game.selected_cards[faction].includes(c)) { gen_action_card(c); } } }, spend_hp() { resolve_spend_hp(); }, card(c) { const faction = get_active_faction(); game.selected_cards[faction] = [c]; game.card_played = 0; game.played_card = game.selected_cards[faction][0]; resolve_active_and_proceed(); }, }; states.choose_card = { inactive: 'choose a card', prompt(player) { gen_spend_hero_points(); view.prompt = 'Choose a card to play this turn.'; const faction = player_faction_map[player]; if (game.selected_cards[faction].length === 0) { view.actions.undo = 0; const hand = game.hands[faction]; for (let c of hand) { if (!game.selected_cards[faction].includes(c)) { gen_action_card(c); } } } else { view.actions.undo = 1; view.actions.confirm = 1; } }, spend_hp() { resolve_spend_hp(); }, card(c, player) { const faction = player_faction_map[player]; game.selected_cards[faction] = [c]; }, undo(_, player) { const faction = player_faction_map[player]; game.selected_cards[faction] = []; }, confirm(_, player) { set_delete(game.active, player); if (game.active.length === 0) { resolve_active_and_proceed(); } }, }; states.choose_final_bid = { inactive: 'choose Final Bid', prompt() { view.prompt = 'Add a card to the Final Bid.'; const faction = get_active_faction(); for (let c of game.hands[faction]) { if (!game.selected_cards[faction].includes(c)) { gen_action_card(c); } } gen_action('done'); }, card(c) { const faction = get_active_faction(); game.selected_cards[faction].push(c); const number_selected = game.selected_cards[faction].length; const number_hand = game.hands[faction].length; if (number_selected === 3 || (number_hand < 4 && number_selected === number_hand - 1)) { resolve_active_and_proceed(); } else { next(); } }, done() { resolve_active_and_proceed(true); }, }; function setup_momentum() { const faction = get_active_faction(); if (game.faction_turn !== faction) { insert_after_active_node(resolve_effect((0, data_1.create_effect)('play_card', faction, 1), 'momentum')); return; } const node = get_nodes_for_state('player_turn')[0]; const player_needs_to_play_card = !game.card_played; const { use_ap, use_morale_bonus, resolving_event } = node.a ?? {}; if (player_needs_to_play_card || use_ap || use_morale_bonus || resolving_event) { node.a = { ...(node.a || {}), use_momentum: true, }; } else { insert_after_active_node(create_state_node('play_card', faction, { src: 'momentum', })); } } states.choose_medallion = { inactive: 'choose a medallion', prompt() { gen_spend_hero_points(); view.prompt = 'Choose a Medallion.'; for (let m of game.medallions.pool) { gen_action_medallion(m); } if (!game.medallions.pool.some((m) => m !== null)) { gen_action('skip'); } }, spend_hp() { resolve_spend_hp(); }, medallion(m) { const faction = get_active_faction(); const medallion = medallions[m]; logi(`${faction_player_map[faction]} earns ${medallion.name}`); const index = game.medallions.pool.indexOf(m); game.medallions.pool[index] = null; switch (m) { case 0: add_glory(faction, 1, true); break; case 1: gain_hero_points(faction, 7); break; case 2: setup_momentum(); break; default: game.medallions[faction].push(m); } resolve_active_and_proceed(); }, skip() { resolve_active_and_proceed(); }, }; states.confirm_turn = { inactive: 'confirm their turn', prompt() { view.prompt = 'You will not be able to undo this action.'; gen_action('confirm'); }, confirm() { resolve_active_and_proceed(true); }, }; states.confirm_fascist_turn = { inactive: 'confirm fascist turn', prompt() { view.prompt = "Done."; gen_action('confirm'); }, confirm() { resolve_active_and_proceed(true); }, }; states.draw_card = { inactive: 'draw a card', auto_resolve() { const { src, v } = get_active_node_args(); if (src !== 'fascist_test') { return false; } draw_hand_cards(get_active_faction(), v); return true; }, prompt() { gen_spend_hero_points(); const { v } = get_active_node_args(); view.prompt = v === 1 ? 'Draw a card.' : `Draw ${v} cards.`; gen_action(v === 1 ? 'draw_card' : 'draw_cards'); }, spend_hp() { resolve_spend_hp(); }, draw_card() { const { v } = get_active_node_args(); draw_hand_cards(get_active_faction(), v); resolve_active_and_proceed(true); }, draw_cards() { const { v } = get_active_node_args(); draw_hand_cards(get_active_faction(), v); resolve_active_and_proceed(true); }, }; states.draw_glory = { inactive: 'draw from the Bag of Glory', prompt() { gen_spend_hero_points(); view.prompt = 'Draw from the Bag of Glory'; gen_action('draw_glory'); }, draw_glory() { const index = random(game.bag_of_glory.length); const faction = game.bag_of_glory[index]; game.glory.push(faction); if (!game.glory_current_year) { game.glory_current_year = game.glory_current_year = { a: false, c: false, m: false, }; } game.glory_current_year[faction] = true; array_remove(game.bag_of_glory, index); log(`${get_player(get_active_faction())} draws from the Bag of Glory`); resolve_active_and_proceed(true); }, }; states.end_of_year_discard = { inactive: 'discard cards from hand and tableau', prompt() { const faction_id = get_active_faction(); const hand = game.hands[faction_id]; const hand_limit = get_hand_limit(faction_id); const needs_to_discard_from_hand = hand.length > hand_limit; if (needs_to_discard_from_hand) { for (let c of hand) gen_action_card(c); } const tableau = game.tableaus[faction_id]; const needs_to_discard_from_tableau = tableau.length > game.year; if (needs_to_discard_from_tableau) { for (let c of tableau) gen_action_card(c); } if (needs_to_discard_from_hand && needs_to_discard_from_tableau) { view.prompt = 'Discard a card from your hand or tableau'; } else { view.prompt = `Discard a card from your ${needs_to_discard_from_hand ? 'hand' : 'tableau'}`; } }, card(c) { const faction_id = get_active_faction(); if (game.hands[faction_id].includes(c)) { game.hands[faction_id] = game.hands[faction_id].filter((id) => id !== c); } else if (game.tableaus[faction_id].includes(c)) { game.tableaus[faction_id] = game.tableaus[faction_id].filter((id) => id !== c); } game.discard[faction_id].push(c); if (game.hands[faction_id].length > get_hand_limit(faction_id) || game.tableaus[faction_id].length > game.year) { next(); } else { log(`${faction_player_map[faction_id]} discards cards`); resolve_active_and_proceed(); } }, }; states.hero_points = { inactive: 'gain Hero Points', auto_resolve() { const { src, v } = get_active_node_args(); if (src !== 'fascist_test') { return false; } if (v < 0) { lose_hero_points(get_active_faction(), v); } else { gain_hero_points(get_active_faction(), v); } return true; }, prompt() { gen_spend_hero_points(); const value = get_active_node_args().v; if (value < 0) { view.prompt = value < -1 ? `Lose ${Math.abs(value)} Hero Points` : 'Lose 1 Hero Point'; gen_action('lose_hp'); return; } if (game.hero_points.pool > 0) { view.prompt = value > 1 ? `Fascist Test: Gain ${value} Hero Points.` : 'Fascist Test: Gain 1 Hero Point.'; gen_action('gain_hp'); } else { view.prompt = 'Fascist Test: No Hero Points available in pool.'; gen_action('skip'); } }, spend_hp() { resolve_spend_hp(); }, gain_hp() { const value = get_active_node_args().v; gain_hero_points(get_active_faction(), value); resolve_active_and_proceed(); }, lose_hp() { const value = get_active_node_args().v; lose_hero_points(get_active_faction(), value); resolve_active_and_proceed(); }, skip() { resolve_active_and_proceed(); }, }; states.game_over = { get inactive() { return game.victory; }, prompt() { view.prompt = game.victory; }, }; function resolve_player_with_most_hero_points(faction) { const value = get_active_node_args().v; if (value < 0) { lose_hero_points(faction, value); } else { gain_hero_points(faction, value); } resolve_active_and_proceed(); } states.select_player_with_most_hero_points = { inactive: 'choose a Player', prompt() { gen_spend_hero_points(); const { v } = get_active_node_args(); view.prompt = v < 0 ? 'Choose player to lose Hero Points.' : 'Choose player to gain Hero Points.'; const factions = get_factions_with_most_hero_poins(); for (let faction_id of factions) { gen_action(faction_player_map[faction_id]); } }, spend_hp() { resolve_spend_hp(); }, Anarchist() { resolve_player_with_most_hero_points(data_1.ANARCHISTS_ID); }, Communist() { resolve_player_with_most_hero_points(data_1.COMMUNISTS_ID); }, Moderate() { resolve_player_with_most_hero_points(data_1.MODERATES_ID); }, }; states.move_track = { inactive: 'move a Track', prompt() { gen_spend_hero_points(); const node = get_active_node(); const track = node.a.t; const value = node.a.v; const name = track === data_1.LIBERTY_OR_COLLECTIVIZATION ? 'Liberty OR Collectivization' : tracks[track].name; if (value === 1) view.prompt = `Move ${name} one step up.`; else if (value == -1) view.prompt = `Move ${name} one step down.`; else if (value > 0) view.prompt = `Move ${name} ${value} steps up.`; else view.prompt = `Move ${name} ${-value} steps down.`; if (track === data_1.GOVERNMENT && value === data_1.TOWARDS_CENTER) { view.prompt = `Move ${name} towards center.`; } else if (track === data_1.GOVERNMENT && value === data_1.AWAY_FROM_CENTER) { view.prompt = `Move ${name} away from center.`; } let can_move_track = false; if (track === data_1.LIBERTY_OR_COLLECTIVIZATION) { can_move_track = gen_move_track(data_1.LIBERTY, game.tracks[data_1.LIBERTY] + value) || can_move_track; can_move_track = gen_move_track(data_1.COLLECTIVIZATION, game.tracks[data_1.COLLECTIVIZATION] + value) || can_move_track; } else if (track === data_1.GOVERNMENT && (value === data_1.TOWARDS_CENTER || value === data_1.AWAY_FROM_CENTER)) { const direction = get_government_track_direction(value); can_move_track = gen_move_track(track, game.tracks[track] + direction) || can_move_track; } else { can_move_track = gen_move_track(track, game.tracks[track] + value) || can_move_track; } if (!can_move_track) { gen_action('skip'); } }, spend_hp() { resolve_spend_hp(); }, tr0(x) { move_track_to(0, x); resolve_active_and_proceed(); }, tr1(x) { move_track_to(1, x); resolve_active_and_proceed(); }, tr2(x) { move_track_to(2, x); resolve_active_and_proceed(); }, tr3(x) { move_track_to(3, x); resolve_active_and_proceed(); }, tr4(x) { move_track_to(4, x); resolve_active_and_proceed(); }, skip() { resolve_active_and_proceed(); }, }; function can_move_track_up(track_id) { const faction = get_active_faction(); return game.faction_turn === data_1.COMMUNISTS_ID && faction === data_1.COMMUNISTS_ID && track_id === data_1.GOVERNMENT ? false : true; } function can_move_track_down(track_id) { const faction = get_active_faction(); if (game.faction_turn === data_1.ANARCHISTS_ID && faction === data_1.ANARCHISTS_ID && (track_id === data_1.LIBERTY || track_id === data_1.COLLECTIVIZATION)) { return false; } if (game.faction_turn === data_1.COMMUNISTS_ID && faction === data_1.COMMUNISTS_ID && track_id === data_1.SOVIET_SUPPORT) { return false; } if (game.faction_turn === data_1.MODERATES_ID && faction === data_1.MODERATES_ID && (track_id === data_1.GOVERNMENT || track_id === data_1.FOREIGN_AID)) { return false; } return true; } states.move_track_up_or_down = { inactive: 'move a track', auto_resolve() { const { track_id, strength } = get_active_node_args(); const can_move_up = can_move_track_up(track_id); const can_move_down = can_move_track_down(track_id); if (can_move_up && can_move_down) { return false; } if (can_move_up) { move_track(track_id, strength); } else if (can_move_down) { move_track(track_id, -1 * strength); } return true; }, prompt() { gen_spend_hero_points(); const { track_id, strength } = get_active_node_args(); const can_move_up = can_move_track_up(track_id); const can_move_down = can_move_track_down(track_id); const track_name = get_track_name(track_id); if (can_move_up) gen_move_track(track_id, strength); if (can_move_down) gen_move_track(track_id, -strength); view.prompt = `Move ${track_name}.`; }, spend_hp() { resolve_spend_hp(); }, tr0(x) { move_track_to(0, x); resolve_active_and_proceed(); }, tr1(x) { move_track_to(1, x); resolve_active_and_proceed(); }, tr2(x) { move_track_to(2, x); resolve_active_and_proceed(); }, tr3(x) { move_track_to(3, x); resolve_active_and_proceed(); }, tr4(x) { move_track_to(4, x); resolve_active_and_proceed(); }, }; states.peek_fascist_cards = { inactive: 'peek at Fascist cards', prompt() { gen_spend_hero_points(); view.prompt = 'Choose one card to return to the top of the deck'; view.fascist_cards = game.fascist_cards; for (const c of game.fascist_cards) { gen_action_card(c); } }, spend_hp() { resolve_spend_hp(); }, card(c) { game.top_of_events_deck = c; for (const ec of game.fascist_cards) { if (ec !== c) { game.discard.f.push(ec); } } delete game.fascist_cards; resolve_active_and_proceed(); }, }; function resolve_spend_hp() { insert_before_active_node(create_state_node('spend_hero_points', get_active_faction())); log('Spends Hero Points'); next(); } function set_player_turn_prompt({ can_play_card, use_ap, use_momentum, use_morale_bonus, }) { if (can_play_card) view.prompt = "Play card for Action Points or for the Event."; else if (use_momentum) view.prompt = "Play a second card."; else if (use_ap && use_morale_bonus) view.prompt = "Use Action Points and Morale Bonus."; else if (use_morale_bonus) view.prompt = "Use Morale Bonus."; else if (use_ap) view.prompt = "Use Action Points."; else view.prompt = "Player Turn: Done."; } states.player_turn = { inactive: 'play their turn', prompt() { gen_spend_hero_points(); const faction_id = get_active_faction(); let { use_ap, use_morale_bonus, use_momentum } = get_active_node_args(); use_morale_bonus = use_morale_bonus && game.bonuses[data_1.MORALE_BONUS] === data_1.ON; const can_spend_hp = game.faction_turn === faction_id && game.hero_points[faction_id] > 0; const can_play_card = !game.card_played; if (use_momentum) { gen_action('use_momentum'); if (use_ap || use_morale_bonus || can_play_card) { view.actions['use_momentum'] = 0; } } set_player_turn_prompt({ can_play_card, can_spend_hp, use_ap, use_momentum, use_morale_bonus, }); if (can_play_card) { gen_action('play_to_tableau'); gen_action('play_for_event'); } if (use_ap) { gen_action('use_ap'); } if (use_morale_bonus && game.bonuses[data_1.MORALE_BONUS] === data_1.ON) { gen_action('use_morale_bonus'); } if (!(can_play_card || use_ap || use_morale_bonus || use_momentum)) { gen_action('end_turn'); } }, spend_hp() { resolve_spend_hp(); }, end_turn() { game.faction_turn = null; game.played_card = null; game.selected_cards[get_active_faction()] = []; resolve_active_and_proceed(true); }, play_to_tableau() { game.card_played = 1; const faction = get_active_faction(); const { strength } = play_card_to_tableau(faction); update_active_node_args({ use_morale_bonus: true, use_ap: true, strength, }); next(); }, play_for_event() { game.card_played = 1; const faction = get_active_faction(); const { effects } = play_card_for_event(faction); update_active_node_args({ resolving_event: true, }); const node = create_effects_node(effects, 'player_event'); node.c.push(create_function_node('trash_card', faction)); node.c.push(create_function_node('end_resolving_event_effects')); insert_before_active_node(node); next(); }, use_ap() { const faction = get_active_faction(); const { strength } = get_active_node_args(); update_active_node_args({ use_ap: false, }); insert_before_active_node(create_state_node('choose_area_ap', faction, { strength, })); next(); }, use_momentum() { const faction = get_active_faction(); game.card_played = 0; game.selected_cards[faction] = []; update_active_node_args({ use_morale_bonus: false, use_momentum: false, }); insert_before_active_node(create_state_node('play_card', faction, { src: 'momentum', })); next(); }, use_morale_bonus() { update_active_node_args({ use_morale_bonus: false, }); insert_before_active_node(create_state_node('activate_icon', get_active_faction())); next(); }, }; states.remove_blank_marker = { inactive: 'remove a Blank marker', prompt() { gen_spend_hero_points(); view.prompt = 'Remove a Blank marker'; for (const b of game.triggered_track_effects) { gen_action_blank_marker(b); } }, spend_hp() { resolve_spend_hp(); }, blank_marker(b) { const faction = get_active_faction(); pay_hero_points(faction, 1); const track_id = Math.floor(b / 11); const space_id = b % 11; logi(`${faction_player_map[faction]} removes a Blank marker from space ${space_id} of ${get_track_name(track_id)}`); game.triggered_track_effects = game.triggered_track_effects.filter((id) => id !== b); game.used_medallions.push(data_1.ARCHIVES_MEDALLION_ID); resolve_active_and_proceed(); }, }; states.remove_attack_from_fronts = { inactive: 'remove attacks', prompt() { gen_spend_hero_points(); const { f, v: card_id } = get_active_node_args(); view.prompt = card_id === 6 ? 'Remove an attack from a Front.' : 'Remove attacks from a Front.'; const front_data = f ?? {}; let is_front_with_attacks = false; data_1.FRONTS.forEach((id) => { if (game.fronts[id].value >= 0 || game.fronts[id].status !== null) { return; } if (card_id === 6 && front_data[id]) { return; } is_front_with_attacks = true; gen_action_front(id); }); if (!is_front_with_attacks) { view.prompt = 'No valid Front to choose. You must skip'; gen_action('skip'); } }, spend_hp() { resolve_spend_hp(); }, front(id) { const { f, v: card_id } = get_active_node_args(); const removed_value = card_id === 6 ? 1 : Math.min(3, Math.abs(game.fronts[id].value)); update_front(id, removed_value, get_active_faction()); const fronts = f ?? {}; fronts[id] = removed_value; update_active_node_args({ f: fronts }); if (card_id === 6 && Object.keys(fronts).length === 3) { resolve_active_and_proceed(); } else if (card_id === 39 || card_id === 16) { insert_after_active_node(create_state_node('attack_front', get_active_faction(), { t: data_1.ANY, v: card_id === 39 ? -2 : -1 * removed_value, n: card_id === 16 ? id : undefined, src: 'player_event' })); resolve_active_and_proceed(); } }, skip() { const { f, v: card_id } = get_active_node_args(); const values = Object.values(f ?? {}); if (card_id === 39 && values.length > 0) { insert_after_active_node(create_state_node('attack_front', get_active_faction(), { t: data_1.ANY, v: -2, src: 'player_event' })); } resolve_active_and_proceed(); }, }; states.return_card = { inactive: 'return a card to their hand', prompt() { const faction = get_active_faction(); gen_spend_hero_points(); view.prompt = 'Return a card to your hand.'; view.trash = game.trash[faction]; let possible = false; for (let c of game.trash[faction]) { if (c !== game.played_card) { gen_action_card(c); possible = true; } } if (!possible) { view.prompt = 'No card in trash to return. You must skip'; gen_action('skip'); } }, spend_hp() { resolve_spend_hp(); }, card(c) { const faction = get_active_faction(); array_remove(game.trash[faction], game.trash[faction].indexOf(c)); game.hands[faction].push(c); logi(`${faction_player_map[faction]} returns a card to their hand`); resolve_active_and_proceed(); }, skip() { resolve_active_and_proceed(); }, }; function gen_spend_hero_points_move_track(track_id, change) { for (let i = 1; i <= change; ++i) { gen_move_track_change(track_id, i); } } function pay_hero_points_to_move_track(track_id, new_value) { const cost_per_step = [3, 3, 4, 2, 2]; const change = Math.abs(game.tracks[track_id] - new_value); pay_hero_points(get_active_faction(), cost_per_step[track_id] * change); } states.spend_hero_points = { inactive: 'spend Hero points', prompt() { const hero_points = game.hero_points[get_active_faction()]; view.prompt = `Spend up to ${hero_points} Hero Points.`; const faction = get_active_faction(); if (hero_points === 0) { return; } gen_action('draw_card'); if (can_use_medallion(data_1.ARCHIVES_MEDALLION_ID, faction)) { gen_action('remove_blank_marker'); } if (can_use_medallion(data_1.VOLUNTEERS_MEDALLION_ID, faction)) { gen_action('add_to_front'); } if (hero_points < 2) { return; } for (const bonus of bonuses) { if (game.bonuses[bonus] === data_1.OFF) { gen_action_bonus(bonus); } } gen_spend_hero_points_move_track(data_1.FOREIGN_AID, Math.floor(hero_points / 2)); gen_spend_hero_points_move_track(data_1.SOVIET_SUPPORT, Math.floor(hero_points / 2)); if (hero_points < 3) { return; } gen_spend_hero_points_move_track(data_1.COLLECTIVIZATION, Math.floor(hero_points / 3)); gen_spend_hero_points_move_track(data_1.LIBERTY, Math.floor(hero_points / 3)); if (hero_points < 4) { return; } gen_spend_hero_points_move_track(data_1.GOVERNMENT, Math.floor(hero_points / 4)); }, add_to_front() { const faction = get_active_faction(); pay_hero_points(faction, 1); insert_after_active_node(create_state_node('add_to_front', faction, { t: data_1.ANY, v: 1, })); resolve_active_and_proceed(); }, bonus(b) { update_active_node_args({ turn_on_bonus: false, }); update_bonus(b, data_1.ON); pay_hero_points(get_active_faction(), 2); resolve_active_and_proceed(); }, draw_card() { const faction = get_active_faction(); pay_hero_points(faction, 1); draw_hand_cards(faction, 1); resolve_active_and_proceed(); }, remove_blank_marker() { const faction = get_active_faction(); if (game.used_medallions) { game.used_medallions.push(data_1.ARCHIVES_MEDALLION_ID); } else { game.used_medallions = [data_1.ARCHIVES_MEDALLION_ID]; } insert_after_active_node(create_state_node('remove_blank_marker', faction)); resolve_active_and_proceed(); }, tr0(x) { pay_hero_points_to_move_track(data_1.LIBERTY, x); move_track_to(0, x); resolve_active_and_proceed(); }, tr1(x) { pay_hero_points_to_move_track(data_1.COLLECTIVIZATION, x); move_track_to(1, x); resolve_active_and_proceed(); }, tr2(x) { pay_hero_points_to_move_track(data_1.GOVERNMENT, x); move_track_to(2, x); resolve_active_and_proceed(); }, tr3(x) { pay_hero_points_to_move_track(data_1.SOVIET_SUPPORT, x); move_track_to(3, x); resolve_active_and_proceed(); }, tr4(x) { pay_hero_points_to_move_track(data_1.FOREIGN_AID, x); move_track_to(4, x); resolve_active_and_proceed(); }, }; states.swap_card_tableau_hand = { inactive: 'swap cards', prompt() { gen_spend_hero_points(); view.prompt = 'Swap a card in your tableau with a card in your hand.'; const faction = get_active_faction(); gen_action('skip'); if (game.tableaus[faction].length === 0) { view.prompt = 'No card in your tableau to swap.'; return; } if (game.hands[faction].length === 0) { view.prompt = 'No card in your hand to swap.'; return; } if (!game.selected_cards[faction].some((card_id) => game.hands[faction].includes(card_id))) { for (const c of game.hands[faction]) { gen_action_card(c); } } if (!game.selected_cards[faction].some((card_id) => game.tableaus[faction].includes(card_id))) { for (const c of game.tableaus[faction]) { gen_action_card(c); } } }, spend_hp() { resolve_spend_hp(); }, card(c) { const faction = get_active_faction(); const selected_cards = game.selected_cards[faction]; selected_cards.push(c); if (selected_cards.length === 2) { const hand_card_index = selected_cards.findIndex((card_id) => game.hands[faction].includes(card_id)); const tableau_card_index = hand_card_index === 0 ? 1 : 0; const hand = game.hands[faction]; const tableau = game.tableaus[faction]; array_remove(hand, hand.indexOf(selected_cards[hand_card_index])); array_remove(tableau, tableau.indexOf(selected_cards[tableau_card_index])); hand.push(selected_cards[tableau_card_index]); tableau.push(selected_cards[hand_card_index]); game.selected_cards[faction] = []; resolve_active_and_proceed(); } }, skip() { const faction = get_active_faction(); game.selected_cards[faction] = []; resolve_active_and_proceed(); }, }; function resolve_take_hero_points(faction) { const { v } = get_active_node_args(); const amount = Math.min(v, game.hero_points[faction]); lose_hero_points(faction, amount); gain_hero_points(get_active_faction(), amount); resolve_active_and_proceed(); } states.take_hero_points = { inactive: 'take Hero Points', prompt() { gen_spend_hero_points(); const { v } = get_active_node_args(); view.prompt = v === 1 ? 'Take a Hero Point from any player.' : `Take ${v} Hero Points from any player.`; const active_faction = get_active_faction(); let target_exists = false; for (const faction of role_ids) { if (faction !== active_faction && game.hero_points[faction] > 0) { gen_action(faction_player_map[faction]); target_exists = true; } } if (!target_exists) { view.prompt = 'Not possible to take Hero Points from another player. You must skip'; gen_action('skip'); } }, spend_hp() { resolve_spend_hp(); }, Anarchist() { resolve_take_hero_points(data_1.ANARCHISTS_ID); }, Communist() { resolve_take_hero_points(data_1.COMMUNISTS_ID); }, Moderate() { resolve_take_hero_points(data_1.MODERATES_ID); }, skip() { resolve_active_and_proceed(); }, }; function trash_card() { const faction = get_active_faction(); const index = game.selected_cards[faction].length - 1; const card_id = game.selected_cards[faction][index]; array_remove(game.hands[faction], game.hands[faction].indexOf(card_id)); array_remove(game.selected_cards[faction], index); game.trash[faction].push(card_id); resolve_active_and_proceed(); } states.use_organization_medallion = { inactive: 'use Organization Medallion', prompt() { gen_spend_hero_points(); view.prompt = 'Use Organization Medallion?'; gen_action('yes'); gen_action('no'); }, spend_hp() { resolve_spend_hp(); }, yes() { const faction = get_active_faction(); pay_hero_points(faction, 1); game.used_medallions.push(data_1.ORGANIZATION_MEDALLION_ID); let { t, v } = get_active_node_args(); if (v > game.tracks[t]) { v++; } else { v--; } move_track(t, v - game.tracks[t]); resolve_active_and_proceed(); }, no() { const { t, v } = get_active_node_args(); move_track(t, v); resolve_active_and_proceed(); }, }; states.use_strategy_medallion = { inactive: 'use Strategy Medallion', prompt() { gen_spend_hero_points(); view.prompt = 'Use Strategy Medallion?'; gen_action('yes'); gen_action('no'); }, spend_hp() { resolve_spend_hp(); }, yes() { game.used_medallions.push(data_1.STRATEGY_MEDALLION_ID); const { f } = get_active_node_args(); const faction = get_active_faction(); update_front(f, 1, faction); resolve_active_and_proceed(); }, no() { resolve_active_and_proceed(); }, }; function card1_event2() { const value = game.tracks[data_1.FOREIGN_AID] >= 6 ? 3 : 2; insert_after_active_node(resolve_effect((0, data_1.create_effect)('front', data_1.NORTHERN, value), 'player_event')); resolve_active_and_proceed(); } function card3_event2() { const value = game.tracks[data_1.FOREIGN_AID] >= 8 ? 2 : 1; insert_after_active_node(resolve_effect((0, data_1.create_effect)('track', data_1.GOVERNMENT, value), 'player_event')); resolve_active_and_proceed(); } function card10_event2() { if (game.tracks[data_1.FOREIGN_AID] >= 6) { insert_after_active_node(resolve_effect((0, data_1.create_effect)('draw_card', data_1.SELF, 2), 'player_event')); } resolve_active_and_proceed(); } function card16_event2() { const value = game.tracks[data_1.GOVERNMENT] >= 6 ? 4 : 3; insert_after_active_node(resolve_effect((0, data_1.create_effect)('track', data_1.FOREIGN_AID, value), 'player_event')); resolve_active_and_proceed(); } function card17_event3() { const value = game.tracks[data_1.GOVERNMENT] >= 6 ? -4 : -3; insert_after_active_node(resolve_effect((0, data_1.create_effect)('track', data_1.COLLECTIVIZATION, value), 'player_event')); resolve_active_and_proceed(); } function card20_event3() { const value = game.tracks[data_1.SOVIET_SUPPORT] >= 6 ? 2 : 1; insert_after_active_node(create_seq_node([ resolve_effect((0, data_1.create_effect)('front', data_1.MADRID, value), 'player_event'), resolve_effect((0, data_1.create_effect)('front', data_1.SOUTHERN, value), 'player_event'), ])); resolve_active_and_proceed(); } function card22_event3() { const value = game.tracks[data_1.SOVIET_SUPPORT] >= 8 ? -3 : -3; insert_after_active_node(resolve_effect((0, data_1.create_effect)('track', data_1.GOVERNMENT, value), 'player_event')); resolve_active_and_proceed(); } function card23_event1() { const value = game.tracks[data_1.SOVIET_SUPPORT] >= 6 ? 4 : 3; insert_after_active_node(resolve_effect((0, data_1.create_effect)('front', data_1.ANY, value), 'player_event')); resolve_active_and_proceed(); } function card26_event1() { game.active_abilities.push(data_1.COMMUNIST_EXTRA_HERO_POINT); resolve_active_and_proceed(); } function card29_event2() { const value = game.tracks[data_1.GOVERNMENT] <= 5 ? -3 : -2; insert_after_active_node(resolve_effect((0, data_1.create_effect)('track', data_1.LIBERTY, value), 'player_event')); resolve_active_and_proceed(); } function card35_event2() { const value = game.tracks[data_1.GOVERNMENT] <= 5 ? 2 : 1; insert_after_active_node(resolve_effect((0, data_1.create_effect)('track', data_1.SOVIET_SUPPORT, value), 'player_event')); resolve_active_and_proceed(); } function card42_event3() { game.active_abilities.push(data_1.ANARCHIST_EXTRA_HERO_POINT); resolve_active_and_proceed(); } function card45_event2() { if (game.tracks[data_1.LIBERTY] >= 6) { insert_after_active_node(resolve_effect((0, data_1.create_effect)('track', data_1.COLLECTIVIZATION, 1), 'player_event')); } resolve_active_and_proceed(); } function card46_event3() { game.fascist_cards = []; for (let i = 0; i < 3; ++i) { game.fascist_cards.push(draw_fascist_card()); } resolve_active_and_proceed(); } function card50_event2() { const value = game.tracks[data_1.COLLECTIVIZATION] >= 8 ? 3 : 2; insert_after_active_node(resolve_effect((0, data_1.create_effect)('front', data_1.ARAGON, value), 'player_event')); resolve_active_and_proceed(); } function card53_event2() { const value = game.tracks[data_1.LIBERTY] >= 8 ? 3 : 2; insert_after_active_node(resolve_effect((0, data_1.create_effect)('front', data_1.ANY, value), 'player_event')); resolve_active_and_proceed(); } function card54_event1() { const value = game.tracks[data_1.COLLECTIVIZATION] >= 8 ? 3 : 2; insert_after_active_node(resolve_effect((0, data_1.create_effect)('track', data_1.LIBERTY, value), 'player_event')); resolve_active_and_proceed(); } function setup_return_card_from_trash() { resolve_active_and_proceed(); } function add_glory(faction, amount, indent = false) { let tokens_log = ''; for (let i = 0; i < amount; ++i) { game.bag_of_glory.push(get_active_faction()); tokens_log += ``; } let text = `${faction_player_map[faction]} adds ${tokens_log} to the Bag of Glory`; if (indent) { logi(text); } else { log_h3(text); } } function check_initiative() { let initiative; if (game.tracks[data_1.LIBERTY] >= 6 && game.tracks[data_1.COLLECTIVIZATION] >= 6) { initiative = data_1.ANARCHISTS_ID; } else if (game.tracks[data_1.GOVERNMENT] <= 5) { initiative = data_1.COMMUNISTS_ID; } else { initiative = data_1.MODERATES_ID; } if (game.initiative === initiative) { return; } game.initiative = initiative; logi(`${faction_player_map[initiative]} claims the Initiative`); } function war_is_won() { let won_fronts = 0; for (const f of data_1.FRONTS) { if (game.fronts[f].value >= 1) { won_fronts++; } } return won_fronts >= 3; } function determine_winner() { const glory = { [data_1.ANARCHISTS_ID]: 0, [data_1.COMMUNISTS_ID]: 0, [data_1.MODERATES_ID]: 0, }; for (const g of game.glory) { glory[g]++; } let highest_glory = 0; let winners = []; for (let f of role_ids) { if (glory[f] === highest_glory) { winners.push(f); } else if (glory[f] > highest_glory) { highest_glory = glory[f]; winners = [f]; } } if (winners.length === 1) { win_game(faction_player_map[winners[0]], highest_glory); } else { insert_after_active_node(create_state_node('break_tie_winner', game.initiative, { winners, glory: highest_glory, })); } resolve_active_and_proceed(); } function end_of_turn() { Object.keys(game.fronts).forEach((front_id) => { game.fronts[front_id].contributions = []; }); game.active_abilities = []; game.used_medallions = []; game.first_player = null; if (game.turn === 4) { end_of_year(); } else { game.turn++; start_turn(); } } function end_of_year() { if (game.year === 3) { log_h1('End of the game'); const is_won = war_is_won(); if (is_won) { log('The war is won!'); } else { game_over('None', 'The war is lost. All Players lose the game!'); resolve_active_and_proceed(); return; } } else { log_h1('End of year'); } const glory_to_draw = [0, 1, 2, 5]; game.glory_current_year = { a: false, c: false, m: false, }; const player_order = get_player_order(); const engine = []; for (let i = 0; i < glory_to_draw[game.year]; ++i) { engine.push(create_state_node('draw_glory', player_order[i % 3])); } engine.push(create_function_node('end_of_year_cleanup')); game.engine = engine; next(true); } function end_of_year_cleanup() { if (game.year === 3) { determine_winner(); return; } const players_to_gain_hero_points = role_ids.filter((f) => !game.glory_current_year?.[f]); gain_hero_points_in_player_order(players_to_gain_hero_points, game.year); game.engine = get_player_order().map((f) => create_function_node('check_end_of_year_discard', { f })); game.engine.push(create_function_node('checkpoint')); game.engine.push(create_function_node('start_year')); game.top_of_events_deck = null; game.glory_current_year = null; next(); } function end_resolving_event_effects() { const node = get_nodes_for_state('player_turn')[0]; node.a = { ...(node.a || {}), resolving_event: false, }; resolve_active_and_proceed(); } function gain_hero_points_in_player_order(factions, value) { for (const f of get_player_order()) { if (factions.includes(f)) { gain_hero_points(f, value); } } } function gain_hero_points(faction_id, value, skip_abilities = false) { if (game.hero_points.pool === 0) { return; } if (!skip_abilities && faction_id === data_1.ANARCHISTS_ID && (game.active_abilities || []).includes(data_1.ANARCHIST_EXTRA_HERO_POINT)) { value++; game.active_abilities = (game.active_abilities || []).filter((ability) => ability !== data_1.ANARCHIST_EXTRA_HERO_POINT); } if (!skip_abilities && faction_id === data_1.COMMUNISTS_ID && (game.active_abilities || []).includes(data_1.COMMUNIST_EXTRA_HERO_POINT)) { value++; game.active_abilities = (game.active_abilities || []).filter((ability) => ability !== data_1.COMMUNIST_EXTRA_HERO_POINT); } const gain = Math.min(game.hero_points.pool, value); game.hero_points.pool -= gain; game.hero_points[faction_id] += gain; logi(`${get_player(faction_id)} +${gain} ${gain === 1 ? 'Hero Point' : 'Hero Points'}`); } function game_over(result, victory) { insert_after_active_node(create_state_node('game_over', 'None')); game.result = result; game.victory = victory; game.undo = []; log_br(); log(game.victory); } function get_hand_limit(faction) { let hand_limit = game.year; if (game.medallions[faction].includes(data_1.INTELLIGENCE_MEDALLION_ID)) { hand_limit++; } return hand_limit; } function play_card_for_event(faction) { const index = game.selected_cards[faction].length - 1; const card_id = game.selected_cards[faction][index]; const card = cards[card_id]; game.played_card = card_id; log('Played for Event:'); return card; } function play_card_to_tableau(faction) { const index = game.selected_cards[faction].length - 1; const card_id = game.selected_cards[faction][index]; const card = cards[card_id]; game.played_card = card_id; array_remove(game.hands[faction], game.hands[faction].indexOf(card_id)); log('Played to Tableau:'); game.tableaus[faction].push(card_id); return card; } function resolve_fascist_test() { game.fascist = 2; log_h2('Fascist Test', 'fascist'); const test = get_current_event().test; const status = game.fronts[test.front].status; const test_passed = status === data_1.VICTORY || (status !== data_1.DEFEAT && game.fronts[test.front].value >= test.value); const hero_point_actions = []; if (test_passed) { log('The Test is passed'); for (const faction of get_player_order()) { let hero_points_gain = game.fronts[test.front].contributions.includes(faction) ? 2 : 0; if (can_use_medallion(data_1.PROPAGANDA_MEDALLION_ID, faction)) { hero_points_gain += 2; } if (hero_points_gain > 0) { const node = resolve_effect((0, data_1.create_effect)('hero_points', faction, hero_points_gain), 'fascist_test'); hero_point_actions.push(node); } } if (hero_point_actions.length > 0) { insert_after_active_node(create_seq_node(hero_point_actions)); } } else { log('The Test is failed'); } const effect = test_passed ? test.pass : test.fail; const node = resolve_effect(effect, 'fascist_test'); if (node !== null) { insert_after_active_node(node); } resolve_active_and_proceed(); } function resolve_final_bid() { let highest_bid = 0; let winners = []; for (const f of get_player_order()) { let player_bid = 0; for (const c of game.selected_cards[f]) { player_bid += cards[c].strength; } log(`${faction_player_map[f]} bids ${player_bid}`); if (player_bid === highest_bid) { winners.push(f); } else if (player_bid > highest_bid) { highest_bid = player_bid; winners = [f]; } game.hands[f] = game.hands[f].filter((c) => !game.selected_cards[f].includes(c)); game.discard[f].concat(game.selected_cards[f]); game.selected_cards[f] = []; } if (winners.length === 1) { win_final_bid(winners[0]); } else { insert_after_active_node(create_state_node('break_tie_final_bid', game.initiative, { winners })); } resolve_active_and_proceed(); } function get_fronts_to_add_to(target, not = []) { if (target === data_1.CLOSEST_TO_DEFEAT || target === data_1.CLOSEST_TO_VICTORY) { return get_fronts_closest_to(target); } else if (target === data_1.ANY) { return data_1.FRONTS.filter((id) => game.fronts[id].status === null && !not.includes(id)); } else if (game.fronts[target].status === data_1.DEFEAT) { return get_fronts_closest_to(data_1.CLOSEST_TO_DEFEAT); } else if (game.fronts[target].status === data_1.VICTORY) { return get_fronts_to_add_to(data_1.ANY); } else { return [target]; } } function get_max_value_for_track(track_id) { switch (track_id) { case data_1.LIBERTY: const max_lib = game.tracks[data_1.COLLECTIVIZATION] >= 8 ? 10 : 7; return Math.max(max_lib, game.tracks[data_1.LIBERTY]); case data_1.GOVERNMENT: const max_gov = game.tracks[data_1.FOREIGN_AID] >= 8 ? 10 : 7; return Math.max(max_gov, game.tracks[data_1.GOVERNMENT]); case data_1.COLLECTIVIZATION: case data_1.SOVIET_SUPPORT: case data_1.FOREIGN_AID: default: return 10; } } function get_min_value_for_track(track_id) { switch (track_id) { case data_1.GOVERNMENT: const min_gov = game.tracks[data_1.SOVIET_SUPPORT] >= 8 ? 1 : 4; return Math.min(min_gov, game.tracks[data_1.GOVERNMENT]); case data_1.LIBERTY: case data_1.COLLECTIVIZATION: case data_1.SOVIET_SUPPORT: case data_1.FOREIGN_AID: default: return 0; } } function get_government_track_direction(direction) { const value = game.tracks[data_1.GOVERNMENT]; if ((direction === data_1.TOWARDS_CENTER && value >= 6) || (direction === data_1.AWAY_FROM_CENTER && value <= 5)) { return -1; } else { return 1; } } const track_action_name = ['tr0', 'tr1', 'tr2', 'tr3', 'tr4']; function gen_move_track(track_id, new_value) { new_value = Math.max(new_value, get_min_value_for_track(track_id)); new_value = Math.min(new_value, get_max_value_for_track(track_id)); if (new_value === game.tracks[track_id]) { return false; } gen_action(track_action_name[track_id], new_value); return true; } function gen_move_track_change(track_id, change) { const current_value = game.tracks[track_id]; let can_move_track = false; if (can_move_track_up(track_id)) { can_move_track = gen_move_track(track_id, current_value + change) || can_move_track; } if (can_move_track_down(track_id)) { can_move_track = gen_move_track(track_id, current_value - change) || can_move_track; } return can_move_track; } function move_track(track_id, change) { const current_value = game.tracks[track_id]; let new_value = current_value + change; new_value = Math.max(new_value, get_min_value_for_track(track_id)); new_value = Math.min(new_value, get_max_value_for_track(track_id)); move_track_to(track_id, new_value); } function move_track_to(track_id, new_value) { const current_value = game.tracks[track_id]; let change = new_value - current_value; game.tracks[track_id] = new_value; logi(`${get_track_name(track_id)} to ${new_value}`); check_initiative(); const triggered_spaces = change > 0 ? make_list(current_value + 1, new_value).reverse() : make_list(new_value, current_value - 1); triggered_spaces.forEach((space_id) => { const trigger = tracks[track_id].triggers[space_id]; if (trigger !== null && !game.triggered_track_effects.includes(get_blank_marker_id(track_id, space_id))) { if (space_id !== 0) { game.triggered_track_effects.push(get_blank_marker_id(track_id, space_id)); } const node = resolve_effect(trigger, tracks[track_id].action); if (node !== null) { insert_after_active_node(node); } } }); } function pay_hero_points(faction, amount) { game.hero_points[faction] -= amount; game.hero_points.pool += amount; } function can_use_medallion(medallion_id, faction) { faction = faction === undefined ? get_active_faction() : faction; const can_use = game.medallions[faction].includes(medallion_id) && !game.used_medallions.includes(medallion_id); if (medallion_id === data_1.ORGANIZATION_MEDALLION_ID) { return can_use && game.hero_points[faction] > 0; } else { return can_use; } } function insert_use_organization_medallion_node(track_id, value) { const faction = get_active_faction(); insert_after_active_node(create_state_node('use_organization_medallion', faction, { t: track_id, v: value, })); } function update_bonus(bonus_id, status) { if (game.bonuses[bonus_id] === status) { return; } game.bonuses[bonus_id] = status; logi(`${bonus_names[bonus_id]} ${status === data_1.ON ? 'on' : 'off'}`); } function update_front(front_id, change, faction_id = null) { const player_token_on_front = faction_id !== null && game.fronts[front_id].contributions.includes(faction_id); if (game.bonuses[data_1.TEAMWORK_BONUS] === data_1.ON && change > 0 && faction_id !== null && !player_token_on_front && game.fronts[front_id].contributions.length > 0) { change += 1; } const value_before = game.fronts[front_id].value; game.fronts[front_id].value += change; logi(`${front_names[front_id]}: ${change > 0 ? '+' : ''}${change}`); if (faction_id !== null && value_before <= 0 && game.fronts[front_id].value > 0) { gain_hero_points(faction_id, 1); } if (faction_id !== null && !game.fronts[front_id].contributions.includes(faction_id)) { game.fronts[front_id].contributions.push(faction_id); } if (change > 0 && faction_id !== undefined && game.fronts[front_id].value < 10 && can_use_medallion(data_1.STRATEGY_MEDALLION_ID)) { insert_after_active_node(create_state_node('use_strategy_medallion', get_active_faction(), { f: front_id, })); } if (game.fronts[front_id].value >= 10) { victory_on_a_front(front_id); } else if (game.fronts[front_id].value <= -10) { defeat_on_a_front(front_id); } } function defeat_on_a_front(front_id) { game.fronts[front_id].status = data_1.DEFEAT; log('Defeat on ' + get_front_name(front_id)); if (front_id === 'm' || get_defeated_front_count() == 2) { game_over('None', 'All players lose the game!'); return; } insert_after_active_node(create_effects_node([ (0, data_1.create_effect)('bonus', data_1.MORALE_BONUS, data_1.OFF), (0, data_1.create_effect)('track', data_1.COLLECTIVIZATION, -1), (0, data_1.create_effect)('track', data_1.SOVIET_SUPPORT, -1), (0, data_1.create_effect)('track', data_1.FOREIGN_AID, -1), ])); } function victory_on_a_front(front_id) { game.fronts[front_id].status = data_1.VICTORY; log('Victory on ' + get_front_name(front_id)); gain_hero_points_in_player_order(game.fronts[front_id].contributions, 3); } function create_effects_node(effects, source) { const nodes = effects.reduce((accrued, current) => { const node = resolve_effect(current, source); if (node !== null) { accrued.push(node); } return accrued; }, []); return create_seq_node(nodes); } function get_faction_to_resolve_effect(effect) { if (!effect.faction) { return get_active_faction(); } if (effect.faction === data_1.INITIATIVE_PLAYER) { return game.initiative; } return effect.faction; } const effect_type_state_map = { add_card_to_tableau: 'add_card_to_tableau', attack: 'attack_front', bonus: 'change_bonus', front: 'add_to_front', medallion: 'choose_medallion', remove_blank_marker: 'remove_blank_marker', return_card: 'return_card', swap_card_tableau_hand: 'swap_card_tableau_hand', take_hero_points: 'take_hero_points', track: 'move_track', }; function resolve_effect(effect, source) { const args = { t: effect.target, v: effect.value, src: source, }; const faction = get_faction_to_resolve_effect(effect); if (effect.type === 'function') { return create_function_node(effect.target); } if (effect.type === 'state') { return create_state_node(effect.target, faction, { v: effect.value, src: source, }); } let state = effect_type_state_map[effect.type]; if (state !== undefined) { return create_state_node(state, faction, args); } const strategies = [ { condition: effect.type === 'hero_points' && effect.target === data_1.PLAYER_WITH_MOST_HERO_POINTS, resolve: () => { return create_state_node('select_player_with_most_hero_points', faction, args); }, }, { condition: effect.type === 'hero_points' && effect.target === data_1.ALL_PLAYERS, resolve: () => { return create_seq_node(get_player_order().map((faction) => create_state_node('hero_points', faction, args))); }, }, { condition: effect.type === 'hero_points' && effect.target === data_1.SELF, resolve: () => { return create_state_node('hero_points', faction, args); }, }, { condition: effect.type === 'hero_points' && role_ids.includes(effect.target), resolve: () => { return create_state_node('hero_points', effect.target, args); }, }, { condition: effect.type === 'hero_points' && effect.target === data_1.INITIATIVE_PLAYER, resolve: () => { return create_state_node('hero_points', game.initiative, args); }, }, { condition: effect.type === 'draw_card' && effect.target === data_1.SELF, resolve: () => { return create_state_node('draw_card', faction, args); }, }, { condition: effect.type === 'draw_card' && effect.target === data_1.INITIATIVE_PLAYER, resolve: () => { return create_state_node('draw_card', game.initiative, args); }, }, { condition: effect.type === 'draw_card' && role_ids.includes(effect.target), resolve: () => { return create_state_node('draw_card', effect.target, args); }, }, { condition: effect.type === 'draw_card' && effect.target === data_1.ALL_PLAYERS, resolve: () => { return create_seq_node(get_player_order(get_active_faction()).map((faction) => create_state_node('draw_card', faction, args))); }, }, { condition: effect.type === 'draw_card' && effect.target === data_1.OTHER_PLAYERS, resolve: () => { const state_nodes = get_player_order(get_active_faction()).map((faction) => create_state_node('draw_card', faction, args)); array_remove(state_nodes, 0); return create_seq_node(state_nodes); }, }, { condition: effect.type === 'play_card', resolve: () => { return create_seq_node([ create_state_node('play_card', faction, { src: source }), create_state_node('player_turn', faction, { src: source }), ]); }, }, ]; const strategy = strategies.find((strategy) => strategy.condition); if (strategy) { return strategy.resolve(); } else { console.log('----UNRESOLVED EFFECT----', effect); throw new Error('Unresolved effect'); } } function win_final_bid(faction_id) { log_br(); log(`${faction_player_map[faction_id]} wins the Final Bid`); game.glory.push(faction_id); } function win_game(player, glory) { game_over(player, `${player} wins the game with a total of ${glory} Glory!`); } function draw_card(deck) { clear_undo(); let i = random(deck.length); let c = deck[i]; set_delete(deck, c); return c; } function draw_fascist_card() { if (game.top_of_events_deck !== null) { const card_id = game.top_of_events_deck; game.top_of_events_deck = null; return card_id; } return draw_card(list_deck(data_1.FASCIST_ID)); } function lose_hero_points(faction, value) { const points_lost = Math.min(game.hero_points[faction], Math.abs(value)); game.hero_points.pool += points_lost; game.hero_points[faction] -= points_lost; if (points_lost === 0) { return; } if (points_lost === 1) { log(`${get_player(faction)}: -1 Hero Point`); } else { log(`${get_player(faction)}: -${points_lost} Hero Points`); } } function get_fronts_closest_to(target) { const values = Object.values(game.fronts).reduce((accrued, current) => { if (current.status === null) { accrued.push(current.value); } return accrued; }, []); if (values.length === 0) { return []; } const targetValue = target === 'd' ? Math.min(...values) : Math.max(...values); return Object.keys(game.fronts).filter((frontId) => game.fronts[frontId].value === targetValue); } function log_br() { if (game.log.length > 0 && game.log[game.log.length - 1] !== '') game.log.push(''); } function log(msg) { game.log.push(msg); } function logi(msg) { log('>' + msg); } function log_h1(msg) { log_br(); log('#t ' + msg); log_br(); } function log_h2(msg, player) { log_br(); log(`#${player[0].toLowerCase()} ${msg}`); log_br(); } function log_h3(msg) { log_br(); log('.h3 ' + msg); } function get_active_faction() { return player_faction_map[game.active]; } function get_blank_marker_id(track_id, space_id) { return track_id * 11 + space_id; } function get_front_name(id) { return front_names[id]; } function get_current_event() { return cards[game.current_events[game.current_events.length - 1]]; } function get_defeated_front_count() { let count = 0; for (const front_id of data_1.FRONTS) { if (game.fronts[front_id].status === data_1.DEFEAT) { count++; } } return count; } function get_icon_count_in_tableau(icon, faction = get_active_faction()) { let count = 0; for (const c of game.tableaus[faction]) { const card = cards[c]; for (const i of card.icons) { if (i === icon) { ++count; } } } return count; } function get_player(faction_id) { return faction_player_map[faction_id]; } function get_player_order(first_player = game.initiative) { const order = []; let faction = first_player; for (let i = 0; i < 3; ++i) { order.push(faction); faction = game.year === 2 ? get_previous_faction(faction) : get_next_faction(faction); } return order; } function get_next_faction_in_player_order(faction_id) { return get_player_order(faction_id)[1]; } function get_previous_faction(faction_id) { const index = game.player_order.indexOf(faction_player_map[faction_id]); if (index === 0) { return player_faction_map[game.player_order[2]]; } return player_faction_map[game.player_order[index - 1]]; } function get_next_faction(faction_id) { const index = game.player_order.indexOf(faction_player_map[faction_id]); if (index === 2) { return player_faction_map[game.player_order[0]]; } return player_faction_map[game.player_order[index + 1]]; } function get_player_order_in_game(first_player = game.initiative) { const order = []; let faction = first_player; for (let i = 0; i < 3; ++i) { order.push(faction); faction = get_next_faction(faction); } return order; } function join_oxford_comma(list, conjunction) { let n = list.length; if (n == 0) return "nothing"; if (n == 1) return list[0]; if (n == 2) return list[0] + " " + conjunction + " " + list[1]; let result = list[0]; for (let i = 1; i < n; ++i) { result += ", "; if (i == n - 1) result += conjunction + " "; result += list[i]; } return result; } const icon_names = { add_to_front: "+1 to Front", collectivization: "Increase Collectivization", foreign_aid: "Increase Foreign Aid", liberty: "Increase Liberty", soviet_support: "Increase Soviet Support", government: "Increase Government", d_collectivization: "Decrease Collectivization", d_foreign_aid: "Decrease Foreign Aid", d_government: "Decrease Government", d_liberty: "Decrease Liberty", d_soviet_support: "Decrease Soviet Support", government_to_center: "Move Government to Center", teamwork_on: "Turn on Teamwork Bonus", draw_card: "Draw a Card", }; function get_icon_name(icon) { return icon_names[icon]; } function get_source_name(source) { switch (source) { case 'player_event': return cards[game.played_card].title; case 'fascist_event': return cards[game.current_events[game.current_events.length - 1]].title; case 'fascist_test': return 'Fascist Test'; case 'tr0': return tracks[0].name + ' Trigger'; case 'tr1': return tracks[1].name + ' Trigger'; case 'tr2': return tracks[2].name + ' Trigger'; case 'tr3': return tracks[3].name + ' Trigger'; case 'tr4': return tracks[4].name + ' Trigger'; case 'track_icon': return 'Track Trigger'; case 'momentum': return 'Momentum'; } return source; } function get_factions_with_most_hero_poins() { let most_hero_points = null; let faction_ids = []; Object.entries(game.hero_points).forEach(([id, value]) => { if (id === 'pool') { return; } if (most_hero_points === null || value > most_hero_points) { most_hero_points = value; faction_ids = [id]; } else if (most_hero_points === value) { faction_ids.push(id); } }); return faction_ids; } function get_track_name(track_id) { return tracks[track_id].name; } function make_list(first, last) { let list = []; for (let i = first; i <= last; i++) list.push(i); return list; } function list_deck(id) { const deck = []; const card_list = id === data_1.FASCIST_ID ? fascist_decks[game.year] : faction_cards[id]; card_list.forEach((card) => { if (id === data_1.FASCIST_ID) { if (game.current_events.includes(card)) return; if (game.discard[id].includes(card)) return; } else if (game.hands[id].includes(card) || game.discard[id].includes(card) || game.tableaus[id].includes(card) || game.trash[id].includes(card)) { return; } deck.push(card); }); return deck; } function draw_medallions() { const medallion_ids = make_list(0, 8); for (let m = 0; m < 5; ++m) { let i = random(medallion_ids.length); let r = medallion_ids[i]; set_delete(medallion_ids, r); game.medallions.pool.push(r); } } function clear_undo() { if (game.undo) { game.undo.length = 0; } } function push_undo() { if (game.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); } return game.undo; } function pop_undo() { if (game.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) { return (game.seed = (game.seed * 200105) % 34359738337) % range; } 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; } } 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 set_delete(set, item) { let a = 0; let b = set.length - 1; while (a <= b) { const m = (a + b) >> 1; const x = set[m]; if (item < x) b = m - 1; else if (item > x) a = m + 1; else return array_remove(set, m); } return set; }