From 9d0630a33e0358f943e68008823f355bceb71be1 Mon Sep 17 00:00:00 2001 From: Frans Bongers Date: Fri, 27 Dec 2024 19:45:21 +0100 Subject: setup for event effects --- rules.ts | 480 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 423 insertions(+), 57 deletions(-) (limited to 'rules.ts') diff --git a/rules.ts b/rules.ts index 6831f5e..98a6806 100644 --- a/rules.ts +++ b/rules.ts @@ -62,6 +62,14 @@ import data, { STRATEGY_MEDALLION_ID, PROPAGANDA_MEDALLION_ID, VOLUNTEERS_MEDALLION_ID, + ALL_PLAYERS, + OTHER_PLAYERS, + MADRID, + SOUTHERN, + COMMUNIST_EXTRA_HERO_POINT, + LIBERTY_OR_COLLECTIVIZATION, + ANARCHIST_EXTRA_HERO_POINT, + ARAGON, // StaticData, // PLAYER_WITH_MOST_HERO_POINTS, } from './data'; @@ -79,12 +87,12 @@ var view = {} as View; // = null // export const COMMUNIST = 'Communist' as Player; // export const MODERATE = 'Moderate' as Player; -const role_ids = [ANARCHISTS_ID, COMMUNISTS_ID, MODERATES_ID]; +const role_ids: FactionId[] = [ANARCHISTS_ID, COMMUNISTS_ID, MODERATES_ID]; const faction_player_map: Record = { - [ANARCHISTS_ID]: ANARCHIST, - [COMMUNISTS_ID]: COMMUNIST, - [MODERATES_ID]: MODERATE, + a: ANARCHIST, + c: COMMUNIST, + m: MODERATE, }; const player_faction_map: Record = { @@ -184,7 +192,7 @@ export function action( if (action !== 'undo') { state.undo = push_undo(); } - + game = state; let S = states[game.state]; if (action in S) S[action](arg, player); @@ -290,6 +298,23 @@ const engine_functions: Record = { start_year, resolve_fascist_test, resolve_final_bid, + // Unique card effects + 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, + card50_event2, + card53_event2, + card54_event1, }; function get_active( @@ -456,21 +481,22 @@ export function setup(seed: number, _scenario: string, _options: unknown) { seed: seed, state: null, active: ANARCHIST, + active_abilities: [], bag_of_glory: [ANARCHISTS_ID, COMMUNISTS_ID, MODERATES_ID], blank_markers: [[], [], [], [], []], bonuses: [ON, ON], current_events: [], discard: { - [ANARCHISTS_ID]: [], - [COMMUNISTS_ID]: [], - [MODERATES_ID]: [], + a: [], + c: [], + m: [], f: [], }, engine: [], final_bid: { - [ANARCHISTS_ID]: [], - [COMMUNISTS_ID]: [], - [MODERATES_ID]: [], + a: [], + c: [], + m: [], }, fronts: { a: { @@ -496,38 +522,38 @@ export function setup(seed: number, _scenario: string, _options: unknown) { }, glory: [], hands: { - [ANARCHISTS_ID]: [], - [COMMUNISTS_ID]: [], - [MODERATES_ID]: [], + a: [], + c: [], + m: [], }, hero_points: { - [ANARCHISTS_ID]: 2, - [COMMUNISTS_ID]: 2, - [MODERATES_ID]: 0, + a: 2, + c: 2, + m: 0, pool: 14, }, chosen_cards: { - [ANARCHISTS_ID]: null, - [COMMUNISTS_ID]: null, - [MODERATES_ID]: null, + a: null, + c: null, + m: null, }, initiative: MODERATES_ID, medallions: { - [ANARCHISTS_ID]: [], - [COMMUNISTS_ID]: [], - [MODERATES_ID]: [], + a: [], + c: [], + m: [], pool: [], }, tableaus: { - [ANARCHISTS_ID]: [], - [COMMUNISTS_ID]: [], - [MODERATES_ID]: [], + a: [], + c: [], + m: [], }, tracks: [5, 5, 6, 3, 3], trash: { - [ANARCHISTS_ID]: [], - [COMMUNISTS_ID]: [], - [MODERATES_ID]: [], + a: [], + c: [], + m: [], }, triggered_track_effects: [], log: [], @@ -594,7 +620,7 @@ function start_turn() { log(card.title); game.engine = card.effects.map((effect) => resolve_effect(effect)); - if (game.year === 3 && game.turn === 1) { + 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')); @@ -739,6 +765,28 @@ states.activate_icon = { }, }; +// TODO: implement +// cards 10, 36, 54 +states.add_card_to_tableau = { + inactive: 'add a card to their tableau', + prompt() { + view.prompt = 'Choose a card to add to your tableau'; + for (const c of game.hands[get_active_faction()]) { + gen_action_card(c); + } + }, + card(c: CardId) { + 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(); + }, +}; + states.add_glory = { inactive: 'add tokens to the Bag of Glory', prompt() { @@ -1082,14 +1130,23 @@ states.gain_hero_points = { inactive: 'gain Hero Points', prompt() { const value = get_active_node_args().v; - view.prompt = value > 1 ? `Gain ${value} Hero Points` : 'Gain 1 Hero Point'; - gen_action('gain_hp'); + if (game.hero_points.pool > 0) { + view.prompt = + value > 1 ? `Gain ${value} Hero Points` : 'Gain 1 Hero Point'; + gen_action('gain_hp'); + } else { + view.prompt = 'No Hero Points available in pool. You must skip'; + gen_action('skip'); + } }, gain_hp() { const value = get_active_node_args().v; gain_hero_points(get_active_faction(), value); resolve_active_and_proceed(); }, + skip() { + resolve_active_and_proceed(); + }, }; states.game_over = { @@ -1130,13 +1187,25 @@ states.lose_hero_points = { }, }; +// TODO: implement +states.move_attacks = { + inactive: 'move attacks', + prompt() { + view.prompt = 'Choose a Front'; + }, +}; + states.move_track = { inactive: 'move a Track', prompt() { const node = get_active_node(); const track = node.a.t; const value = node.a.v; - const name = tracks[track].name; + + const name = + track === LIBERTY_OR_COLLECTIVIZATION + ? 'Liberty OR Collectivization' + : tracks[track].name; view.prompt = `Move ${name} ${value > 0 ? 'up' : 'down'}`; if (track === GOVERNMENT && value === TOWARDS_CENTER) { @@ -1144,8 +1213,13 @@ states.move_track = { } else if (track === GOVERNMENT && value === AWAY_FROM_CENTER) { view.prompt = `Move ${name} away from center`; } - // return 'Decrease ' + tracks[effect.target].name; - gen_action_standee(track); + + if (track === LIBERTY_OR_COLLECTIVIZATION) { + gen_action_standee(LIBERTY); + gen_action_standee(COLLECTIVIZATION); + } else { + gen_action_standee(track); + } }, standee(s: number) { const node = get_active_node(); @@ -1185,6 +1259,14 @@ states.move_track_up_or_down = { }, }; +// TODO: implement card 46 +states.peek_fascist_cards = { + inactive: 'peek at Fascist cards', + prompt() { + view.prompt = 'Choose one card to return to the top of the deck'; + }, +}; + states.player_turn = { inactive: 'play their turn', prompt() { @@ -1224,7 +1306,7 @@ states.player_turn = { insert_before_active_node( create_seq_node([ create_leaf_node('choose_area_ap', faction_id, { - strength: (cards[card] as PlayerCard).strength, + strength: (cards[card] as PlayerCard).strength, }), create_function_node('check_activate_icon'), ]) @@ -1285,6 +1367,23 @@ states.remove_blank_marker = { }, }; +// TOOD: implement from card 6 +states.remove_attack_from_fronts = { + inactive: 'remove attacks', + prompt() { + view.prompt = 'Choose a front to remove an attack'; + }, +}; + +// TOOD: implement from card 15 +states.return_card = { + inactive: 'return a card', + prompt() { + view.prompt = 'Choose a card to return'; + // Return from trash + }, +}; + states.spend_hero_points = { inactive: 'spend Hero points', prompt() { @@ -1388,6 +1487,28 @@ states.spend_hero_points = { }, }; +// TODO: implement, card 33 +states.swap_card_tableau_hand = { + inactive: 'swap cards', + prompt() { + view.prompt = 'Choose cards'; + const faction = get_active_faction(); + for (const c of game.hands[faction]) { + gen_action_card(c); + } + }, + card(c: CardId) {}, +}; + +// TODO: implement, card 12 + card 32 + card 44 +// Value should come from args +states.take_hero_points = { + inactive: 'take Hero Points', + prompt() { + view.prompt = 'Choose a player to take Hero Points from'; + }, +}; + states.use_organization_medallion = { inactive: 'use Organization Medallion', prompt() { @@ -1444,6 +1565,129 @@ states.use_strategy_medallion = { // #endrregion +// #region card effects + +function card1_event2() { + const value = game.tracks[FOREIGN_AID] >= 6 ? 3 : 2; + insert_after_active_node( + resolve_effect(create_effect('track', FOREIGN_AID, value)) + ); + resolve_active_and_proceed(); +} + +function card3_event2() { + const value = game.tracks[FOREIGN_AID] >= 8 ? 2 : 1; + insert_after_active_node( + resolve_effect(create_effect('track', GOVERNMENT, value)) + ); + resolve_active_and_proceed(); +} + +function card10_event2() { + if (game.tracks[FOREIGN_AID] >= 6) { + resolve_effect(create_effect('draw_card', SELF, 2)); + } + resolve_active_and_proceed(); +} + +function card16_event2() { + const value = game.tracks[GOVERNMENT] >= 6 ? 4 : 3; + + insert_after_active_node( + resolve_effect(create_effect('track', FOREIGN_AID, value)) + ); + resolve_active_and_proceed(); +} + +function card17_event3() { + const value = game.tracks[GOVERNMENT] >= 6 ? -4 : -3; + insert_after_active_node( + resolve_effect(create_effect('track', COLLECTIVIZATION, value)) + ); + resolve_active_and_proceed(); +} + +function card20_event3() { + const value = game.tracks[SOVIET_SUPPORT] >= 6 ? 2 : 1; + insert_after_active_node( + create_seq_node([ + resolve_effect(create_effect('front', MADRID, value)), + resolve_effect(create_effect('front', SOUTHERN, value)), + ]) + ); + resolve_active_and_proceed(); +} + +function card22_event3() { + const value = game.tracks[SOVIET_SUPPORT] >= 8 ? -3 : -3; + insert_after_active_node( + resolve_effect(create_effect('track', GOVERNMENT, value)) + ); + resolve_active_and_proceed(); +} + +function card23_event1() { + const value = game.tracks[SOVIET_SUPPORT] >= 6 ? 4 : 3; + insert_after_active_node(resolve_effect(create_effect('front', ANY, value))); + resolve_active_and_proceed(); +} + +function card26_event1() { + game.active_abilities.push(COMMUNIST_EXTRA_HERO_POINT); +} + +function card29_event2() { + const value = game.tracks[GOVERNMENT] <= 5 ? -3 : -2; + insert_after_active_node( + resolve_effect(create_effect('track', LIBERTY, value)) + ); + resolve_active_and_proceed(); +} + +function card35_event2() { + const value = game.tracks[GOVERNMENT] <= 5 ? 2 : 1; + insert_after_active_node( + resolve_effect(create_effect('track', SOVIET_SUPPORT, value)) + ); + resolve_active_and_proceed(); +} + +function card42_event3() { + game.active_abilities.push(ANARCHIST_EXTRA_HERO_POINT); +} + +function card45_event2() { + if (game.tracks[LIBERTY] >= 6) { + insert_after_active_node( + resolve_effect(create_effect('track', COLLECTIVIZATION, 1)) + ); + } + resolve_active_and_proceed(); +} + +function card50_event2() { + const value = game.tracks[COLLECTIVIZATION] >= 8 ? 3 : 2; + insert_after_active_node( + resolve_effect(create_effect('front', ARAGON, value)) + ); + resolve_active_and_proceed(); +} + +function card53_event2() { + const value = game.tracks[LIBERTY] >= 8 ? 3 : 2; + insert_after_active_node(resolve_effect(create_effect('front', ANY, value))); + resolve_active_and_proceed(); +} + +function card54_event1() { + const value = game.tracks[COLLECTIVIZATION] >= 8 ? 3 : 2; + insert_after_active_node( + resolve_effect(create_effect('track', LIBERTY, value)) + ); + resolve_active_and_proceed(); +} + +// #endregion // #region GAME FUNCTIONS @@ -1541,6 +1785,7 @@ function end_of_turn() { Object.keys(game.fronts).forEach((front_id) => { game.fronts[front_id].contributions = []; }); + game.active_abilities = []; game.used_medallions = []; if (game.turn === 4) { end_of_year(); @@ -1563,9 +1808,9 @@ function end_of_year() { } const glory_to_draw = [0, 1, 2, 5]; const glory_this_year: Record = { - [ANARCHISTS_ID]: false, - [COMMUNISTS_ID]: false, - [MODERATES_ID]: false, + a: false, + c: false, + m: false, }; for (let i = 0; i < glory_to_draw[game.year]; ++i) { const index = random(game.bag_of_glory.length); @@ -1606,6 +1851,14 @@ function gain_hero_points(faction_id: FactionId, value: number) { if (game.hero_points.pool === 0) { return; } + if ( + (faction_id === ANARCHISTS_ID && + (game.active_abilities || []).includes(ANARCHIST_EXTRA_HERO_POINT)) || + (faction_id === COMMUNISTS_ID && + (game.active_abilities || []).includes(COMMUNIST_EXTRA_HERO_POINT)) + ) { + value++; + } const gain = Math.min(game.hero_points.pool, value); game.hero_points.pool -= gain; game.hero_points[faction_id] += gain; @@ -1940,17 +2193,22 @@ function get_faction_to_resolve_effect(effect: Effect): FactionId { } const effect_type_state_map: Record = { + 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: Effect // faction: FactionId = get_active_faction() // -): EngineNode | null { +): EngineNode { const args = { t: effect.target, v: effect.value, @@ -1958,6 +2216,14 @@ function resolve_effect( const faction = get_faction_to_resolve_effect(effect); + if (effect.type === 'function') { + return create_function_node(effect.target as string); + } + if (effect.type === 'state') { + return create_leaf_node(effect.target as string, faction, { + v: effect.value, + }); + } // Default cases where effect type is mapped to a state let state = effect_type_state_map[effect.type]; if (state !== undefined) { @@ -1965,23 +2231,124 @@ function resolve_effect( } // Specific mapping based on target - if ( - effect.type === 'hero_points' && - effect.target === PLAYER_WITH_MOST_HERO_POINTS - ) { - state = 'lose_hero_points'; - } - if (effect.type === 'hero_points' && effect.target === SELF) { - state = 'gain_hero_points'; - } - if (effect.type === 'draw_card' && effect.target === SELF) { - state = 'draw_card'; - } - if (state === undefined) { + const strategies = [ + { + condition: + effect.type === 'hero_points' && + effect.target === PLAYER_WITH_MOST_HERO_POINTS, + resolve: () => { + return create_leaf_node('lose_hero_points', faction, args); + }, + }, + { + condition: effect.type === 'hero_points' && effect.target === ALL_PLAYERS, + resolve: () => { + return create_seq_node( + get_player_order().map((faction) => + create_leaf_node('gain_hero_points', faction, { + v: effect.value, + }) + ) + ); + }, + }, + { + condition: effect.type === 'hero_points' && effect.target === SELF, + resolve: () => { + return create_leaf_node('gain_hero_points', faction, args); + }, + }, + { + condition: + effect.type === 'hero_points' && + role_ids.includes(effect.target as FactionId), + resolve: () => { + return create_leaf_node( + 'gain_hero_points', + effect.target as FactionId, + args + ); + }, + }, + { + condition: + effect.type === 'hero_points' && effect.target === INITIATIVE_PLAYER, + resolve: () => { + return create_leaf_node('gain_hero_points', game.initiative); + }, + }, + { + condition: effect.type === 'draw_card' && effect.target === SELF, + resolve: () => { + return create_leaf_node('draw_card', faction, args); + }, + }, + { + condition: + effect.type === 'draw_card' && effect.target === INITIATIVE_PLAYER, + resolve: () => { + return create_leaf_node('draw_card', game.initiative, args); + }, + }, + { + condition: + effect.type === 'draw_card' && + role_ids.includes(effect.target as FactionId), + resolve: () => { + return create_leaf_node('draw_card', effect.target as FactionId, args); + }, + }, + { + condition: effect.type === 'draw_card' && effect.target === ALL_PLAYERS, + resolve: () => { + return create_seq_node( + get_player_order(get_active_faction()).map((faction) => + create_leaf_node('draw_card', faction, { + v: effect.value, + }) + ) + ); + }, + }, + { + condition: effect.type === 'draw_card' && effect.target === OTHER_PLAYERS, + resolve: () => { + const leaf_nodes = get_player_order(get_active_faction()).map( + (faction) => + create_leaf_node('draw_card', faction, { + v: effect.value, + }) + ); + array_remove(leaf_nodes, 0); + return create_seq_node(leaf_nodes); + }, + }, + ]; + + const strategy = strategies.find((strategy) => strategy.condition); + + if (strategy) { + return strategy.resolve(); + } else { console.log('----UNRESOLVED EFFECT----', effect); - return null; - } - return create_leaf_node(state, faction, args); + throw new Error('Unresolved effect'); + } + // if ( + // // TODO: determine state based on value? + // effect.type === 'hero_points' && + // effect.target === PLAYER_WITH_MOST_HERO_POINTS + // ) { + // state = 'lose_hero_points'; + // } else if (effect.type === 'hero_points' && effect.target === SELF) { + // state = 'gain_hero_points'; + // } else if (effect.type === 'draw_card' && effect.target === SELF) { + // state = 'draw_card'; + // } + // if (state === undefined) { + // console.log('----UNRESOLVED EFFECT----', effect); + // return null; + // } + // return create_leaf_node(state, faction, args); } function win_final_bid(faction_id: FactionId) { @@ -2116,7 +2483,6 @@ function log_h3(msg: string) { // #region UTILITY - function get_active_faction(): FactionId { return player_faction_map[game.active]; } -- cgit v1.2.3