diff options
Diffstat (limited to 'rules.js')
-rw-r--r-- | rules.js | 464 |
1 files changed, 269 insertions, 195 deletions
@@ -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; |