summaryrefslogtreecommitdiff
path: root/rules.ts
diff options
context:
space:
mode:
authorFrans Bongers <fransbongers@franss-mbp.home>2024-12-27 19:45:21 +0100
committerFrans Bongers <fransbongers@franss-mbp.home>2024-12-27 19:45:21 +0100
commit9d0630a33e0358f943e68008823f355bceb71be1 (patch)
treebbb5b0cd5e1b8381d767ee1694f928f24f8a5ac9 /rules.ts
parent5dc1ee327e16431b6700f3977469943f002a0c7c (diff)
downloadland-and-freedom-9d0630a33e0358f943e68008823f355bceb71be1.tar.gz
setup for event effects
Diffstat (limited to 'rules.ts')
-rw-r--r--rules.ts480
1 files changed, 423 insertions, 57 deletions
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<FactionId, Player> = {
- [ANARCHISTS_ID]: ANARCHIST,
- [COMMUNISTS_ID]: COMMUNIST,
- [MODERATES_ID]: MODERATE,
+ a: ANARCHIST,
+ c: COMMUNIST,
+ m: MODERATE,
};
const player_faction_map: Record<Player, FactionId> = {
@@ -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<string, Function> = {
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<FactionId, boolean> = {
- [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<string, string> = {
+ 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];
}