summaryrefslogtreecommitdiff
path: root/rules.js
diff options
context:
space:
mode:
Diffstat (limited to 'rules.js')
-rw-r--r--rules.js464
1 files changed, 269 insertions, 195 deletions
diff --git a/rules.js b/rules.js
index 62592e5..8dc2b2e 100644
--- a/rules.js
+++ b/rules.js
@@ -26,14 +26,16 @@ const player_faction_map = {
[exports.MODERATE]: MODERATES_ID,
};
const front_names = {
- a: 'the Aragon Front',
- m: 'the Madrid Front',
- n: 'the Nothern Front',
- s: 'the Southern Front',
+ a: 'Aragon Front',
+ m: 'Madrid Front',
+ n: 'Nothern Front',
+ s: 'Southern Front',
d: 'the Front closest to Defeat',
v: 'the Front closest to Victory',
};
+const bonus_names = ['Morale Bonus', 'Teamwork Bonus'];
const { cards, tracks, } = data_1.default;
+const bonuses = [data_1.MORALE_BONUS, data_1.TEAMWORK_BONUS];
const faction_cards = {
[ANARCHISTS_ID]: make_list(37, 54),
[COMMUNISTS_ID]: make_list(19, 36),
@@ -56,9 +58,15 @@ function gen_action(action, argument) {
view.actions[action].push(argument);
}
}
+function gen_action_bonus(bonus_id) {
+ gen_action('bonus', bonus_id);
+}
function gen_action_card(card_id) {
gen_action('card', card_id);
}
+function gen_action_front(front_id) {
+ gen_action('front', front_id);
+}
function gen_action_standee(track_id) {
gen_action('standee', track_id);
}
@@ -78,59 +86,53 @@ const leaf_node = 'l';
const seq_node = 's';
const function_node = 'f';
const resolved = 1;
+function create_leaf_node(state, faction, args) {
+ return {
+ t: leaf_node,
+ s: state,
+ p: faction,
+ a: args,
+ r: 0,
+ };
+}
+function create_function_node(func_name, args) {
+ return {
+ t: function_node,
+ f: func_name,
+ a: args,
+ r: 0,
+ };
+}
+function create_seq_node(children) {
+ return {
+ t: seq_node,
+ c: children,
+ };
+}
function setup_bag_of_glory() {
- console.log('setup_bag_of_glory');
game.engine = [
- {
- t: leaf_node,
- p: game.initiative,
- s: 'add_glory',
- },
- {
- t: function_node,
- f: 'end_of_turn',
- },
+ create_leaf_node('add_glory', game.initiative),
+ create_function_node('end_of_turn'),
];
next();
}
function setup_choose_card() {
console.log('setup_choose_card');
const player_order = get_player_order();
- game.engine = player_order.map((faction_id) => ({
- t: leaf_node,
- p: faction_id,
- s: 'choose_card',
- }));
- game.engine.push({
- t: function_node,
- f: 'setup_player_turn',
- });
+ game.engine = player_order.map((faction_id) => create_leaf_node('choose_card', faction_id));
+ game.engine.push(create_function_node('setup_player_turn'));
next();
}
function setup_player_turn() {
console.log('setup_player_turn');
const player_order = get_player_order();
- game.engine = player_order.map((faction_id) => ({
- t: seq_node,
- c: [
- {
- t: leaf_node,
- s: 'player_turn',
- p: faction_id,
- },
- ],
- }));
- game.engine.push({
- t: function_node,
- f: 'resolve_fascist_test',
- });
- game.engine.push({
- t: function_node,
- f: 'setup_bag_of_glory',
- });
+ game.engine = player_order.map((faction_id) => create_seq_node([create_leaf_node('player_turn', faction_id)]));
+ game.engine.push(create_function_node('resolve_fascist_test'));
+ game.engine.push(create_function_node('setup_bag_of_glory'));
next();
}
const engine_functions = {
+ check_activate_icon,
end_of_turn,
end_of_year,
setup_bag_of_glory,
@@ -224,8 +226,10 @@ function game_view(state, player) {
fronts: game.fronts,
hand: game.hands[faction_id],
medaillons: game.medaillons,
- selected_card: game.cards_in_play[faction_id],
+ selected_card: game.chosen_cards[faction_id],
+ tableaus: game.tableaus,
tracks: game.tracks,
+ triggered_track_effects: game.triggered_track_effects,
};
if (player !== game.active) {
let inactive = states[game.state].inactive || game.state;
@@ -278,7 +282,7 @@ function setup(seed, _scenario, _options) {
[MODERATES_ID]: 0,
pool: 14,
},
- cards_in_play: {
+ chosen_cards: {
[ANARCHISTS_ID]: null,
[COMMUNISTS_ID]: null,
[MODERATES_ID]: null,
@@ -297,6 +301,11 @@ function setup(seed, _scenario, _options) {
[MODERATES_ID]: [],
},
tracks: [5, 5, 6, 3, 3],
+ trash: {
+ [ANARCHISTS_ID]: [],
+ [COMMUNISTS_ID]: [],
+ [MODERATES_ID]: [],
+ },
triggered_track_effects: [[], [], [], [], []],
log: [],
undo: [],
@@ -335,6 +344,12 @@ function start_turn() {
});
next();
}
+states.activate_icon = {
+ inactive: 'activate an icon',
+ prompt() {
+ view.prompt = 'Choose an icon to activate';
+ }
+};
states.add_glory = {
inactive: 'add tokens to the Bag of Glory',
prompt() {
@@ -357,25 +372,43 @@ states.add_glory = {
resolve_active_and_proceed();
},
};
+states.add_to_front = {
+ inactive: 'add strength to a Front',
+ prompt() {
+ const args = get_active_node_args();
+ const possible_fronts = get_fronts_to_add_to(args.t);
+ view.prompt =
+ possible_fronts.length === 1
+ ? `Add strength to ${front_names[possible_fronts[0]]}`
+ : 'Add strength to a Front';
+ for (let f of possible_fronts) {
+ gen_action_front(f);
+ }
+ },
+ front(f) {
+ const value = get_active_node_args().v;
+ update_front(f, value);
+ resolve_active_and_proceed();
+ },
+};
states.attack_front = {
inactive: 'attack a Front',
prompt() {
const node = get_active_node();
- const front = node.a.f;
+ const front = node.a.t;
view.prompt = 'Attack ' + front_names[front];
if (front === 'd' || front === 'v') {
const fronts = get_fronts_closest_to(front);
fronts.forEach((id) => gen_action('front', id));
}
else {
- gen_action('front', front);
+ gen_action_front(front);
}
},
front(f) {
const node = get_active_node();
const value = node.a.v;
- game.fronts[f] += value;
- log_h3(`${Math.abs(value)} attacks added to ${front_names[f]}`);
+ update_front(f, value);
resolve_active_and_proceed();
},
};
@@ -383,9 +416,18 @@ states.choose_area_ap = {
inactive: 'choose area to use Action Points',
prompt() {
view.prompt = 'Choose area of the board to affect';
- for (let track of tracks) {
+ for (const track of tracks) {
gen_action_standee(track.id);
}
+ const fronts = get_fronts_to_add_to(data_1.ANY);
+ for (const front of fronts) {
+ gen_action_front(front);
+ }
+ },
+ front(f) {
+ const s = get_active_node_args().strength;
+ update_front(f, s);
+ resolve_active_and_proceed();
},
standee(track_id) {
console.log('standee', track_id);
@@ -401,22 +443,36 @@ states.choose_area_ap = {
resolve_active_and_proceed();
},
};
-states.move_track_up_or_down = {
- inactive: 'move a track',
+states.change_bonus = {
+ inactive: 'gain select Bonus',
prompt() {
- const node = get_active_node();
- view.prompt = `Move ${get_track_name(node.a.track_id)} up or down`;
- gen_action('up');
- gen_action('down');
+ const args = get_active_node_args();
+ if ((args.v === data_1.ON &&
+ game.bonuses[data_1.TEAMWORK_BONUS] === data_1.ON &&
+ game.bonuses[data_1.MORALE_BONUS] === data_1.ON) ||
+ (args.v === data_1.OFF && game.bonuses[args.t] === data_1.OFF)) {
+ gen_action('skip');
+ }
+ if (args.t === data_1.ANY && args.v === data_1.ON) {
+ view.prompt = 'Turn on a Bonus';
+ for (const bonus of bonuses) {
+ if (game.bonuses[bonus] === data_1.OFF) {
+ gen_action_bonus(bonus);
+ }
+ }
+ }
+ else if (args.v === data_1.OFF) {
+ view.prompt = `Turn off ${bonus_names[args.t]}`;
+ gen_action_bonus(args.t);
+ }
},
- down() {
- const node = get_active_node();
- move_track(node.a.track_id, -1 * node.a.strength);
+ bonus(b) {
+ const value = get_active_node_args().v;
+ game.bonuses[b] = value;
+ log(`${bonus_names[b]} ${value === data_1.ON ? 'on' : 'off'}`);
resolve_active_and_proceed();
},
- up() {
- const node = get_active_node();
- move_track(node.a.track_id, node.a.strength);
+ skip() {
resolve_active_and_proceed();
},
};
@@ -429,18 +485,53 @@ states.choose_card = {
gen_action_card(c);
},
card(c) {
- log_h3(`${game.active} chooses a card`);
- if (!game.cards_in_play) {
- game.cards_in_play = {};
+ game.chosen_cards[player_faction_map[game.active]] = c;
+ resolve_active_and_proceed();
+ },
+};
+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');
+ },
+ gain_hp() {
+ const value = get_active_node_args().v;
+ if (game.hero_points.pool > value) {
+ game.hero_points.pool -= value;
+ game.hero_points[get_active_faction()] += value;
}
- game.cards_in_play[player_faction_map[game.active]] = c;
resolve_active_and_proceed();
},
};
states.lose_hero_points = {
inactive: 'choose a Player',
prompt() {
- view.prompt = 'Lose Hero Points';
+ const args = get_active_node_args();
+ view.prompt = 'Choose player to lose Hero Points';
+ if (args.t === data_1.PLAYER_WITH_MOST_HERO_POINTS) {
+ const factions = get_factions_with_most_hero_poins();
+ console.log('faction', factions);
+ for (let faction_id of factions) {
+ gen_action(faction_player_map[faction_id]);
+ }
+ }
+ },
+ Anarchist() {
+ const value = get_active_node_args().v;
+ lose_hero_point(ANARCHISTS_ID, value);
+ resolve_active_and_proceed();
+ },
+ Communist() {
+ const value = get_active_node_args().v;
+ lose_hero_point(ANARCHISTS_ID, value);
+ resolve_active_and_proceed();
+ },
+ Moderate() {
+ const value = get_active_node_args().v;
+ lose_hero_point(ANARCHISTS_ID, value);
+ resolve_active_and_proceed();
},
};
states.move_track = {
@@ -459,23 +550,46 @@ states.move_track = {
resolve_active_and_proceed();
},
};
+states.move_track_up_or_down = {
+ inactive: 'move a track',
+ prompt() {
+ const node = get_active_node();
+ view.prompt = `Move ${get_track_name(node.a.track_id)} up or down`;
+ gen_action('up');
+ gen_action('down');
+ },
+ down() {
+ const node = get_active_node();
+ move_track(node.a.track_id, -1 * node.a.strength);
+ resolve_active_and_proceed();
+ },
+ up() {
+ const node = get_active_node();
+ move_track(node.a.track_id, node.a.strength);
+ resolve_active_and_proceed();
+ },
+};
states.player_turn = {
inactive: 'play their turn',
prompt() {
const faction_id = get_faction_id(game.active);
- const hero_points = game.hero_points[faction_id];
- view.prompt =
- hero_points === 0
- ? 'Play your card'
- : 'Play your card or spend Hero points';
- if (game.cards_in_play[faction_id] !== null) {
+ const can_spend_hp = game.hero_points[faction_id] > 0;
+ const can_play_card = game.hands[faction_id].includes(game.chosen_cards[faction_id]);
+ view.prompt = 'Play a card or spend Hero points';
+ if (!(can_play_card || can_spend_hp)) {
+ view.prompt = 'End turn';
+ }
+ else if (!can_play_card && can_spend_hp) {
+ view.prompt = 'Spend Hero Points or end turn';
+ }
+ if (can_play_card) {
gen_action('play_for_ap');
gen_action('play_for_event');
}
else {
gen_action('done');
}
- if (hero_points > 0) {
+ if (can_spend_hp) {
gen_action('spend_hp');
}
},
@@ -484,34 +598,25 @@ states.player_turn = {
},
play_for_ap() {
const faction_id = get_faction_id(game.active);
- const card = game.cards_in_play[faction_id];
+ const card = game.chosen_cards[faction_id];
log_h3(`${game.active} plays ${cards[card].title} for the Action Points`);
- if (!game.discard) {
- game.discard = {
- [ANARCHISTS_ID]: [],
- [COMMUNISTS_ID]: [],
- [MODERATES_ID]: [],
- f: [],
- };
- }
array_remove(game.hands[faction_id], game.hands[faction_id].indexOf(card));
- game.discard[faction_id].push(card);
- insert_before_active_node({
- t: leaf_node,
- p: faction_id,
- s: 'choose_area_ap',
- a: {
+ game.tableaus[faction_id].push(card);
+ insert_before_active_node(create_seq_node([
+ create_leaf_node('choose_area_ap', faction_id, {
strength: cards[card].strength,
- },
- });
+ }),
+ create_function_node('check_activate_icon'),
+ ]));
next();
},
play_for_event() {
const faction_id = get_faction_id(game.active);
- const card = game.cards_in_play[faction_id];
+ const card = game.chosen_cards[faction_id];
+ array_remove(game.hands[faction_id], game.hands[faction_id].indexOf(card));
+ game.trash[faction_id].push(card);
log_h3(`${game.active} plays ${cards[card].title} for the Event`);
- game.cards_in_play[faction_id] = null;
- game.tableaus[faction_id].push(card);
+ insert_after_active_node(create_effects_node(cards[card].effects));
resolve_active_and_proceed();
},
spend_hp() {
@@ -522,52 +627,6 @@ states.player_turn = {
});
next();
},
- card(c) {
- const faction = get_active_faction();
- log_h3(`${game.active} plays ${cards[c].title} to their tableau`);
- if (!game.tableaus) {
- game.tableaus = {
- [ANARCHISTS_ID]: [],
- [COMMUNISTS_ID]: [],
- [MODERATES_ID]: [],
- };
- }
- game.cards_in_play[faction] = null;
- game.tableaus[faction].push(c);
- array_remove(game.hands[faction], game.hands[faction].indexOf(c));
- resolve_active_and_proceed();
- },
-};
-states.resolve_event = {
- inactive: 'resolve Fascist Event',
- prompt() {
- const card = get_current_event();
- const node = get_active_node(game.engine);
- const effect = card.effects[node.a];
- view.prompt = get_event_prompt(effect);
- if (effect.type === 'track') {
- gen_action('standee', effect.target);
- }
- else if (effect.type === 'hero_points' &&
- effect.target === data_1.PLAYER_WITH_MOST_HERO_POINTS) {
- const factions = get_factions_with_most_hero_poins();
- for (let faction_id of factions) {
- gen_action(get_player(faction_id));
- }
- }
- },
- Anarchist() {
- lose_hero_point(ANARCHISTS_ID, 1);
- resolve_active_and_proceed();
- },
- Communist() {
- lose_hero_point(ANARCHISTS_ID, 1);
- resolve_active_and_proceed();
- },
- Moderate() {
- lose_hero_point(ANARCHISTS_ID, 1);
- resolve_active_and_proceed();
- },
};
states.spend_hero_points = {
inactive: 'spend Hero points',
@@ -595,6 +654,12 @@ function pop_undo() {
game.log = save_log;
game.undo = save_undo;
}
+function check_activate_icon() {
+ if (game.bonuses[data_1.MORALE_BONUS] === data_1.ON) {
+ insert_after_active_node(create_leaf_node('activate_icon', get_active_faction_id()));
+ }
+ resolve_active_and_proceed();
+}
function end_of_turn() {
log_h2('End of turn');
if (game.turn === 4) {
@@ -611,13 +676,25 @@ function resolve_fascist_test() {
log_h2('Fascist test is resolved');
next();
}
+function get_fronts_to_add_to(target) {
+ console.log('get_fronts_to_add_to', target);
+ if (target === data_1.CLOSEST_TO_DEFEAT || target === data_1.CLOSEST_TO_VICTORY) {
+ return get_fronts_closest_to(target);
+ }
+ else if (target === data_1.ANY) {
+ return ['a', 'm', 'n', 's'];
+ }
+ else {
+ return [target];
+ }
+}
function move_track(track_id, change) {
const current_value = game.tracks[track_id];
let new_value = current_value + change;
new_value = Math.max(new_value, track_id === data_1.GOVERNMENT ? 1 : 0);
new_value = Math.min(new_value, 10);
game.tracks[track_id] = new_value;
- log(`${game.active} moves ${get_track_name(track_id)} to ${new_value}`);
+ log(`${get_track_name(track_id)} to ${new_value}`);
const triggered_spaces = change > 0
? make_list(current_value + 1, new_value).reverse()
: make_list(new_value, current_value - 1);
@@ -635,42 +712,46 @@ function move_track(track_id, change) {
}
});
}
+function update_front(f, change) {
+ game.fronts[f] += change;
+ log(`${front_names[f]}: ${change > 0 ? '+' : ''}${change}`);
+}
+function create_effects_node(effects) {
+ const nodes = effects.reduce((accrued, current) => {
+ const node = resolve_effect(current);
+ if (node !== null) {
+ accrued.push(node);
+ }
+ return accrued;
+ }, []);
+ return {
+ t: seq_node,
+ c: nodes,
+ };
+}
+const effect_type_state_map = {
+ attack: 'attack_front',
+ bonus: 'change_bonus',
+ front: 'add_to_front',
+ track: 'move_track',
+};
function resolve_effect(effect, faction = get_active_faction()) {
- if (effect.type === 'attack') {
- return {
- t: leaf_node,
- p: faction,
- s: 'attack_front',
- a: {
- f: effect.target,
- v: effect.value,
- },
- };
- }
- if (effect.type === 'track') {
- return {
- t: leaf_node,
- p: faction,
- s: 'move_track',
- a: {
- t: effect.target,
- v: effect.value,
- },
- };
+ const args = {
+ t: effect.target,
+ v: effect.value,
+ };
+ let state = effect_type_state_map[effect.type];
+ if (state !== undefined) {
+ return create_leaf_node(state, faction, args);
}
if (effect.type === 'hero_points' &&
effect.target === data_1.PLAYER_WITH_MOST_HERO_POINTS) {
- return {
- t: leaf_node,
- p: faction,
- s: 'lose_hero_points',
- a: {
- p: effect.target,
- v: effect.value,
- },
- };
+ state = 'lose_hero_points';
}
- return null;
+ if (effect.type === 'hero_points' && effect.target === data_1.SELF) {
+ state = 'gain_hero_points';
+ }
+ return state === undefined ? null : create_leaf_node(state, faction, args);
}
function draw_card(deck) {
clear_undo();
@@ -686,33 +767,19 @@ function draw_item(ids) {
return r;
}
function lose_hero_point(faction, value) {
- const points_lost = Math.min(game.hero_points[faction], value);
+ const points_lost = Math.min(game.hero_points[faction], Math.abs(value));
game.hero_points.pool += points_lost;
game.hero_points[faction] -= points_lost;
+ if (points_lost === 0) {
+ return;
+ }
if (points_lost === 1) {
- log(`${get_player(faction)} loses 1 Hero Point`);
+ log(`${get_player(faction)}: -1 Hero Point`);
}
else {
- log(`${get_player(faction)} loses ${points_lost} Hero Points`);
+ log(`${get_player(faction)}: -${points_lost} Hero Points`);
}
}
-function get_current_event() {
- return cards[game.current_events[game.current_events.length - 1]];
-}
-function get_event_prompt(effect) {
- let prompt = '';
- switch (effect.type) {
- case 'attack':
- return 'Attack ' + front_names[effect.target];
- case 'bonus':
- break;
- case 'hero_points':
- return 'Select player with most Hero points';
- case 'track':
- return 'Decrease ' + tracks[effect.target].name;
- }
- return prompt;
-}
function get_fronts_closest_to(target) {
const values = Object.values(game.fronts);
const targetValue = target === 'd' ? Math.min(...values) : Math.max(...values);
@@ -783,11 +850,18 @@ function get_next_faction(faction_id) {
return role_ids[index + 1];
}
function get_factions_with_most_hero_poins() {
- const most_hero_points = Math.max(...Object.values(game.hero_points));
- const faction_ids = [];
- Object.entries(game.hero_points).forEach(([faction, hero_points]) => {
- if (hero_points === most_hero_points) {
- faction_ids.push(faction);
+ let most_hero_points = null;
+ let faction_ids = [];
+ Object.entries(game.hero_points).forEach(([id, value]) => {
+ if (id === 'pool') {
+ return;
+ }
+ if (most_hero_points === null || value > most_hero_points) {
+ most_hero_points = value;
+ faction_ids = [id];
+ }
+ else if (most_hero_points === value) {
+ faction_ids.push(id);
}
});
return faction_ids;