summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrans Bongers <fransbongers@macbookpro.home>2025-03-23 11:43:03 +0100
committerFrans Bongers <fransbongers@macbookpro.home>2025-03-23 11:43:13 +0100
commitad4cc37be3e14fdf8f631c64d99ab595a2524b24 (patch)
tree1fdfce91bdc8fc4abc725937033ca8aa8f869380
parent846df22740ec71f9aba3fa52351ce8de1a34435f (diff)
downloadland-and-freedom-ad4cc37be3e14fdf8f631c64d99ab595a2524b24.tar.gz
multiactive end of year discard
-rw-r--r--rules.js81
-rw-r--r--rules.ts106
-rw-r--r--types.d.ts6
3 files changed, 130 insertions, 63 deletions
diff --git a/rules.js b/rules.js
index aaaa0b3..72b3966 100644
--- a/rules.js
+++ b/rules.js
@@ -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;
diff --git a/rules.ts b/rules.ts
index 1469ece..be48715 100644
--- a/rules.ts
+++ b/rules.ts
@@ -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
diff --git a/types.d.ts b/types.d.ts
index 213b25d..3afee29 100644
--- a/types.d.ts
+++ b/types.d.ts
@@ -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