summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrans Bongers <fransbongers@macbookpro.home>2025-03-21 21:34:05 +0100
committerFrans Bongers <fransbongers@macbookpro.home>2025-03-21 21:34:05 +0100
commit8f5f3f54a610801a1d739636b49343a122f7113a (patch)
treebebc0159ea037d3ba7af600d537ff2d5ccdb1750
parent9f01f50ce897eda24625d292402b6a731c28e1f6 (diff)
downloadland-and-freedom-8f5f3f54a610801a1d739636b49343a122f7113a.tar.gz
momentum refactor
-rw-r--r--data.js6
-rw-r--r--data.ts5
-rw-r--r--rules.js94
-rw-r--r--rules.ts122
-rw-r--r--types.d.ts5
5 files changed, 108 insertions, 124 deletions
diff --git a/data.js b/data.js
index 916a0aa..5adf1d8 100644
--- a/data.js
+++ b/data.js
@@ -1,6 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
-exports.LIBERTY_OR_COLLECTIVIZATION = exports.COMMUNIST_EXTRA_HERO_POINT = exports.ANARCHIST_EXTRA_HERO_POINT = exports.NORTHERN = exports.SOUTHERN = exports.ARAGON = exports.MADRID = exports.ORGANIZATION_MEDALLION_ID = exports.ARCHIVES_MEDALLION_ID = exports.VOLUNTEERS_MEDALLION_ID = exports.INTELLIGENCE_MEDALLION_ID = exports.PROPAGANDA_MEDALLION_ID = exports.STRATEGY_MEDALLION_ID = exports.FRONTS = exports.DEFEAT = exports.VICTORY = exports.TRASH = exports.TOWARDS_CENTER = exports.AWAY_FROM_CENTER = exports.SELF = exports.PLAYER_WITH_MOST_HERO_POINTS = exports.OTHER_PLAYERS = exports.ON = exports.OFF = exports.TEAMWORK_BONUS = exports.MORALE_BONUS = exports.FOREIGN_AID = exports.SOVIET_SUPPORT = exports.INITIATIVE_PLAYER = exports.GOVERNMENT = exports.COLLECTIVIZATION = exports.CLOSEST_TO_VICTORY = exports.CLOSEST_TO_DEFEAT = exports.LIBERTY = exports.ANY = exports.MODERATES_ID = exports.FASCIST_ID = exports.COMMUNISTS_ID = exports.ANARCHISTS_ID = exports.MODERATE = exports.COMMUNIST = exports.ANARCHIST = exports.ALL_PLAYERS = void 0;
+exports.MOMENTUM = exports.LIBERTY_OR_COLLECTIVIZATION = exports.COMMUNIST_EXTRA_HERO_POINT = exports.ANARCHIST_EXTRA_HERO_POINT = exports.NORTHERN = exports.SOUTHERN = exports.ARAGON = exports.MADRID = exports.ORGANIZATION_MEDALLION_ID = exports.ARCHIVES_MEDALLION_ID = exports.VOLUNTEERS_MEDALLION_ID = exports.INTELLIGENCE_MEDALLION_ID = exports.PROPAGANDA_MEDALLION_ID = exports.STRATEGY_MEDALLION_ID = exports.FRONTS = exports.DEFEAT = exports.VICTORY = exports.TRASH = exports.TOWARDS_CENTER = exports.AWAY_FROM_CENTER = exports.SELF = exports.PLAYER_WITH_MOST_HERO_POINTS = exports.OTHER_PLAYERS = exports.ON = exports.OFF = exports.TEAMWORK_BONUS = exports.MORALE_BONUS = exports.FOREIGN_AID = exports.SOVIET_SUPPORT = exports.INITIATIVE_PLAYER = exports.GOVERNMENT = exports.COLLECTIVIZATION = exports.CLOSEST_TO_VICTORY = exports.CLOSEST_TO_DEFEAT = exports.LIBERTY = exports.ANY = exports.MODERATES_ID = exports.FASCIST_ID = exports.COMMUNISTS_ID = exports.ANARCHISTS_ID = exports.MODERATE = exports.COMMUNIST = exports.ANARCHIST = exports.ALL_PLAYERS = void 0;
exports.create_effect = create_effect;
const LIBERTY = 0;
exports.LIBERTY = LIBERTY;
@@ -22,7 +22,7 @@ const OFF = 0;
exports.OFF = OFF;
const ON = 1;
exports.ON = ON;
-const PLAYER_WITH_MOST_HERO_POINTS = 0;
+const PLAYER_WITH_MOST_HERO_POINTS = 6;
exports.PLAYER_WITH_MOST_HERO_POINTS = PLAYER_WITH_MOST_HERO_POINTS;
const INITIATIVE_PLAYER = 'i';
exports.INITIATIVE_PLAYER = INITIATIVE_PLAYER;
@@ -84,6 +84,8 @@ const ARCHIVES_MEDALLION_ID = 7;
exports.ARCHIVES_MEDALLION_ID = ARCHIVES_MEDALLION_ID;
const ORGANIZATION_MEDALLION_ID = 8;
exports.ORGANIZATION_MEDALLION_ID = ORGANIZATION_MEDALLION_ID;
+const MOMENTUM = 'momentum';
+exports.MOMENTUM = MOMENTUM;
const ANARCHIST_EXTRA_HERO_POINT = 0;
exports.ANARCHIST_EXTRA_HERO_POINT = ANARCHIST_EXTRA_HERO_POINT;
const COMMUNIST_EXTRA_HERO_POINT = 1;
diff --git a/data.ts b/data.ts
index 468cc6d..a06cf10 100644
--- a/data.ts
+++ b/data.ts
@@ -13,7 +13,7 @@ const TEAMWORK_BONUS = 1;
const OFF = 0;
const ON = 1;
-const PLAYER_WITH_MOST_HERO_POINTS = 0;
+const PLAYER_WITH_MOST_HERO_POINTS = 6; // Should not overlap with factionId
const INITIATIVE_PLAYER = 'i';
const ALL_PLAYERS = 'all';
@@ -53,6 +53,8 @@ const VOLUNTEERS_MEDALLION_ID = 6;
const ARCHIVES_MEDALLION_ID = 7;
const ORGANIZATION_MEDALLION_ID = 8;
+const MOMENTUM = 'momentum';
+
// Abilities granted by cards
const ANARCHIST_EXTRA_HERO_POINT = 0;
const COMMUNIST_EXTRA_HERO_POINT = 1;
@@ -116,6 +118,7 @@ export {
ANARCHIST_EXTRA_HERO_POINT,
COMMUNIST_EXTRA_HERO_POINT,
LIBERTY_OR_COLLECTIVIZATION,
+ MOMENTUM,
};
const data: StaticData = {
diff --git a/rules.js b/rules.js
index 2e1b0b9..29b676d 100644
--- a/rules.js
+++ b/rules.js
@@ -146,19 +146,18 @@ function setup_final_bid() {
game.engine.push(create_function_node('setup_choose_card'));
next();
}
-function setup_player_turn() {
+function setup_player_turn(faction_id) {
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());
+ : faction_id;
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();
}
@@ -172,7 +171,8 @@ function check_end_of_year_discard() {
}
function end_of_player_turn() {
const { f: faction } = get_active_node_args();
- if (get_next_faction_in_player_order(faction) === game.first_player) {
+ const next_faction = get_next_faction_in_player_order(faction);
+ if (next_faction === game.first_player) {
game.engine = [
create_state_node('change_active_player', game.initiative),
create_function_node('resolve_fascist_test'),
@@ -180,7 +180,7 @@ function end_of_player_turn() {
];
}
else {
- game.engine = [create_function_node('setup_player_turn')];
+ game.engine = [create_function_node('setup_player_turn', next_faction)];
}
next();
}
@@ -443,6 +443,7 @@ function setup(seed, _scenario, options) {
hero_points: [2, 2, 0, 14],
initiative: data_1.MODERATES_ID,
medallions: [[], [], [], []],
+ momentum: null,
played_card: null,
player_order: [data_1.MODERATE],
selected_cards: [
@@ -972,7 +973,7 @@ states.play_card = {
},
card(c) {
const faction = get_active_faction();
- game.selected_cards[faction] = [c];
+ game.selected_cards[faction].push(c);
game.card_played = 0;
game.played_card = game.selected_cards[faction][0];
resolve_active_and_proceed();
@@ -1047,26 +1048,14 @@ states.choose_final_bid = {
};
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'));
+ game.momentum = faction;
+ const momentum_nodes = resolve_effect((0, data_1.create_effect)('play_card', faction, 1), data_1.MOMENTUM);
+ if (game.faction_turn !== null) {
+ game.engine.push(momentum_nodes);
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',
- }));
+ insert_after_active_node(momentum_nodes);
}
}
states.choose_medallion = {
@@ -1519,10 +1508,11 @@ states.player_turn = {
prompt() {
gen_spend_hero_points();
const faction_id = get_active_faction();
- let { use_ap, use_morale_bonus, use_momentum } = get_active_node_args();
+ let { use_ap, use_morale_bonus, src } = 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;
+ const use_momentum = game.momentum === faction_id && src !== data_1.MOMENTUM;
if (use_momentum) {
gen_action('use_momentum');
if (use_ap || use_morale_bonus || can_play_card) {
@@ -1554,10 +1544,16 @@ states.player_turn = {
resolve_spend_hp();
},
end_turn() {
- if (game.faction_turn === get_active_faction()) {
+ const { src } = get_active_node_args();
+ const faction_id = get_active_faction();
+ if (game.faction_turn === faction_id) {
game.faction_turn = null;
game.played_card = null;
- game.selected_cards[get_active_faction()] = [];
+ game.engine.push(create_function_node('end_of_player_turn', { f: faction_id }));
+ }
+ game.selected_cards[faction_id].pop();
+ if (src === data_1.MOMENTUM) {
+ game.momentum = null;
}
resolve_active_and_proceed(true);
},
@@ -1599,18 +1595,10 @@ states.player_turn = {
next();
},
use_momentum() {
- log("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();
+ const faction_id = get_active_faction();
+ game.selected_cards[faction_id].pop();
+ game.momentum = null;
+ resolve_active_and_proceed();
},
use_morale_bonus() {
log(`Morale Bonus:`);
@@ -1864,6 +1852,7 @@ states.swap_card_tableau_hand = {
const selected_cards = game.selected_cards[faction];
const hand = game.hands[faction];
const tableau = game.tableaus[faction];
+ const { s: selected_at_start } = get_active_node_args();
gen_action('skip');
if (tableau.length === 0) {
view.prompt = 'No card in your tableau to swap.';
@@ -1873,14 +1862,14 @@ states.swap_card_tableau_hand = {
view.prompt = 'No card in your hand to swap.';
return;
}
- if (selected_cards.length === 1) {
+ if (selected_cards.length === selected_at_start) {
for (const c of hand) {
- if (selected_cards[0] !== c) {
+ if (!selected_cards.includes(c)) {
gen_action_card(c);
}
}
}
- if (selected_cards.length === 2) {
+ if (selected_cards.length === selected_at_start + 1) {
for (const c of tableau) {
gen_action_card(c);
}
@@ -1893,14 +1882,15 @@ states.swap_card_tableau_hand = {
const faction = get_active_faction();
const selected_cards = game.selected_cards[faction];
selected_cards.push(c);
- if (selected_cards.length === 3) {
+ const { s: selected_at_start } = get_active_node_args();
+ if (selected_cards.length === selected_at_start + 2) {
const hand = game.hands[faction];
const tableau = game.tableaus[faction];
- array_remove_item(hand, selected_cards[1]);
- array_remove_item(tableau, selected_cards[2]);
- hand.push(selected_cards[2]);
- tableau.push(selected_cards[1]);
- game.selected_cards[faction].length = 1;
+ array_remove_item(hand, selected_cards[selected_at_start + 1]);
+ array_remove_item(tableau, selected_cards[selected_at_start + 2]);
+ hand.push(selected_cards[selected_at_start + 2]);
+ tableau.push(selected_cards[selected_at_start + 1]);
+ game.selected_cards[faction].length = selected_at_start;
resolve_active_and_proceed();
}
},
@@ -2588,7 +2578,7 @@ function create_effects_node(effects, source) {
return create_seq_node(nodes);
}
function get_faction_to_resolve_effect(effect) {
- if (!effect.faction) {
+ if (effect.faction === undefined || effect.faction === null) {
return get_active_faction();
}
if (effect.faction === data_1.INITIATIVE_PLAYER) {
@@ -2624,6 +2614,12 @@ function resolve_effect(effect, source) {
src: source,
});
}
+ if (effect.type === 'swap_card_tableau_hand') {
+ return create_state_node('swap_card_tableau_hand', faction, {
+ ...args,
+ s: game.selected_cards[get_active_faction()].length
+ });
+ }
let state = effect_type_state_map[effect.type];
if (state !== undefined) {
return create_state_node(state, faction, args);
@@ -2911,7 +2907,7 @@ function get_source_name(source) {
case 'tr4': return tracks[4].name + ' Trigger';
case 'track_icon':
return 'Track Trigger';
- case 'momentum':
+ case data_1.MOMENTUM:
return 'Momentum';
}
return source;
diff --git a/rules.ts b/rules.ts
index 8fdf1b9..7c6075d 100644
--- a/rules.ts
+++ b/rules.ts
@@ -68,6 +68,7 @@ import data, {
ARAGON,
FASCIST_ID,
NORTHERN,
+ MOMENTUM,
} from './data';
const OBSERVER = 'Observer';
@@ -263,14 +264,14 @@ function setup_final_bid() {
next();
}
-function setup_player_turn() {
+function setup_player_turn(faction_id: FactionId) {
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());
+ : faction_id;
if (game.first_player === null) {
game.first_player = next_faction;
@@ -279,7 +280,6 @@ function setup_player_turn() {
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();
@@ -301,14 +301,15 @@ function check_end_of_year_discard() {
function end_of_player_turn() {
const { f: faction } = get_active_node_args();
- if (get_next_faction_in_player_order(faction) === game.first_player) {
+ const next_faction = get_next_faction_in_player_order(faction);
+ if (next_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')];
+ game.engine = [create_function_node('setup_player_turn', next_faction)];
}
next();
}
@@ -629,6 +630,7 @@ export function setup(seed: number, _scenario: string, options: Record<string,bo
hero_points: [2, 2, 0, 14],
initiative: MODERATES_ID,
medallions: [[],[],[],[]],
+ momentum: null,
played_card: null,
player_order: [MODERATE],
selected_cards: [
@@ -1228,7 +1230,7 @@ states.play_card = {
},
card(c: CardId) {
const faction = get_active_faction();
- game.selected_cards[faction] = [c];
+ game.selected_cards[faction].push(c);
game.card_played = 0;
// NOTE: I don't think we are using game.played_card in the UI at the moment?
@@ -1314,43 +1316,17 @@ states.choose_final_bid = {
function setup_momentum() {
const faction = get_active_faction();
- // Player received medallion outside of their normal turn
- // and must be resolved
- if (game.faction_turn !== faction) {
- insert_after_active_node(
- resolve_effect(create_effect('play_card', faction, 1), 'momentum')
- );
- return;
- }
-
- // Player gets medallion during their turn. Need to check if it can be player
- // right away or not. Depends on whether card for this turn has been fully resolved or not
-
- // Get player turn node
- const node: StateNode<PlayerTurnArgs> = 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 ?? ({} as PlayerTurnArgs);
+ game.momentum = faction;
- if (
- player_needs_to_play_card ||
- use_ap ||
- use_morale_bonus ||
- resolving_event
- ) {
- // Player hasn't fully resolved this turns card. Update args to enable button
- node.a = {
- ...(node.a || {}),
- use_momentum: true,
- };
+ const momentum_nodes = resolve_effect(create_effect('play_card', faction, 1), MOMENTUM);
+ if (game.faction_turn !== null) {
+ // Player received medallion during a player turn, either their own or another players.
+ // Push to end of the engine so it's resolved after current turn ends
+ game.engine.push(momentum_nodes);
+ return;
} else {
- // Player can resolve choosing a new card
- insert_after_active_node(
- create_state_node<PlayCardArgs>('play_card', faction, {
- src: 'momentum',
- })
- );
+ // Insert right after current node
+ insert_after_active_node(momentum_nodes)
}
}
@@ -1863,7 +1839,7 @@ states.player_turn = {
prompt() {
gen_spend_hero_points();
const faction_id = get_active_faction();
- let { use_ap, use_morale_bonus, use_momentum } =
+ let { use_ap, use_morale_bonus, src } =
get_active_node_args<PlayerTurnArgs>();
use_morale_bonus = use_morale_bonus && game.bonuses[MORALE_BONUS] === ON;
@@ -1872,6 +1848,7 @@ states.player_turn = {
game.faction_turn === faction_id && game.hero_points[faction_id] > 0;
const can_play_card = !game.card_played;
+ const use_momentum = game.momentum === faction_id && src !== MOMENTUM;
if (use_momentum) {
gen_action('use_momentum');
if (use_ap || use_morale_bonus || can_play_card) {
@@ -1905,10 +1882,16 @@ states.player_turn = {
resolve_spend_hp();
},
end_turn() {
- if (game.faction_turn === get_active_faction()) {
+ const {src} = get_active_node_args();
+ const faction_id = get_active_faction();
+ if (game.faction_turn === faction_id) {
game.faction_turn = null;
game.played_card = null;
- game.selected_cards[get_active_faction()] = [];
+ game.engine.push(create_function_node('end_of_player_turn', { f: faction_id }));
+ }
+ game.selected_cards[faction_id].pop();
+ if (src === MOMENTUM) {
+ game.momentum = null;
}
resolve_active_and_proceed(true);
},
@@ -1954,23 +1937,10 @@ states.player_turn = {
next();
},
use_momentum() {
- log("Momentum:");
- const faction = get_active_faction();
- // We need to update since there can be a case where
- // morale bonus hasn't been used yet but is still set to true
- // due to bonus being turned off.
- game.card_played = 0;
- game.selected_cards[faction] = [];
- update_active_node_args<PlayerTurnArgs>({
- use_morale_bonus: false,
- use_momentum: false,
- });
- insert_before_active_node(
- create_state_node<PlayCardArgs>('play_card', faction, {
- src: 'momentum',
- })
- )
- next();
+ const faction_id = get_active_faction();
+ game.selected_cards[faction_id].pop();
+ game.momentum = null;
+ resolve_active_and_proceed();
},
use_morale_bonus() {
log(`Morale Bonus:`)
@@ -2280,6 +2250,7 @@ states.swap_card_tableau_hand = {
const selected_cards = game.selected_cards[faction];
const hand = game.hands[faction];
const tableau = game.tableaus[faction];
+ const { s: selected_at_start } = get_active_node_args();
gen_action('skip');
if (tableau.length === 0) {
view.prompt = 'No card in your tableau to swap.';
@@ -2290,15 +2261,15 @@ states.swap_card_tableau_hand = {
view.prompt = 'No card in your hand to swap.';
return;
}
- if (selected_cards.length === 1) {
+ if (selected_cards.length === selected_at_start) {
for (const c of hand) {
// do not swap currently executing card!
- if (selected_cards[0] !== c) {
+ if (!selected_cards.includes(c)) {
gen_action_card(c);
}
}
}
- if (selected_cards.length === 2) {
+ if (selected_cards.length === selected_at_start + 1) {
for (const c of tableau) {
gen_action_card(c);
}
@@ -2311,14 +2282,15 @@ states.swap_card_tableau_hand = {
const faction = get_active_faction();
const selected_cards = game.selected_cards[faction];
selected_cards.push(c);
- if (selected_cards.length === 3) {
+ const { s: selected_at_start } = get_active_node_args();
+ if (selected_cards.length === selected_at_start + 2) {
const hand = game.hands[faction];
const tableau = game.tableaus[faction];
- array_remove_item(hand, selected_cards[1]);
- array_remove_item(tableau, selected_cards[2]);
- hand.push(selected_cards[2]);
- tableau.push(selected_cards[1]);
- game.selected_cards[faction].length = 1;
+ array_remove_item(hand, selected_cards[selected_at_start + 1]);
+ array_remove_item(tableau, selected_cards[selected_at_start + 2]);
+ hand.push(selected_cards[selected_at_start + 2]);
+ tableau.push(selected_cards[selected_at_start + 1]);
+ game.selected_cards[faction].length = selected_at_start;
resolve_active_and_proceed();
}
},
@@ -3223,7 +3195,7 @@ function create_effects_node(
}
function get_faction_to_resolve_effect(effect: Effect): FactionId {
- if (!effect.faction) {
+ if (effect.faction === undefined || effect.faction === null) {
return get_active_faction();
}
if (effect.faction === INITIATIVE_PLAYER) {
@@ -3263,6 +3235,12 @@ function resolve_effect(effect: Effect, source?: EffectSource): EngineNode {
src: source,
});
}
+ if (effect.type === 'swap_card_tableau_hand') {
+ return create_state_node('swap_card_tableau_hand', faction, {
+ ...args,
+ s: game.selected_cards[get_active_faction()].length
+ });
+ }
// Default cases where effect type is mapped to a state
let state = effect_type_state_map[effect.type];
@@ -3641,7 +3619,7 @@ function get_source_name(source: EffectSource): string {
case 'tr4': return tracks[4].name + ' Trigger';
case 'track_icon':
return 'Track Trigger';
- case 'momentum':
+ case MOMENTUM:
return 'Momentum';
}
return source;
diff --git a/types.d.ts b/types.d.ts
index c1341d2..3459f5a 100644
--- a/types.d.ts
+++ b/types.d.ts
@@ -57,6 +57,11 @@ export interface Game {
hero_points: number[];
initiative: FactionId;
medallions: number[][];
+ /**
+ * Set to FactionId if player has earned the Momentum medallion
+ * during the current turn
+ */
+ momentum: FactionId | null;
played_card: CardId | null;
player_order: Player[];
selected_cards: CardId[][];