summaryrefslogtreecommitdiff
path: root/rules.js
diff options
context:
space:
mode:
Diffstat (limited to 'rules.js')
-rw-r--r--rules.js230
1 files changed, 199 insertions, 31 deletions
diff --git a/rules.js b/rules.js
index 5974267..b7f9196 100644
--- a/rules.js
+++ b/rules.js
@@ -112,6 +112,12 @@ function create_seq_node(children) {
c: children,
};
}
+function checkpoint() {
+ if (game.undo.length > 0) {
+ insert_after_active_node(create_leaf_node('confirm_turn', get_active_faction()));
+ }
+ resolve_active_and_proceed();
+}
function setup_bag_of_glory() {
game.engine = [
create_leaf_node('add_glory', game.initiative),
@@ -129,6 +135,7 @@ function setup_final_bid() {
log_h1('Final Bid');
const player_order = get_player_order();
game.engine = player_order.map((faction_id) => create_leaf_node('choose_final_bid', faction_id));
+ game.engine.push(create_function_node('checkpoint'));
game.engine.push(create_function_node('resolve_final_bid'));
game.engine.push(create_function_node('setup_choose_card'));
next();
@@ -152,6 +159,7 @@ function start_of_player_turn() {
}
const engine_functions = {
check_activate_icon,
+ checkpoint,
end_of_turn,
setup_bag_of_glory,
setup_choose_card,
@@ -178,6 +186,7 @@ const engine_functions = {
card50_event2,
card53_event2,
card54_event1,
+ setup_return_card_from_trash,
};
function get_active(engine) {
for (let i of engine) {
@@ -204,6 +213,15 @@ function get_active_node_args() {
}
return null;
}
+function update_active_node_args(args) {
+ const node = get_active_node(game.engine);
+ if (node.t === leaf_node || node.t === function_node) {
+ node.a = {
+ ...node.a,
+ ...args,
+ };
+ }
+}
function insert_before_or_after_active_node(node, position, engine = game.engine) {
const a = get_active(engine);
if (a === null) {
@@ -278,7 +296,6 @@ function game_view(state, player) {
tableaus: game.tableaus,
tracks: game.tracks,
triggered_track_effects: game.triggered_track_effects,
- undo: game.undo,
used_medallions: game.used_medallions,
year: game.year,
};
@@ -568,9 +585,13 @@ 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()]) {
+ const faction = get_active_faction();
+ for (const c of game.hands[faction]) {
gen_action_card(c);
}
+ if (game.hands[faction].length === 0) {
+ gen_action('skip');
+ }
},
card(c) {
const faction_id = get_active_faction();
@@ -580,6 +601,9 @@ states.add_card_to_tableau = {
logi(`${faction_player_map[faction_id]} adds ${card.title} to their tableau`);
resolve_active_and_proceed();
},
+ skip() {
+ resolve_active_and_proceed();
+ },
};
states.add_glory = {
inactive: 'add tokens to the Bag of Glory',
@@ -618,26 +642,28 @@ states.add_to_front = {
states.attack_front = {
inactive: 'attack a Front',
prompt() {
- const node = get_active_node();
- const front = node.a.t;
- let targets = [];
- if (front === 'd' || front === 'v') {
- targets = get_fronts_closest_to(front);
+ const { t: target, n } = get_active_node_args();
+ let fronts = [];
+ if (target === data_1.ANY) {
+ fronts = get_fronts_to_add_to(data_1.ANY, n);
}
- else if (game.fronts[front].status === data_1.DEFEAT) {
- targets = get_fronts_closest_to('d');
+ else if (target === 'd' || target === 'v') {
+ fronts = get_fronts_closest_to(target);
}
- else if (game.fronts[front].status === data_1.VICTORY) {
- targets = get_fronts_to_add_to(data_1.ANY);
+ else if (game.fronts[target].status === data_1.DEFEAT) {
+ fronts = get_fronts_closest_to('d');
+ }
+ else if (game.fronts[target].status === data_1.VICTORY) {
+ fronts = get_fronts_to_add_to(data_1.ANY);
}
else {
- targets.push(front);
+ fronts.push(target);
}
view.prompt =
- targets.length === 1
- ? `Attack ${front_names[targets[0]]}`
+ fronts.length === 1
+ ? `Attack ${front_names[fronts[0]]}`
: 'Attack a front';
- targets.forEach((id) => gen_action('front', id));
+ fronts.forEach((id) => gen_action('front', id));
},
front(f) {
const node = get_active_node();
@@ -712,6 +738,11 @@ states.choose_area_ap = {
},
bonus(b) {
update_bonus(b, data_1.ON);
+ const s = get_active_node_args().strength;
+ const other_bonus = b === data_1.TEAMWORK_BONUS ? data_1.MORALE_BONUS : data_1.TEAMWORK_BONUS;
+ if (s > 1 && game.bonuses[other_bonus] === data_1.OFF) {
+ insert_after_active_node(resolve_effect((0, data_1.create_effect)('bonus', other_bonus, data_1.ON)));
+ }
resolve_active_and_proceed();
},
front(f) {
@@ -733,7 +764,7 @@ states.choose_area_ap = {
},
};
states.change_bonus = {
- inactive: 'gain select Bonus',
+ inactive: 'select Bonus',
prompt() {
const args = get_active_node_args();
if ((args.v === data_1.ON &&
@@ -750,8 +781,8 @@ states.change_bonus = {
}
}
}
- else if (args.v === data_1.OFF) {
- view.prompt = `Turn off ${bonus_names[args.t]}`;
+ else {
+ view.prompt = `Turn ${args.v === data_1.OFF ? 'off' : 'on'} ${bonus_names[args.t]}`;
gen_action_bonus(args.t);
}
},
@@ -797,15 +828,20 @@ states.choose_final_bid = {
card(c) {
const faction = get_active_faction();
game.selected_cards[faction].push(c);
- if (game.selected_cards[faction].length < 3) {
- next();
+ const number_selected = game.selected_cards[faction].length;
+ console.log('number selected', number_selected);
+ const number_hand = game.hands[faction].length;
+ if (number_selected === 3 ||
+ (number_hand < 4 && number_selected === number_hand - 1)) {
+ console.log('proceed');
+ resolve_active_and_proceed();
}
else {
- resolve_active_and_proceed();
+ next();
}
},
done() {
- resolve_active_and_proceed();
+ resolve_active_and_proceed(true);
},
};
states.choose_medallion = {
@@ -911,6 +947,14 @@ states.gain_hero_points = {
inactive: 'gain Hero Points',
prompt() {
const value = get_active_node_args().v;
+ if (value < 0) {
+ view.prompt =
+ value < -1
+ ? `Lose ${Math.abs(value)} Hero Points`
+ : 'Lose 1 Hero Point';
+ gen_action('lose_hp');
+ return;
+ }
if (game.hero_points.pool > 0) {
view.prompt =
value > 1 ? `Gain ${value} Hero Points` : 'Gain 1 Hero Point';
@@ -926,6 +970,11 @@ states.gain_hero_points = {
gain_hero_points(get_active_faction(), value);
resolve_active_and_proceed();
},
+ lose_hp() {
+ const value = get_active_node_args().v;
+ lose_hero_point(get_active_faction(), value);
+ resolve_active_and_proceed();
+ },
skip() {
resolve_active_and_proceed();
},
@@ -1096,7 +1145,7 @@ states.player_turn = {
},
play_for_event() {
const faction = get_active_faction();
- const { effects } = play_card(faction, 'play_for_ap');
+ const { effects } = play_card(faction, 'play_for_event');
insert_before_active_node(create_effects_node(effects));
next();
},
@@ -1128,13 +1177,82 @@ states.remove_blank_marker = {
states.remove_attack_from_fronts = {
inactive: 'remove attacks',
prompt() {
- view.prompt = 'Choose a front to remove an attack';
+ const { f, v: card_id } = get_active_node_args();
+ view.prompt =
+ card_id === 6
+ ? 'Choose a Front to remove an attack from'
+ : 'Choose a Front to remove attacks from';
+ const front_data = f ?? {};
+ let is_front_with_attacks = false;
+ data_1.FRONTS.forEach((id) => {
+ if (game.fronts[id].value >= 0 || game.fronts[id].status !== null) {
+ return;
+ }
+ if (card_id === 6 && front_data[id]) {
+ return;
+ }
+ is_front_with_attacks = true;
+ gen_action_front(id);
+ });
+ if (!is_front_with_attacks) {
+ view.prompt = 'No valid Front to choose. You must skip';
+ gen_action('skip');
+ }
+ },
+ front(id) {
+ const { f, v: card_id } = get_active_node_args();
+ const removed_value = card_id === 6 ? 1 : Math.min(3, Math.abs(game.fronts[id].value));
+ update_front(id, removed_value);
+ const fronts = f ?? {};
+ fronts[id] = removed_value;
+ update_active_node_args({ f: fronts });
+ if (card_id === 6 && Object.keys(fronts).length === 3) {
+ resolve_active_and_proceed();
+ }
+ else if (card_id === 39 || card_id === 16) {
+ insert_after_active_node(create_leaf_node('attack_front', get_active_faction(), {
+ t: data_1.ANY,
+ v: card_id === 39 ? -2 : -1 * removed_value,
+ n: card_id === 16 ? id : undefined,
+ }));
+ resolve_active_and_proceed();
+ }
+ },
+ skip() {
+ const { f, v: card_id } = get_active_node_args();
+ const values = Object.values(f ?? {});
+ if (card_id === 39 && values.length > 0) {
+ insert_after_active_node(create_leaf_node('attack_front', get_active_faction(), {
+ t: data_1.ANY,
+ v: -2,
+ }));
+ }
+ resolve_active_and_proceed();
},
};
states.return_card = {
- inactive: 'return a card',
+ inactive: 'return a card to their hand',
prompt() {
- view.prompt = 'Choose a card to return';
+ view.prompt = 'Choose a card to return to your hand';
+ if (game.selectable_cards.length === 0) {
+ view.prompt = 'No card in trash to return. You must skip';
+ gen_action('skip');
+ }
+ for (const c of game.selectable_cards) {
+ gen_action_card(c);
+ }
+ },
+ card(c) {
+ const faction = get_active_faction();
+ array_remove(game.trash[faction], game.trash[faction].indexOf(c));
+ game.hands[faction].push(c);
+ logi(`${faction_player_map[faction]} returns a card to their hand`);
+ game.selectable_cards = [];
+ resolve_active_and_proceed();
+ },
+ skip() {
+ game.selectable_cards = [];
+ resolve_active_and_proceed();
},
};
states.spend_hero_points = {
@@ -1235,12 +1353,52 @@ states.spend_hero_points = {
states.swap_card_tableau_hand = {
inactive: 'swap cards',
prompt() {
- view.prompt = 'Choose cards';
+ view.prompt =
+ 'Choose a card in your tableau and a card in your hand to swap';
const faction = get_active_faction();
- for (const c of game.hands[faction]) {
- gen_action_card(c);
+ gen_action('skip');
+ if (game.tableaus[faction].length === 0) {
+ view.prompt = 'No card in your tableau to swap. You must skip';
+ return;
+ }
+ if (game.hands[faction].length === 0) {
+ view.prompt = 'No card in your hand to swap. You must skip';
+ return;
+ }
+ if (!game.selected_cards[faction].some((card_id) => game.hands[faction].includes(card_id))) {
+ for (const c of game.hands[faction]) {
+ gen_action_card(c);
+ }
+ }
+ if (!game.selected_cards[faction].some((card_id) => game.tableaus[faction].includes(card_id))) {
+ for (const c of game.tableaus[faction]) {
+ gen_action_card(c);
+ }
+ }
+ },
+ card(c) {
+ console.log('card', c);
+ const faction = get_active_faction();
+ const selected_cards = game.selected_cards[faction];
+ selected_cards.push(c);
+ if (selected_cards.length === 2) {
+ const hand_card_index = selected_cards.findIndex((card_id) => game.hands[faction].includes(card_id));
+ const tableau_card_index = hand_card_index === 0 ? 1 : 0;
+ const hand = game.hands[faction];
+ const tableau = game.tableaus[faction];
+ array_remove(hand, hand.indexOf(selected_cards[hand_card_index]));
+ array_remove(tableau, tableau.indexOf(selected_cards[tableau_card_index]));
+ hand.push(selected_cards[tableau_card_index]);
+ tableau.push(selected_cards[hand_card_index]);
+ game.selected_cards[faction] = [];
+ resolve_active_and_proceed();
}
},
+ skip() {
+ const faction = get_active_faction();
+ game.selected_cards[faction] = [];
+ resolve_active_and_proceed();
+ },
};
function resolve_take_hero_points(faction) {
const { v } = get_active_node_args();
@@ -1365,6 +1523,7 @@ function card23_event1() {
}
function card26_event1() {
game.active_abilities.push(data_1.COMMUNIST_EXTRA_HERO_POINT);
+ resolve_active_and_proceed();
}
function card29_event2() {
const value = game.tracks[data_1.GOVERNMENT] <= 5 ? -3 : -2;
@@ -1378,6 +1537,7 @@ function card35_event2() {
}
function card42_event3() {
game.active_abilities.push(data_1.ANARCHIST_EXTRA_HERO_POINT);
+ resolve_active_and_proceed();
}
function card45_event2() {
if (game.tracks[data_1.LIBERTY] >= 6) {
@@ -1406,6 +1566,11 @@ function card54_event1() {
insert_after_active_node(resolve_effect((0, data_1.create_effect)('track', data_1.LIBERTY, value)));
resolve_active_and_proceed();
}
+function setup_return_card_from_trash() {
+ const faction = get_active_faction();
+ game.selectable_cards = game.trash[faction].filter((c) => c !== game.played_card);
+ resolve_active_and_proceed();
+}
function add_glory(faction, amount, indent = false) {
for (let i = 0; i < amount; ++i) {
game.bag_of_glory.push(get_active_faction());
@@ -1505,7 +1670,9 @@ function end_of_year() {
log('The war is won!');
}
else {
+ console.log('The war is lost');
game_over('None', 'The war is lost. All Players lose the game!');
+ resolve_active_and_proceed();
return;
}
}
@@ -1560,6 +1727,7 @@ function game_over(result, victory) {
insert_after_active_node(create_leaf_node('game_over', 'None'));
game.result = result;
game.victory = victory;
+ game.undo = [];
log_br();
log(game.victory);
}
@@ -1644,12 +1812,12 @@ function resolve_final_bid() {
}
resolve_active_and_proceed();
}
-function get_fronts_to_add_to(target) {
+function get_fronts_to_add_to(target, not = []) {
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 data_1.FRONTS.filter((id) => game.fronts[id].status === null);
+ return data_1.FRONTS.filter((id) => game.fronts[id].status === null && !not.includes(id));
}
else {
return [target];