diff options
author | Frans Bongers <fransbongers@franss-mbp.home> | 2024-12-30 21:31:30 +0100 |
---|---|---|
committer | Frans Bongers <fransbongers@franss-mbp.home> | 2024-12-30 21:31:30 +0100 |
commit | 2122adacc6c569e78bc71b049ea5cdffe13208a5 (patch) | |
tree | a13a52a8271b82f76c63246482c10490a36d8721 /rules.ts | |
parent | b952d532d4b84c136e3c3a6893a63b0ed5b5ece3 (diff) | |
download | land-and-freedom-2122adacc6c569e78bc71b049ea5cdffe13208a5.tar.gz |
implement remove attacks effects
Diffstat (limited to 'rules.ts')
-rw-r--r-- | rules.ts | 299 |
1 files changed, 255 insertions, 44 deletions
@@ -239,6 +239,16 @@ function create_seq_node(children: EngineNode[]): SeqNode { }; } +// Use to force confirm question if possible to undo +function checkpoint() { + if (game.undo.length > 0) { + insert_after_active_node( + create_leaf_node('confirm_turn', get_active_faction()) + ); + } + resolve_active_and_proceed(); +} + function setup_bag_of_glory() { game.engine = [ create_leaf_node('add_glory', game.initiative), @@ -262,6 +272,7 @@ function setup_final_bid() { game.engine = player_order.map((faction_id) => create_leaf_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(); @@ -290,6 +301,7 @@ function start_of_player_turn() { const engine_functions: Record<string, Function> = { check_activate_icon, + checkpoint, end_of_turn, // end_of_year, setup_bag_of_glory, @@ -318,6 +330,7 @@ const engine_functions: Record<string, Function> = { card50_event2, card53_event2, card54_event1, + setup_return_card_from_trash, }; function get_active( @@ -352,6 +365,16 @@ function get_active_node_args<T = any>(): T { return null; } +function update_active_node_args<T = any>(args: T) { + const node = get_active_node(game.engine); + if (node.t === leaf_node || node.t === function_node) { + node.a = { + ...node.a, + ...args, + }; + } +} + function insert_before_or_after_active_node( node: EngineNode, position: 'before' | 'after', @@ -453,7 +476,6 @@ function game_view(state: Game, player: Player) { tableaus: game.tableaus, tracks: game.tracks, triggered_track_effects: game.triggered_track_effects, - undo: game.undo, // TODO: remove used_medallions: game.used_medallions, year: game.year, }; @@ -765,15 +787,17 @@ 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()]) { + 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'); + } }, card(c: CardId) { const faction_id = get_active_faction(); @@ -785,6 +809,9 @@ states.add_card_to_tableau = { ); resolve_active_and_proceed(); }, + skip() { + resolve_active_and_proceed(); + }, }; states.add_glory = { @@ -826,26 +853,27 @@ states.add_to_front = { states.attack_front = { inactive: 'attack a Front', prompt() { - const node = get_active_node(); - const front = node.a.t; - - let targets: Array<FrontId> = []; - - if (front === 'd' || front === 'v') { - targets = get_fronts_closest_to(front); - } else if (game.fronts[front].status === DEFEAT) { - targets = get_fronts_closest_to('d'); - } else if (game.fronts[front].status === VICTORY) { - targets = get_fronts_to_add_to(ANY); + const { t: target, n } = get_active_node_args(); + + let fronts: Array<FrontId> = []; + + if (target === ANY) { + fronts = get_fronts_to_add_to(ANY, n); + } else if (target === 'd' || target === 'v') { + fronts = get_fronts_closest_to(target); + } else if (game.fronts[target].status === DEFEAT) { + fronts = get_fronts_closest_to('d'); + } else if (game.fronts[target].status === VICTORY) { + fronts = get_fronts_to_add_to(ANY); } else { - targets.push(front); + fronts.push(target); } view.prompt = - targets.length === 1 - ? `Attack ${front_names[targets[0]]}` + fronts.length === 1 + ? `Attack ${front_names[fronts[0]]}` : 'Attack a front'; - targets.forEach((id) => gen_action('front', id)); + fronts.forEach((id) => gen_action('front', id)); }, front(f: FrontId) { const node = get_active_node(); @@ -922,8 +950,17 @@ states.choose_area_ap = { } }, bonus(b: number) { + // Turn on bonus update_bonus(b, ON); - // TODO: insert action in case other bonus is OFF and AP left + const s: number = get_active_node_args().strength; + + // Check if other bonus can be activated + const other_bonus = b === TEAMWORK_BONUS ? MORALE_BONUS : TEAMWORK_BONUS; + if (s > 1 && game.bonuses[other_bonus] === OFF) { + insert_after_active_node( + resolve_effect(create_effect('bonus', other_bonus, ON)) + ); + } resolve_active_and_proceed(); }, front(f: FrontId) { @@ -946,7 +983,7 @@ states.choose_area_ap = { }; states.change_bonus = { - inactive: 'gain select Bonus', + inactive: 'select Bonus', prompt() { const args = get_active_node_args(); if ( @@ -964,8 +1001,10 @@ states.change_bonus = { gen_action_bonus(bonus); } } - } else if (args.v === OFF) { - view.prompt = `Turn off ${bonus_names[args.t]}`; + } else { + view.prompt = `Turn ${args.v === OFF ? 'off' : 'on'} ${ + bonus_names[args.t] + }`; gen_action_bonus(args.t); } }, @@ -1013,14 +1052,21 @@ states.choose_final_bid = { card(c: CardId) { const faction = get_active_faction(); game.selected_cards[faction].push(c); - if (game.selected_cards[faction].length < 3) { - next(); - } else { + const number_selected = game.selected_cards[faction].length; + console.log('number selected', number_selected); + const number_hand = game.hands[faction].length; + if ( + number_selected === 3 || + (number_hand < 4 && number_selected === number_hand - 1) + ) { + console.log('proceed'); resolve_active_and_proceed(); + } else { + next(); } }, done() { - resolve_active_and_proceed(); + resolve_active_and_proceed(true); }, }; @@ -1132,10 +1178,19 @@ states.end_of_year_discard = { }, }; +// TODO: rename to change_hero_points states.gain_hero_points = { inactive: 'gain Hero Points', prompt() { 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 ? `Gain ${value} Hero Points` : 'Gain 1 Hero Point'; @@ -1150,6 +1205,11 @@ states.gain_hero_points = { gain_hero_points(get_active_faction(), value); resolve_active_and_proceed(); }, + lose_hp() { + const value = get_active_node_args().v; + lose_hero_point(get_active_faction(), value) + resolve_active_and_proceed(); + }, skip() { resolve_active_and_proceed(); }, @@ -1271,7 +1331,6 @@ states.move_track_up_or_down = { }, }; -// TODO: implement card 46 states.peek_fascist_cards = { inactive: 'peek at Fascist cards', prompt() { @@ -1344,7 +1403,7 @@ states.player_turn = { }, play_for_event() { const faction = get_active_faction(); - const { effects } = play_card(faction, 'play_for_ap'); + const { effects } = play_card(faction, 'play_for_event'); insert_before_active_node(create_effects_node(effects)); @@ -1393,20 +1452,103 @@ states.remove_blank_marker = { }, }; -// TOOD: implement from card 6 +/** + * Event cards 6 and 39 + * 6: remove 1 attack from up to three fronts + * 16: Move up to 2 Attacks from a Front to another Front + * 39: remove up to 3 attacks from a Front; place 2 back(-2) + */ states.remove_attack_from_fronts = { inactive: 'remove attacks', prompt() { - view.prompt = 'Choose a front to remove an attack'; + const { f, v: card_id } = get_active_node_args(); + view.prompt = + card_id === 6 + ? 'Choose a Front to remove an attack from' + : 'Choose a Front to remove attacks from'; + const front_data = f ?? {}; + + let is_front_with_attacks = false; + 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'); + } + }, + front(id: FrontId) { + 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); + + 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_leaf_node('attack_front', get_active_faction(), { + t: ANY, + v: card_id === 39 ? -2 : -1 * removed_value, + n: card_id === 16 ? id : undefined, + }) + ); + resolve_active_and_proceed(); + } + }, + skip() { + const { f, v: card_id } = get_active_node_args(); + const values: number[] = Object.values(f ?? {}); + if (card_id === 39 && values.length > 0) { + insert_after_active_node( + create_leaf_node('attack_front', get_active_faction(), { + t: ANY, + v: -2, + }) + ); + } + resolve_active_and_proceed(); }, }; -// TOOD: implement from card 15 states.return_card = { - inactive: 'return a card', + inactive: 'return a card to their hand', prompt() { - view.prompt = 'Choose a card to return'; - // Return from trash + view.prompt = 'Choose a card to return to your hand'; + if (game.selectable_cards.length === 0) { + view.prompt = 'No card in trash to return. You must skip'; + gen_action('skip'); + } + for (const c of game.selectable_cards) { + gen_action_card(c); + } + }, + card(c: CardId) { + 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`); + + game.selectable_cards = []; + resolve_active_and_proceed(); + }, + skip() { + game.selectable_cards = []; + resolve_active_and_proceed(); }, }; @@ -1513,17 +1655,73 @@ states.spend_hero_points = { }, }; -// TODO: implement, card 33 states.swap_card_tableau_hand = { inactive: 'swap cards', prompt() { - view.prompt = 'Choose cards'; + view.prompt = + 'Choose a card in your tableau and a card in your hand to swap'; const faction = get_active_faction(); - for (const c of game.hands[faction]) { - gen_action_card(c); + gen_action('skip'); + if (game.tableaus[faction].length === 0) { + view.prompt = 'No card in your tableau to swap. You must skip'; + return; + } + if (game.hands[faction].length === 0) { + view.prompt = 'No card in your hand to swap. You must skip'; + 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); + } + } + }, + card(c: CardId) { + console.log('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(); }, - // card(c: CardId) {}, }; function resolve_take_hero_points(faction: FactionId) { @@ -1685,6 +1883,7 @@ function card23_event1() { function card26_event1() { game.active_abilities.push(COMMUNIST_EXTRA_HERO_POINT); + resolve_active_and_proceed(); } function card29_event2() { @@ -1705,6 +1904,7 @@ function card35_event2() { function card42_event3() { game.active_abilities.push(ANARCHIST_EXTRA_HERO_POINT); + resolve_active_and_proceed(); } function card45_event2() { @@ -1745,6 +1945,14 @@ function card54_event1() { resolve_active_and_proceed(); } +function setup_return_card_from_trash() { + const faction = get_active_faction(); + game.selectable_cards = game.trash[faction].filter( + (c) => c !== game.played_card + ); + resolve_active_and_proceed(); +} + // #endregion // #region GAME FUNCTIONS @@ -1860,7 +2068,9 @@ function end_of_year() { if (is_won) { log('The war is won!'); } else { + console.log('The war is lost'); game_over('None', 'The war is lost. All Players lose the game!'); + resolve_active_and_proceed(); return; } } @@ -1943,6 +2153,7 @@ function game_over(result: Player | 'None', victory: string) { // game.active = 'None'; game.result = result; game.victory = victory; + game.undo = []; log_br(); log(game.victory); } @@ -2048,12 +2259,13 @@ function resolve_final_bid() { resolve_active_and_proceed(); } -// TODO: check for defeated / won fronts -function get_fronts_to_add_to(target: string): FrontId[] { +function get_fronts_to_add_to(target: string, not: FrontId[] = []): FrontId[] { if (target === CLOSEST_TO_DEFEAT || target === CLOSEST_TO_VICTORY) { return get_fronts_closest_to(target); } else if (target === ANY) { - return FRONTS.filter((id) => game.fronts[id].status === null); + return FRONTS.filter( + (id) => game.fronts[id].status === null && !not.includes(id) + ); } else { return [target as FrontId]; } @@ -2180,7 +2392,6 @@ function update_bonus(bonus_id: number, status: number) { logi(`${bonus_names[bonus_id]} ${status === ON ? 'on' : 'off'}`); } -// TODO: acccount for victory / defeat of front function update_front( // f: string, front_id: FrontId, |