diff options
author | Frans Bongers <fransbongers@macbookpro.home> | 2025-03-23 11:43:03 +0100 |
---|---|---|
committer | Frans Bongers <fransbongers@macbookpro.home> | 2025-03-23 11:43:13 +0100 |
commit | ad4cc37be3e14fdf8f631c64d99ab595a2524b24 (patch) | |
tree | 1fdfce91bdc8fc4abc725937033ca8aa8f869380 | |
parent | 846df22740ec71f9aba3fa52351ce8de1a34435f (diff) | |
download | land-and-freedom-ad4cc37be3e14fdf8f631c64d99ab595a2524b24.tar.gz |
multiactive end of year discard
-rw-r--r-- | rules.js | 81 | ||||
-rw-r--r-- | rules.ts | 106 | ||||
-rw-r--r-- | types.d.ts | 6 |
3 files changed, 130 insertions, 63 deletions
@@ -76,9 +76,10 @@ function gen_spend_hero_points() { gen_action('spend_hp'); } } +const multiactive_states = ['choose_card', 'end_of_year_discard']; function action(state, player, action, arg) { game = state; - if (action !== 'undo' && game.state !== 'choose_card') { + if (action !== 'undo' && !multiactive_states.includes(game.state)) { push_undo(); } let S = states[game.state]; @@ -171,14 +172,6 @@ function setup_player_turn(faction_id) { 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(); const next_faction = get_next_faction_in_player_order(faction); @@ -202,7 +195,6 @@ function start_of_player_turn() { resolve_active_and_proceed(); } const engine_functions = { - check_end_of_year_discard, checkpoint, end_of_player_turn, end_of_turn, @@ -604,9 +596,10 @@ states.activate_icon = { gen_action_bonus(data_1.TEAMWORK_BONUS); can_activate_icon = true; } - else { - can_activate_icon = false; - } + break; + case 'draw_card': + gen_action('draw_card'); + can_activate_icon = true; break; default: gen_action(i); @@ -614,9 +607,9 @@ states.activate_icon = { if (!player_can_resolve_icon(i)) { view.actions[i] = 0; } - if (!can_activate_icon) { - gen_action('skip'); - } + } + if (!can_activate_icon) { + gen_action('skip'); } }, spend_hp() { @@ -1234,8 +1227,9 @@ states.draw_glory = { }; states.end_of_year_discard = { inactive: 'discard cards from hand and tableau', - prompt() { - const faction_id = get_active_faction(); + prompt(player) { + const faction_id = player_faction_map[player]; + const { d: discarded } = get_active_node_args(); const hand = game.hands[faction_id]; const hand_limit = get_hand_limit(faction_id); const needs_to_discard_from_hand = hand.length > hand_limit; @@ -1252,25 +1246,50 @@ states.end_of_year_discard = { if (needs_to_discard_from_hand && needs_to_discard_from_tableau) { view.prompt = 'Discard a card from your hand or tableau'; } - else { + else if (needs_to_discard_from_hand || needs_to_discard_from_tableau) { view.prompt = `Discard a card from your ${needs_to_discard_from_hand ? 'hand' : 'tableau'}`; } + else { + view.prompt = 'Confirm discard'; + view.actions.confirm = 1; + } + if (discarded[faction_id].h.length > 0 || discarded[faction_id].t.length > 0) { + view.actions.undo = 1; + } }, - card(c) { - const faction_id = get_active_faction(); + card(c, player) { + const faction_id = player_faction_map[player]; + const { d: discarded } = get_active_node_args(); if (game.hands[faction_id].includes(c)) { game.hands[faction_id] = game.hands[faction_id].filter((id) => id !== c); + discarded[faction_id].h.push(c); } else if (game.tableaus[faction_id].includes(c)) { game.tableaus[faction_id] = game.tableaus[faction_id].filter((id) => id !== c); + discarded[faction_id].t.push(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(); + if (game.hands[faction_id].length <= get_hand_limit(faction_id) && + game.tableaus[faction_id].length <= game.year) { + log(`${player} discarded cards.`); + } + }, + undo(_, player) { + const faction_id = player_faction_map[player]; + const last_discarded = game.discard[faction_id].pop(); + const { d: discarded } = get_active_node_args(); + if (discarded[faction_id].h.includes(last_discarded)) { + game.hands[faction_id].push(last_discarded); + discarded[faction_id].h.pop(); } else { - log(`${game.active} discarded cards.`); + game.tableaus[faction_id].push(last_discarded); + discarded[faction_id].t.pop(); + } + }, + confirm(_, player) { + set_delete(game.active, player); + if (game.active.length === 0) { resolve_active_and_proceed(); } }, @@ -2269,7 +2288,17 @@ function end_of_year_cleanup() { } 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 = []; + const factions_that_must_discard = []; + role_ids.forEach((factionId) => { + if (game.hands[factionId].length > get_hand_limit(factionId) || + game.tableaus[factionId].length > game.year) { + factions_that_must_discard.push(factionId); + } + }); + if (factions_that_must_discard.length > 0) { + game.engine.push(create_state_node('end_of_year_discard', factions_that_must_discard, { d: [{ h: [], t: [] }, { h: [], t: [] }, { h: [], t: [] }] })); + } game.engine.push(create_function_node('checkpoint')); game.engine.push(create_function_node('start_year')); game.top_of_events_deck = null; @@ -21,6 +21,7 @@ import { FascistId, ClosestToDefeat, ClosestToVictory, + EndOfYearDiscardArgs, } from './types'; import data, { @@ -167,6 +168,8 @@ function gen_spend_hero_points() { } } +const multiactive_states = ['choose_card', 'end_of_year_discard']; + export function action( state: Game, player: Player, @@ -175,7 +178,7 @@ export function action( ) { game = state; - if (action !== 'undo' && game.state !== 'choose_card') { + if (action !== 'undo' && !multiactive_states.includes(game.state)) { push_undo(); } @@ -298,20 +301,6 @@ function setup_player_turn(faction_id: FactionId) { } } -// Check if player needs to discard cards. If so inserts state node -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(); const next_faction = get_next_faction_in_player_order(faction); @@ -336,7 +325,6 @@ function start_of_player_turn() { } const engine_functions: Record<string, Function> = { - check_end_of_year_discard, checkpoint, end_of_player_turn, end_of_turn, @@ -846,20 +834,23 @@ states.activate_icon = { if (game.bonuses[TEAMWORK_BONUS] === OFF) { gen_action_bonus(TEAMWORK_BONUS); can_activate_icon = true; - } else { - can_activate_icon = false; } break; + case 'draw_card': + gen_action('draw_card'); + can_activate_icon = true; + break; default: gen_action(i); } - + if (!player_can_resolve_icon(i)) { view.actions[i] = 0; } - if (!can_activate_icon) { - gen_action('skip'); - } + } + + if (!can_activate_icon) { + gen_action('skip'); } }, spend_hp() { @@ -1538,10 +1529,14 @@ states.draw_glory = { }, }; +// Multiactive end of year discard state states.end_of_year_discard = { inactive: 'discard cards from hand and tableau', - prompt() { - const faction_id = get_active_faction(); + prompt(player: Player) { + const faction_id = player_faction_map[player]; + + const {d: discarded} = get_active_node_args<EndOfYearDiscardArgs>(); + const hand = game.hands[faction_id]; const hand_limit = get_hand_limit(faction_id); const needs_to_discard_from_hand = hand.length > hand_limit; @@ -1556,30 +1551,57 @@ states.end_of_year_discard = { if (needs_to_discard_from_hand && needs_to_discard_from_tableau) { view.prompt = 'Discard a card from your hand or tableau'; - } else { + } else if (needs_to_discard_from_hand || needs_to_discard_from_tableau) { view.prompt = `Discard a card from your ${ needs_to_discard_from_hand ? 'hand' : 'tableau' }`; + } else { + view.prompt = 'Confirm discard'; + view.actions.confirm = 1; + } + if (discarded[faction_id].h.length > 0 || discarded[faction_id].t.length > 0) { + view.actions.undo = 1; } }, - card(c: CardId) { - const faction_id = get_active_faction(); + card(c: CardId, player: Player) { + const faction_id = player_faction_map[player]; + + // Need to keep track of where cards are discarded from for undo + const {d: discarded} = get_active_node_args<EndOfYearDiscardArgs>(); + if (game.hands[faction_id].includes(c)) { game.hands[faction_id] = game.hands[faction_id].filter((id) => id !== c); + discarded[faction_id].h.push(c); } else if (game.tableaus[faction_id].includes(c)) { game.tableaus[faction_id] = game.tableaus[faction_id].filter( (id) => id !== c ); + discarded[faction_id].t.push(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 + game.hands[faction_id].length <= get_hand_limit(faction_id) && + game.tableaus[faction_id].length <= game.year ) { - // More cards to discard so resolve same state again - next(); + log(`${player} discarded cards.`); + } + }, + undo(_, player: Player) { + const faction_id = player_faction_map[player]; + const last_discarded = game.discard[faction_id].pop(); + const {d: discarded} = get_active_node_args<EndOfYearDiscardArgs>(); + if (discarded[faction_id].h.includes(last_discarded)) { + game.hands[faction_id].push(last_discarded); + discarded[faction_id].h.pop() } else { - log(`${game.active} discarded cards.`); + game.tableaus[faction_id].push(last_discarded); + discarded[faction_id].t.pop() + } + }, + confirm(_, player: Player) { + set_delete(game.active as Player[], player); + if (game.active.length === 0) { resolve_active_and_proceed(); } }, @@ -2780,11 +2802,23 @@ function end_of_year_cleanup() { ); gain_hero_points_in_player_order(players_to_gain_hero_points, game.year); + game.engine = []; // Setup card discarding - game.engine = get_player_order().map((f) => - create_function_node('check_end_of_year_discard', { f }) - ); - game.engine.push(create_function_node('checkpoint')); + const factions_that_must_discard: FactionId[] = []; + role_ids.forEach((factionId) => { + if ( + game.hands[factionId].length > get_hand_limit(factionId) || + game.tableaus[factionId].length > game.year + ) { + factions_that_must_discard.push(factionId); + } + }) + if (factions_that_must_discard.length > 0) { + game.engine.push(create_state_node('end_of_year_discard', factions_that_must_discard, {d: [{h:[], t: []}, {h:[], t: []}, {h:[], t: []}]})) + + } + + game.engine.push(create_function_node('checkpoint')); // Can be removed as preceding state is now multi active? game.engine.push(create_function_node('start_year')); // New deck is used for next year so clear top card @@ -39,7 +39,7 @@ export interface Game { bag_of_glory: FactionId[]; bonuses: number[]; current_events: CardId[]; - discard: number[][]; + discard: CardId[][]; engine: EngineNode[]; /** * Set to faction whos turn it is or null if not player turn @@ -252,4 +252,8 @@ export interface PlayCardArgs extends EngineNodeArgsBase { export interface PlayerTurnArgs extends EngineNodeArgsBase { } +export interface EndOfYearDiscardArgs { + d: Array<{h: CardId[]; t: CardId[]}>; +} + // #endregion |