summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data.js4
-rw-r--r--data.ts4
-rw-r--r--play.css11
-rw-r--r--play.html4
-rw-r--r--play.js21
-rw-r--r--rules.js379
-rw-r--r--rules.ts415
-rw-r--r--types.d.ts3
8 files changed, 497 insertions, 344 deletions
diff --git a/data.js b/data.js
index eaebba1..3764c25 100644
--- a/data.js
+++ b/data.js
@@ -1784,12 +1784,12 @@ const data = {
{
id: PROPAGANDA_MEDALLION_ID,
name: 'Propaganda',
- tooltip: '+1 card when drawing from your deck & keep +1 in hand at End of the Year.'
+ tooltip: "Gain 2 extra Hero points form each Successful Test (even if you didn't support)."
},
{
id: INTELLIGENCE_MEDALLION_ID,
name: 'Intelligence',
- tooltip: "Gain 2 extra Hero points form each Successful Test (even if you didn't support)."
+ tooltip: '+1 card when drawing from your deck & keep +1 in hand at End of the Year.'
},
{
id: VOLUNTEERS_MEDALLION_ID,
diff --git a/data.ts b/data.ts
index de4774f..1a2d6c9 100644
--- a/data.ts
+++ b/data.ts
@@ -1857,12 +1857,12 @@ const data: StaticData = {
{
id: PROPAGANDA_MEDALLION_ID,
name: 'Propaganda',
- tooltip: '+1 card when drawing from your deck & keep +1 in hand at End of the Year.'
+ tooltip: "Gain 2 extra Hero points form each Successful Test (even if you didn't support)."
},
{
id: INTELLIGENCE_MEDALLION_ID,
name: 'Intelligence',
- tooltip: "Gain 2 extra Hero points form each Successful Test (even if you didn't support)."
+ tooltip: '+1 card when drawing from your deck & keep +1 in hand at End of the Year.'
},
{
id: VOLUNTEERS_MEDALLION_ID,
diff --git a/play.css b/play.css
index 2d668a9..7f32d5c 100644
--- a/play.css
+++ b/play.css
@@ -166,6 +166,17 @@ body.Observer #toggle_trash {
/* CONTAINERS */
+#hero_point_pool {
+ position: absolute;
+ top: 150px;
+ left: 26px;
+ width: 60px;
+}
+
+#hero_point_pool .token {
+ margin-bottom: -30px;
+}
+
.front_container {
position: absolute;
display: flex;
diff --git a/play.html b/play.html
index 3d94fd1..19634c8 100644
--- a/play.html
+++ b/play.html
@@ -44,8 +44,8 @@
<main>
<div id="mapwrap">
<div id="map">
- <div id="pieces">
- </div>
+ <div id="hero_point_pool"></div>
+ <div id="pieces"></div>
</div>
</div>
diff --git a/play.js b/play.js
index 91ef6c4..a7d8ce7 100644
--- a/play.js
+++ b/play.js
@@ -2,9 +2,6 @@
/* global view, action_button, send_action */
-// TODO: pool for hero points to animate ?
-// TODO: pool for faction markers to animate ?
-
const ui = {
header: document.querySelector("header"),
status: document.getElementById("status"),
@@ -37,6 +34,7 @@ const ui = {
document.getElementById("tokens_c"),
document.getElementById("tokens_m"),
],
+ hero_point_pool: document.getElementById("hero_point_pool"),
bag_of_glory: document.getElementById("bag_of_glory"),
// spaces
@@ -275,6 +273,13 @@ function on_init() {
return
on_init_once = true
+ let favicon = document.querySelector('link[rel="icon"]')
+ switch (params.role) {
+ case "Anarchist": favicon.href = "images/tokens150/player_anarchist.png"; break
+ case "Communist": favicon.href = "images/tokens150/player_communist.png"; break
+ case "Moderate": favicon.href = "images/tokens150/player_moderate.png"; break
+ }
+
ui.roles_list = document.getElementById("roles"),
ui.roles = [
document.getElementById("role_Anarchist"),
@@ -550,6 +555,9 @@ function on_update() { // eslint-disable-line no-unused-vars
update_hero_points(ui.tokens[1], view.hero_points[1])
update_hero_points(ui.tokens[2], view.hero_points[2])
+ ui.hero_point_pool.replaceChildren()
+ update_hero_points(ui.hero_point_pool, view.hero_points[3])
+
update_front(ui.status_fronts[0], ui.str_fronts[0], ui.con_fronts[0], view.fronts[0], "a")
update_front(ui.status_fronts[1], ui.str_fronts[1], ui.con_fronts[1], view.fronts[1], "m")
update_front(ui.status_fronts[2], ui.str_fronts[2], ui.con_fronts[2], view.fronts[2], "n")
@@ -577,8 +585,10 @@ function on_update() { // eslint-disable-line no-unused-vars
action_button("Communist", "Communist")
action_button("Moderate", "Moderate")
+ action_button("archives", "Archives")
+ action_button("volunteers", "Volunteers")
+
action_button("add_glory", "Add to Bag of Glory")
- action_button("add_to_front", "+1 to a Front")
action_button("draw_card", "Draw a Card")
action_button("draw_cards", "Draw Cards")
action_button("draw_glory", "Draw from Bag of Glory")
@@ -586,7 +596,6 @@ function on_update() { // eslint-disable-line no-unused-vars
action_button("lose_hp", "Lose Hero Points")
action_button("play_to_tableau", "Action Points")
action_button("play_for_event", "Event")
- action_button("remove_blank_marker", "Remove Blank marker")
action_button("use_momentum", "Momentum")
action_button("use_ap", "Action Points")
@@ -614,10 +623,12 @@ function on_focus_card_tip(x) {
}
function on_focus_medallion_tip(x) {
+ ui.status.textContent = data.medallions[x].name + ": " + data.medallions[x].tooltip
ui.tooltip.className = "pink token medallion medallion_" + x
}
function on_blur_tip(x) {
+ ui.status.textContent = ""
ui.tooltip.className = "hide"
}
diff --git a/rules.js b/rules.js
index e44b8e0..f08657f 100644
--- a/rules.js
+++ b/rules.js
@@ -76,7 +76,7 @@ function gen_spend_hero_points() {
gen_action('spend_hp');
}
}
-const multiactive_states = ['choose_card', 'end_of_year_discard'];
+const multiactive_states = ['choose_card', 'end_of_year_discard', 'choose_final_bid'];
function action(state, player, action, arg) {
game = state;
if (action !== 'undo' && !multiactive_states.includes(game.state)) {
@@ -140,8 +140,7 @@ function setup_choose_card() {
function setup_final_bid() {
game.fascist = 0;
log_header('Final Bid', 't');
- const player_order = get_player_order();
- game.engine = player_order.map((faction_id) => create_state_node('choose_final_bid', faction_id));
+ game.engine = [create_state_node('choose_final_bid', 'all')];
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'));
@@ -207,7 +206,7 @@ const engine_functions = {
start_year,
resolve_fascist_test,
resolve_final_bid,
- log_trigger,
+ place_blank_marker,
card1_event2,
card3_event2,
card10_event2,
@@ -311,7 +310,7 @@ function next(checkpoint = false) {
const current_active = game.active;
const next_active = get_next_active(node.p);
if (next_active !== current_active && game.undo.length > 0) {
- insert_before_active_node(create_state_node('confirm_turn', get_active_faction()));
+ insert_before_active_node(create_state_node('confirm_turn', get_active_faction(), { f: next_active }));
game.state = 'confirm_turn';
return;
}
@@ -369,7 +368,8 @@ function game_view(state, current) {
}
else if (current !== game.active &&
!game.active.includes(current)) {
- let inactive = states[game.state].inactive || game.state;
+ let src = get_active_node_args()?.src;
+ let inactive = src ? get_source_inactive(src) : states[game.state].inactive || game.state;
view.prompt = Array.isArray(game.active)
? `Waiting for ${game.active.join(' and ')} to ${inactive}.`
: `Waiting for ${game.active} to ${inactive}.`;
@@ -456,6 +456,7 @@ function setup(seed, _scenario, options) {
[],
],
triggered_track_effects: [],
+ untriggered_track_effects: [],
log: [],
undo: [],
used_medallions: [],
@@ -480,6 +481,7 @@ function setup(seed, _scenario, options) {
function draw_hand_cards(faction_id, count, indent = true) {
const deck = list_deck(faction_id);
if (game.medallions[faction_id].includes(data_1.INTELLIGENCE_MEDALLION_ID)) {
+ log(">M" + data_1.INTELLIGENCE_MEDALLION_ID);
count++;
}
let drawn_cards = 0;
@@ -549,7 +551,7 @@ const track_icon_to_track_id_map = {
d_soviet_support: data_1.SOVIET_SUPPORT,
};
states.activate_icon = {
- inactive: 'activate an icon',
+ inactive: 'activate a Morale Bonus icon',
prompt() {
gen_spend_hero_points();
const c = cards[game.played_card];
@@ -623,51 +625,19 @@ states.activate_icon = {
update_front(f, get_icon_count_in_tableau('add_to_front'), get_active_faction());
resolve_active_and_proceed();
},
- tr0(x) {
+ trX(x, track) {
+ let old = game.tracks[track];
+ move_track_to(track, x);
if (can_use_medallion(data_1.ORGANIZATION_MEDALLION_ID)) {
- insert_use_organization_medallion_node(data_1.LIBERTY, x);
- }
- else {
- move_track_to(0, x);
- }
- resolve_active_and_proceed();
- },
- tr1(x) {
- if (can_use_medallion(data_1.ORGANIZATION_MEDALLION_ID)) {
- insert_use_organization_medallion_node(data_1.COLLECTIVIZATION, x);
- }
- else {
- move_track_to(1, x);
- }
- resolve_active_and_proceed();
- },
- tr2(x) {
- if (can_use_medallion(data_1.ORGANIZATION_MEDALLION_ID)) {
- insert_use_organization_medallion_node(data_1.GOVERNMENT, x);
- }
- else {
- move_track_to(2, x);
- }
- resolve_active_and_proceed();
- },
- tr3(x) {
- if (can_use_medallion(data_1.ORGANIZATION_MEDALLION_ID)) {
- insert_use_organization_medallion_node(data_1.SOVIET_SUPPORT, x);
- }
- else {
- move_track_to(3, x);
- }
- resolve_active_and_proceed();
- },
- tr4(x) {
- if (can_use_medallion(data_1.ORGANIZATION_MEDALLION_ID)) {
- insert_use_organization_medallion_node(data_1.FOREIGN_AID, x);
- }
- else {
- move_track_to(4, x);
+ insert_use_organization_medallion_node(track, game.tracks[track] - old);
}
resolve_active_and_proceed();
},
+ tr0(x) { this.trX(x, 0); },
+ tr1(x) { this.trX(x, 1); },
+ tr2(x) { this.trX(x, 2); },
+ tr3(x) { this.trX(x, 3); },
+ tr4(x) { this.trX(x, 4); },
draw_card() {
draw_hand_cards(get_active_faction(), get_icon_count_in_tableau('draw_card'));
resolve_active_and_proceed();
@@ -736,7 +706,7 @@ states.add_to_front = {
const args = get_active_node_args();
const possible_fronts = get_fronts_to_add_to(args.t);
if (possible_fronts.length === 0) {
- view.prompt = 'No valid front to add strength to.';
+ view.prompt = 'Cannot support ' + front_names[args.t] + '.';
gen_action('skip');
}
else if (possible_fronts.length === 4) {
@@ -769,7 +739,7 @@ states.attack_front = {
const possible_fronts = get_fronts_to_add_to(target, n);
const number_of_fronts = possible_fronts.length;
if (number_of_fronts === 0) {
- view.prompt = 'No valid front to attack.';
+ view.prompt = 'Cannot attack ' + front_names[target] + '.';
gen_action('skip');
}
else if (possible_fronts.length === 4) {
@@ -795,7 +765,7 @@ states.attack_front = {
states.break_tie_final_bid = {
inactive: 'break tie for Final Bid',
prompt() {
- view.prompt = 'Choose the winner of the Final Bid';
+ view.prompt = 'Choose the winner of the Final Bid.';
const { winners } = get_active_node_args();
for (const f of winners) {
gen_action(faction_player_map[f]);
@@ -817,7 +787,7 @@ states.break_tie_final_bid = {
states.break_tie_winner = {
inactive: 'break tie for winner of the game',
prompt() {
- view.prompt = 'Choose the winner of the game';
+ view.prompt = 'Choose the winner of the game.';
const { winners } = get_active_node_args();
for (const f of winners) {
gen_action(faction_player_map[f]);
@@ -849,7 +819,7 @@ states.change_active_player = {
},
};
states.choose_area_ap = {
- inactive: 'choose area to use Action Points',
+ inactive: 'use action points',
prompt() {
gen_spend_hero_points();
const use_morale_bonus = game.can_use_mb === 1 && game.bonuses[data_1.MORALE_BONUS] === data_1.ON;
@@ -947,7 +917,7 @@ states.choose_area_ap = {
},
};
states.change_bonus = {
- inactive: 'select Bonus',
+ inactive: 'toggle Bonus',
prompt() {
gen_spend_hero_points();
const args = get_active_node_args();
@@ -955,7 +925,10 @@ states.change_bonus = {
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)) {
- view.prompt = `${bonus_names[args.t]} is already ${args.v === data_1.OFF ? 'off' : 'on'}.`;
+ if (args.t === 'any')
+ view.prompt = `Both bonuses are already ${args.v === data_1.OFF ? 'off' : 'on'}.`;
+ else
+ view.prompt = `${bonus_names[args.t]} is already ${args.v === data_1.OFF ? 'off' : 'on'}.`;
gen_action('skip');
}
else if (args.t === data_1.ANY && args.v === data_1.ON) {
@@ -981,15 +954,15 @@ states.change_bonus = {
},
skip() {
const args = get_active_node_args();
- logi(`${bonus_names[args.t]} ${args.v === data_1.OFF ? 'off' : 'on'}`);
+ logi(`Bonus already ${args.v === data_1.OFF ? 'off' : 'on'}`);
resolve_active_and_proceed();
},
};
states.play_card = {
- inactive: 'play a card',
+ inactive: 'play another card',
prompt() {
gen_spend_hero_points();
- view.prompt = 'Play a card.';
+ view.prompt = 'Play another card.';
const faction = get_active_faction();
const hand = game.hands[faction];
for (let c of hand) {
@@ -1011,7 +984,7 @@ states.play_card = {
game.played_card = game.selected_cards[faction][game.selected_cards[faction].length - 1];
const args = get_active_node_args();
if (args && args.src === 'momentum') {
- log_header("~ Momentum ~\nC" + game.played_card, faction);
+ log_header("~ M" + data_1.MOMENTUM_MEDALLION_ID + " ~\nC" + game.played_card, faction);
}
else {
log_header("~ Play Card ~\nC" + game.played_card, faction);
@@ -1024,10 +997,9 @@ states.play_card = {
},
};
states.choose_card = {
- inactive: 'choose a card',
+ inactive: 'play a card for this turn',
prompt(player) {
- gen_spend_hero_points();
- view.prompt = 'Choose a card to play this turn.';
+ view.prompt = 'Play a card for this turn.';
const faction = player_faction_map[player];
if (game.selected_cards[faction].length === 0) {
view.actions.undo = 0;
@@ -1046,9 +1018,6 @@ states.choose_card = {
gen_action('skip');
}
},
- spend_hp() {
- resolve_spend_hp();
- },
card(c, player) {
const faction = player_faction_map[player];
game.selected_cards[faction] = [c];
@@ -1071,32 +1040,43 @@ states.choose_card = {
},
};
states.choose_final_bid = {
- inactive: 'choose Final Bid',
- prompt() {
- view.prompt = 'Add a card to the Final Bid.';
- const faction = get_active_faction();
- for (let c of game.hands[faction]) {
- if (!game.selected_cards[faction].includes(c)) {
- gen_action_card(c);
+ inactive: 'add cards to the Final Bid',
+ prompt(player) {
+ const faction = player_faction_map[player];
+ const number_selected = game.selected_cards[faction].length;
+ const number_hand = game.hands[faction].length;
+ if (number_selected < 3 && !(number_hand < 4 && number_selected === number_hand - 1)) {
+ for (let c of game.hands[faction]) {
+ if (!game.selected_cards[faction].includes(c)) {
+ gen_action_card(c);
+ }
}
}
- gen_action('done');
+ let n = 0;
+ for (let c of game.selected_cards[faction]) {
+ n += cards[c].strength;
+ }
+ if (n > 0)
+ view.prompt = `Final Bid for Glory: Discard up to 3 cards. Your bid is ${n} strength.`;
+ else
+ view.prompt = `Final Bid for Glory: Discard up to 3 cards for strength.`;
+ gen_action('confirm');
+ if (game.selected_cards[faction].length > 0)
+ gen_action('undo');
},
- card(c) {
- const faction = get_active_faction();
+ card(c, player) {
+ const faction = player_faction_map[player];
game.selected_cards[faction].push(c);
- const number_selected = game.selected_cards[faction].length;
- const number_hand = game.hands[faction].length;
- if (number_selected === 3 ||
- (number_hand < 4 && number_selected === number_hand - 1)) {
+ },
+ undo(_, player) {
+ const faction = player_faction_map[player];
+ game.selected_cards[faction].length--;
+ },
+ confirm(_, player) {
+ set_delete(game.active, player);
+ if (game.active.length === 0) {
resolve_active_and_proceed();
}
- else {
- next();
- }
- },
- done() {
- resolve_active_and_proceed(true);
},
};
function setup_momentum() {
@@ -1112,7 +1092,7 @@ function setup_momentum() {
}
}
states.choose_medallion = {
- inactive: 'claim a medallion',
+ inactive: 'claim a Medallion',
prompt() {
gen_spend_hero_points();
view.prompt = 'Claim a Medallion.';
@@ -1164,9 +1144,16 @@ states.choose_medallion = {
},
};
states.confirm_turn = {
- inactive: 'confirm their turn',
+ inactive: 'confirm their move',
prompt() {
- view.prompt = 'You will not be able to undo this action.';
+ if (game.fascist === 2)
+ view.prompt = 'Fascist Test: Done.';
+ else if (game.fascist === 1) {
+ let f = get_active_node_args().f;
+ view.prompt = `Fascist Event: ${f} needs to act.`;
+ }
+ else
+ view.prompt = 'You will not be able to undo this action.';
gen_action('confirm');
},
confirm() {
@@ -1174,7 +1161,7 @@ states.confirm_turn = {
},
};
states.confirm_fascist_turn = {
- inactive: 'confirm fascist turn',
+ inactive: 'end the Fascist turn',
prompt() {
view.prompt = "Done.";
gen_action('confirm');
@@ -1184,7 +1171,7 @@ states.confirm_fascist_turn = {
},
};
states.draw_card = {
- inactive: 'draw a card',
+ inactive: 'draw cards',
auto_resolve() {
const { src, v } = get_active_node_args();
if (src !== 'fascist_test') {
@@ -1217,18 +1204,15 @@ function draw_glory_from_bag() {
const index = random(game.bag_of_glory.length);
const faction = game.bag_of_glory[index];
game.glory.push(faction);
- game.glory_current_year = game.glory_current_year = [
- false,
- false,
- false,
- ];
game.glory_current_year[faction] = true;
array_remove(game.bag_of_glory, index);
- logi(`Pulled T${faction} from the Bag`);
+ log(`Pulled T${faction} from the Bag.`);
}
states.draw_glory = {
inactive: 'draw from the Bag of Glory',
auto_resolve() {
+ if (get_active_faction() === game.initiative)
+ return false;
draw_glory_from_bag();
return true;
},
@@ -1260,14 +1244,12 @@ states.end_of_year_discard = {
for (let c of tableau)
gen_action_card(c);
}
- if (needs_to_discard_from_hand && needs_to_discard_from_tableau) {
- view.prompt = 'Discard a card from your hand or tableau';
- }
- else if (needs_to_discard_from_hand || needs_to_discard_from_tableau) {
- view.prompt = `Discard a card from your ${needs_to_discard_from_hand ? 'hand' : 'tableau'}`;
+ view.prompt = JSON.stringify({ needs_to_discard_from_hand, needs_to_discard_from_tableau });
+ if (needs_to_discard_from_hand || needs_to_discard_from_tableau) {
+ view.prompt = 'End of Year: Discard cards from your hand and tableau.';
}
else {
- view.prompt = 'Confirm discard';
+ view.prompt = 'End of Year: Done.';
view.actions.confirm = 1;
}
if (discarded[faction_id].h.length > 0 || discarded[faction_id].t.length > 0) {
@@ -1321,7 +1303,7 @@ states.end_of_year_discard = {
},
};
states.hero_points = {
- inactive: 'gain Hero points',
+ inactive: 'gain or lose Hero points',
auto_resolve() {
const { src, v } = get_active_node_args();
if (src !== 'fascist_test') {
@@ -1341,14 +1323,14 @@ states.hero_points = {
if (value < 0) {
view.prompt =
value < -1
- ? `Lose ${Math.abs(value)} Hero points`
- : 'Lose 1 Hero point';
+ ? `Lose ${Math.abs(value)} Hero points.`
+ : 'Lose a Hero point.';
gen_action('lose_hp');
return;
}
if (game.hero_points[POOL_ID] > 0) {
view.prompt =
- value > 1 ? `Gain ${value} Hero points.` : 'Gain 1 Hero point.';
+ value > 1 ? `Gain ${value} Hero points.` : 'Gain a Hero point.';
gen_action('gain_hp');
}
else {
@@ -1392,7 +1374,7 @@ function resolve_player_with_most_hero_points(faction) {
resolve_active_and_proceed();
}
states.select_player_with_most_hero_points = {
- inactive: 'choose a Player',
+ inactive: 'choose who will gain or lose Hero points',
prompt() {
gen_spend_hero_points();
const { v } = get_active_node_args();
@@ -1456,6 +1438,7 @@ states.move_track = {
can_move_track = gen_move_track(track, game.tracks[track] + value) || can_move_track;
}
if (!can_move_track) {
+ view.prompt = view.prompt.replace("Move", "Cannot move");
gen_action('skip');
}
},
@@ -1483,6 +1466,14 @@ states.move_track = {
resolve_active_and_proceed();
},
skip() {
+ const node = get_active_node();
+ const track = node.a.t;
+ if (track === data_1.GOVERNMENT)
+ logi(`Government +0`);
+ else if (track === data_1.LIBERTY_OR_COLLECTIVIZATION)
+ logi("Liberty or Collectivization +0");
+ else
+ logi(`${get_track_name(track)} +0`);
resolve_active_and_proceed();
},
};
@@ -1514,7 +1505,7 @@ function can_move_track_down(track_id) {
return true;
}
states.move_track_up_or_down = {
- inactive: 'move a track',
+ inactive: 'move a Track',
auto_resolve() {
const { track_id, strength } = get_active_node_args();
const can_move_up = can_move_track_up(track_id);
@@ -1570,7 +1561,7 @@ states.peek_fascist_cards = {
inactive: 'peek at Fascist cards',
prompt() {
gen_spend_hero_points();
- view.prompt = 'Return one card to the top of the Fascist deck.';
+ view.prompt = 'Return one Fascist card to the top of the Fascist deck and discard the others.';
view.fascist_cards = game.fascist_cards;
for (const c of game.fascist_cards) {
gen_action_card(c);
@@ -1580,6 +1571,7 @@ states.peek_fascist_cards = {
resolve_spend_hp();
},
card(c) {
+ log(">Peeked at top three Fascist cards, returned one, and discarded the others");
game.top_of_events_deck = c;
for (const ec of game.fascist_cards) {
if (ec !== c) {
@@ -1605,12 +1597,12 @@ function set_player_turn_prompt({ can_play_card, use_ap, use_momentum, use_moral
else if (use_ap)
view.prompt = "Use Action Points.";
else if (use_momentum)
- view.prompt = "Play a second card.";
+ view.prompt = "Use Momentum Medallion.";
else
- view.prompt = "Player Turn: Done.";
+ view.prompt = "Player turn done.";
}
states.player_turn = {
- inactive: 'play their turn',
+ inactive: 'play their card',
prompt() {
gen_spend_hero_points();
const faction_id = get_active_faction();
@@ -1699,11 +1691,17 @@ states.player_turn = {
next();
},
};
+function remove_blank_marker(b) {
+ const track_id = Math.floor(b / 11);
+ const space_id = b % 11;
+ set_delete(game.triggered_track_effects, b);
+ logi(`Removed Blank from ${get_track_name(track_id)} ${space_id}`);
+}
states.remove_blank_marker = {
inactive: 'remove a Blank marker',
prompt() {
gen_spend_hero_points();
- view.prompt = 'Remove a Blank marker';
+ view.prompt = 'Remove a Blank marker.';
for (const b of game.triggered_track_effects) {
gen_action_blank_marker(b);
}
@@ -1716,12 +1714,30 @@ states.remove_blank_marker = {
resolve_spend_hp();
},
blank_marker(b) {
+ remove_blank_marker(b);
+ resolve_active_and_proceed();
+ },
+ skip() {
+ resolve_active_and_proceed();
+ },
+};
+states.remove_blank_marker_archives = {
+ inactive: 'remove a Blank marker',
+ prompt() {
+ view.prompt = 'Archives Medallion: Remove a Blank marker.';
+ for (const b of game.triggered_track_effects) {
+ gen_action_blank_marker(b);
+ }
+ if (game.triggered_track_effects.length === 0) {
+ view.prompt = 'No Blank marker to remove.';
+ gen_action('skip');
+ }
+ },
+ blank_marker(b) {
const faction = get_active_faction();
pay_hero_points(faction, 1);
- const track_id = Math.floor(b / 11);
- const space_id = b % 11;
- logp(`removed blank marker from ${get_track_name(track_id)} ${space_id}`);
- game.triggered_track_effects = game.triggered_track_effects.filter((id) => id !== b);
+ log(">M" + data_1.ARCHIVES_MEDALLION_ID);
+ remove_blank_marker(b);
game.used_medallions.push(data_1.ARCHIVES_MEDALLION_ID);
resolve_active_and_proceed();
},
@@ -1751,7 +1767,7 @@ states.remove_attack_from_fronts = {
gen_action_front(id);
});
if (!is_front_with_attacks) {
- view.prompt = 'No valid Front to remove attacks from.';
+ view.prompt = 'No Front to remove attacks from.';
gen_action('skip');
}
},
@@ -1844,13 +1860,13 @@ states.spend_hero_points = {
}
gen_action('draw_card');
if (can_use_medallion(data_1.ARCHIVES_MEDALLION_ID, faction)) {
- gen_action('remove_blank_marker');
+ gen_action('archives');
if (game.triggered_track_effects.length === 0) {
- view.actions['remove_blank_marker'] = 0;
+ view.actions['archives'] = 0;
}
}
if (can_use_medallion(data_1.VOLUNTEERS_MEDALLION_ID, faction)) {
- gen_action('add_to_front');
+ gen_action('volunteers');
}
if (hero_points < 2) {
return;
@@ -1872,10 +1888,12 @@ states.spend_hero_points = {
}
gen_spend_hero_points_move_track(data_1.GOVERNMENT, Math.floor(hero_points / 4));
},
- add_to_front() {
+ volunteers() {
const faction = get_active_faction();
+ log(">M" + data_1.VOLUNTEERS_MEDALLION_ID);
pay_hero_points(faction, 1);
insert_after_active_node(create_state_node('add_to_front', faction, {
+ src: 'volunteers',
t: data_1.ANY,
v: 1,
}));
@@ -1895,7 +1913,7 @@ states.spend_hero_points = {
draw_hand_cards(faction, 1);
resolve_active_and_proceed();
},
- remove_blank_marker() {
+ archives() {
const faction = get_active_faction();
if (game.used_medallions) {
game.used_medallions.push(data_1.ARCHIVES_MEDALLION_ID);
@@ -1903,7 +1921,7 @@ states.spend_hero_points = {
else {
game.used_medallions = [data_1.ARCHIVES_MEDALLION_ID];
}
- insert_after_active_node(create_state_node('remove_blank_marker', faction));
+ insert_after_active_node(create_state_node('remove_blank_marker_archives', faction));
resolve_active_and_proceed();
},
tr0(x) {
@@ -1933,7 +1951,7 @@ states.spend_hero_points = {
},
};
states.swap_card_tableau_hand = {
- inactive: 'swap cards',
+ inactive: 'swap cards in their tableau and hand',
prompt() {
gen_spend_hero_points();
view.prompt = 'Swap a card in your tableau with a card in your hand.';
@@ -2047,10 +2065,11 @@ function trash_card(faction) {
resolve_active_and_proceed();
}
states.use_organization_medallion = {
- inactive: 'use Organization Medallion',
+ inactive: 'choose to use Organization Medallion',
prompt() {
- gen_spend_hero_points();
- view.prompt = 'Use Organization Medallion?';
+ let { t, v } = get_active_node_args();
+ view.prompt = `Organization Medallion: Spend 1 Hero point to increase ${get_track_name(t)} movement?`;
+ gen_action(track_action_name[t], v);
gen_action('yes');
gen_action('no');
},
@@ -2062,33 +2081,36 @@ states.use_organization_medallion = {
pay_hero_points(faction, 1);
game.used_medallions.push(data_1.ORGANIZATION_MEDALLION_ID);
let { t, v } = get_active_node_args();
- if (v > game.tracks[t]) {
- v++;
- }
- else {
- v--;
- }
- move_track(t, v - game.tracks[t]);
+ log("M" + data_1.ORGANIZATION_MEDALLION_ID + ":");
+ move_track_to(t, v);
resolve_active_and_proceed();
},
+ tr0() { this.yes(); },
+ tr1() { this.yes(); },
+ tr2() { this.yes(); },
+ tr3() { this.yes(); },
+ tr4() { this.yes(); },
no() {
- const { t, v } = get_active_node_args();
- move_track(t, v);
resolve_active_and_proceed();
},
};
states.use_strategy_medallion = {
- inactive: 'use Strategy Medallion',
+ inactive: 'choose to use Strategy Medallion',
prompt() {
- gen_spend_hero_points();
- view.prompt = 'Use Strategy Medallion?';
+ const { f } = get_active_node_args();
+ view.prompt = `Strategy Medallion: Add 1 strength to ${front_names[f]}?`;
+ gen_action_front(f);
gen_action('yes');
gen_action('no');
},
spend_hp() {
resolve_spend_hp();
},
+ front(_) {
+ this.yes();
+ },
yes() {
+ log(">M" + data_1.STRATEGY_MEDALLION_ID);
game.used_medallions.push(data_1.STRATEGY_MEDALLION_ID);
const { f } = get_active_node_args();
const faction = get_active_faction();
@@ -2407,6 +2429,7 @@ function resolve_fascist_test() {
? 2
: 0;
if (can_use_medallion(data_1.PROPAGANDA_MEDALLION_ID, faction)) {
+ log(">M" + data_1.PROPAGANDA_MEDALLION_ID);
hero_points_gain += 2;
}
if (hero_points_gain > 0) {
@@ -2431,12 +2454,13 @@ function resolve_fascist_test() {
function resolve_final_bid() {
let highest_bid = 0;
let winners = [];
+ log("Final Bid for Glory:");
for (const f of get_player_order()) {
let player_bid = 0;
for (const c of game.selected_cards[f]) {
player_bid += cards[c].strength;
}
- log(`${faction_player_map[f]} bid ${player_bid} cards`);
+ log(`>${faction_player_map[f]} ${player_bid} strength`);
if (player_bid === highest_bid) {
winners.push(f);
}
@@ -2553,15 +2577,16 @@ function move_track_to(track_id, new_value) {
: make_list(new_value, current_value - 1);
triggered_spaces.forEach((space_id) => {
const trigger = tracks[track_id].triggers[space_id];
+ const blank = get_blank_marker_id(track_id, space_id);
if (trigger !== null &&
- !game.triggered_track_effects.includes(get_blank_marker_id(track_id, space_id))) {
- if (space_id !== 0) {
- game.triggered_track_effects.push(get_blank_marker_id(track_id, space_id));
- }
+ !game.triggered_track_effects.includes(blank) &&
+ !game.untriggered_track_effects.includes(blank)) {
+ set_delete(game.triggered_track_effects, blank);
+ set_add(game.untriggered_track_effects, blank);
const node = resolve_effect(trigger, tracks[track_id].action);
if (node !== null) {
insert_after_active_node(node);
- insert_after_active_node(create_function_node('log_trigger', [track_id, space_id]));
+ insert_after_active_node(create_function_node('place_blank_marker', [track_id, space_id]));
}
}
});
@@ -2581,11 +2606,20 @@ function can_use_medallion(medallion_id, faction) {
return can_use;
}
}
-function insert_use_organization_medallion_node(track_id, value) {
+function insert_use_organization_medallion_node(track_id, delta) {
const faction = get_active_faction();
+ if (delta > 0)
+ delta = 1;
+ else if (delta < 0)
+ delta = -1;
+ else
+ return;
+ let v = game.tracks[track_id] + delta;
+ if (v < 0 || v > 10)
+ return;
insert_after_active_node(create_state_node('use_organization_medallion', faction, {
t: track_id,
- v: value,
+ v: v,
}));
}
function update_bonus(bonus_id, status) {
@@ -2800,7 +2834,8 @@ function resolve_effect(effect, source) {
}
function win_final_bid(faction_id) {
log_br();
- log(`${faction_player_map[faction_id]} won the Final Bid`);
+ log(`${faction_player_map[faction_id]} won the Final Bid:`);
+ logi("Placed T" + faction_id);
game.glory.push(faction_id);
}
function win_game(player, glory) {
@@ -2858,9 +2893,12 @@ function log_header(msg, prefix) {
log(`#${prefix} ${msg}`);
log_br();
}
-function log_trigger(args) {
+function place_blank_marker(args) {
let [track_id, space_id] = args;
+ let blank = get_blank_marker_id(track_id, space_id);
log(`Trigger ${get_track_name(track_id)} ${space_id}:`);
+ set_delete(game.untriggered_track_effects, blank);
+ set_add(game.triggered_track_effects, blank);
resolve_active_and_proceed();
}
function logi(msg) {
@@ -2996,12 +3034,34 @@ function get_source_name(source) {
case 'tr3': return tracks[3].name + ' Trigger';
case 'tr4': return tracks[4].name + ' Trigger';
case 'track_icon':
- return 'Track Trigger';
+ throw "UNUSED";
+ case 'volunteers':
+ return 'Volunteers Medallion';
case data_1.MOMENTUM:
return 'Momentum';
}
return source;
}
+function get_source_inactive(source) {
+ switch (source) {
+ case 'player_event':
+ return 'execute ' + cards[game.played_card].title;
+ case 'fascist_event':
+ return 'execute ' + cards[game.current_events[game.current_events.length - 1]].title;
+ case 'fascist_test':
+ return 'resolve Test';
+ case 'tr0': return 'trigger ' + tracks[0].name + ' icon';
+ case 'tr1': return 'trigger ' + tracks[1].name + ' icon';
+ case 'tr2': return 'trigger ' + tracks[2].name + ' icon';
+ case 'tr3': return 'trigger ' + tracks[3].name + ' icon';
+ case 'tr4': return 'trigger ' + tracks[4].name + ' icon';
+ case 'track_icon':
+ throw "UNUSED";
+ case data_1.MOMENTUM:
+ return 'use Momentum';
+ }
+ return source;
+}
function get_factions_with_most_hero_poins() {
let most_hero_points = null;
let faction_ids = [];
@@ -3037,6 +3097,8 @@ function list_deck(id) {
return;
if (game.discard[id].includes(card))
return;
+ if (game.fascist_cards && game.fascist_cards.includes(card))
+ return;
}
else if (game.hands[id].includes(card) ||
game.discard[id].includes(card) ||
@@ -3136,6 +3198,21 @@ function array_insert(array, index, item) {
array[i] = array[i - 1];
array[index] = item;
}
+function set_add(set, item) {
+ let a = 0;
+ let b = set.length - 1;
+ while (a <= b) {
+ const m = (a + b) >> 1;
+ const x = set[m];
+ if (item < x)
+ b = m - 1;
+ else if (item > x)
+ a = m + 1;
+ else
+ return set;
+ }
+ return array_insert(set, a, item);
+}
function set_delete(set, item) {
let a = 0;
let b = set.length - 1;
diff --git a/rules.ts b/rules.ts
index bb64184..cc1fb2d 100644
--- a/rules.ts
+++ b/rules.ts
@@ -168,7 +168,7 @@ function gen_spend_hero_points() {
}
}
-const multiactive_states = ['choose_card', 'end_of_year_discard'];
+const multiactive_states = ['choose_card', 'end_of_year_discard', 'choose_final_bid'];
export function action(
state: Game,
@@ -257,10 +257,8 @@ function setup_final_bid() {
game.fascist = 0;
log_header('Final Bid', 't');
- const player_order = get_player_order();
- game.engine = player_order.map((faction_id) =>
- create_state_node('choose_final_bid', faction_id)
- );
+
+ game.engine = [create_state_node('choose_final_bid', 'all')];
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'));
@@ -337,7 +335,7 @@ const engine_functions: Record<string, Function> = {
start_year,
resolve_fascist_test,
resolve_final_bid,
- log_trigger,
+ place_blank_marker,
// Unique card effects
card1_event2,
card3_event2,
@@ -484,7 +482,7 @@ function next(checkpoint = false) {
if (next_active !== current_active && game.undo.length > 0) {
insert_before_active_node(
- create_state_node('confirm_turn', get_active_faction())
+ create_state_node('confirm_turn', get_active_faction(), { f: next_active })
);
game.state = 'confirm_turn';
return;
@@ -561,7 +559,8 @@ function game_view(state: Game, current: Player | 'Observer') {
current !== game.active &&
!game.active.includes(current as Player)
) {
- let inactive = states[game.state].inactive || game.state;
+ let src = get_active_node_args()?.src;
+ let inactive = src ? get_source_inactive(src) : states[game.state].inactive || game.state;
view.prompt = Array.isArray(game.active)
? `Waiting for ${game.active.join(' and ')} to ${inactive}.`
: `Waiting for ${game.active} to ${inactive}.`;
@@ -653,6 +652,7 @@ export function setup(seed: number, _scenario: string, options: Record<string,bo
[],
],
triggered_track_effects: [],
+ untriggered_track_effects: [],
log: [],
undo: [],
used_medallions: [],
@@ -688,6 +688,7 @@ function draw_hand_cards(faction_id: FactionId, count: number, indent = true) {
const deck = list_deck(faction_id);
if (game.medallions[faction_id].includes(INTELLIGENCE_MEDALLION_ID)) {
+ log(">M" + INTELLIGENCE_MEDALLION_ID);
count++;
}
let drawn_cards = 0;
@@ -774,7 +775,7 @@ const track_icon_to_track_id_map = {
};
states.activate_icon = {
- inactive: 'activate an icon',
+ inactive: 'activate a Morale Bonus icon',
prompt() {
gen_spend_hero_points();
const c = cards[game.played_card] as PlayerCard;
@@ -867,46 +868,19 @@ states.activate_icon = {
);
resolve_active_and_proceed();
},
- tr0(x: number) {
- if (can_use_medallion(ORGANIZATION_MEDALLION_ID)) {
- insert_use_organization_medallion_node(LIBERTY, x);
- } else {
- move_track_to(0, x);
- }
- resolve_active_and_proceed();
- },
- tr1(x: number) {
- if (can_use_medallion(ORGANIZATION_MEDALLION_ID)) {
- insert_use_organization_medallion_node(COLLECTIVIZATION, x);
- } else {
- move_track_to(1, x);
- }
- resolve_active_and_proceed();
- },
- tr2(x: number) {
+ trX(x: number, track: number) {
+ let old = game.tracks[track]
+ move_track_to(track, x);
if (can_use_medallion(ORGANIZATION_MEDALLION_ID)) {
- insert_use_organization_medallion_node(GOVERNMENT, x);
- } else {
- move_track_to(2, x);
- }
- resolve_active_and_proceed();
- },
- tr3(x: number) {
- if (can_use_medallion(ORGANIZATION_MEDALLION_ID)) {
- insert_use_organization_medallion_node(SOVIET_SUPPORT, x);
- } else {
- move_track_to(3, x);
- }
- resolve_active_and_proceed();
- },
- tr4(x: number) {
- if (can_use_medallion(ORGANIZATION_MEDALLION_ID)) {
- insert_use_organization_medallion_node(FOREIGN_AID, x);
- } else {
- move_track_to(4, x);
+ insert_use_organization_medallion_node(track, game.tracks[track] - old);
}
resolve_active_and_proceed();
},
+ tr0(x: number) { this.trX(x, 0) },
+ tr1(x: number) { this.trX(x, 1) },
+ tr2(x: number) { this.trX(x, 2) },
+ tr3(x: number) { this.trX(x, 3) },
+ tr4(x: number) { this.trX(x, 4) },
draw_card() {
draw_hand_cards(
get_active_faction(),
@@ -981,7 +955,7 @@ states.add_to_front = {
const args = get_active_node_args();
const possible_fronts = get_fronts_to_add_to(args.t);
if (possible_fronts.length === 0) {
- view.prompt = 'No valid front to add strength to.'
+ view.prompt = 'Cannot support ' + front_names[args.t] + '.'
gen_action('skip');
} else if (possible_fronts.length === 4) {
view.prompt = `Support any Front.`;
@@ -1016,7 +990,7 @@ states.attack_front = {
const number_of_fronts = possible_fronts.length;
if (number_of_fronts === 0) {
- view.prompt = 'No valid front to attack.';
+ view.prompt = 'Cannot attack ' + front_names[target] + '.'
gen_action('skip');
} else if (possible_fronts.length === 4) {
view.prompt = `Attack any Front.`;
@@ -1042,7 +1016,7 @@ states.attack_front = {
states.break_tie_final_bid = {
inactive: 'break tie for Final Bid',
prompt() {
- view.prompt = 'Choose the winner of the Final Bid';
+ view.prompt = 'Choose the winner of the Final Bid.';
const { winners } = get_active_node_args();
for (const f of winners) {
gen_action(faction_player_map[f]);
@@ -1065,7 +1039,7 @@ states.break_tie_final_bid = {
states.break_tie_winner = {
inactive: 'break tie for winner of the game',
prompt() {
- view.prompt = 'Choose the winner of the game';
+ view.prompt = 'Choose the winner of the game.';
const { winners } = get_active_node_args();
for (const f of winners) {
gen_action(faction_player_map[f]);
@@ -1104,7 +1078,7 @@ states.change_active_player = {
};
states.choose_area_ap = {
- inactive: 'choose area to use Action Points',
+ inactive: 'use action points',
prompt() {
gen_spend_hero_points();
const use_morale_bonus = game.can_use_mb === 1 && game.bonuses[MORALE_BONUS] === ON;
@@ -1213,7 +1187,7 @@ states.choose_area_ap = {
};
states.change_bonus = {
- inactive: 'select Bonus',
+ inactive: 'toggle Bonus',
prompt() {
gen_spend_hero_points();
const args = get_active_node_args();
@@ -1223,7 +1197,10 @@ states.change_bonus = {
game.bonuses[MORALE_BONUS] === ON) ||
(args.v === OFF && game.bonuses[args.t] === OFF)
) {
- view.prompt = `${bonus_names[args.t]} is already ${args.v === OFF ? 'off' : 'on'}.`;
+ if (args.t === 'any')
+ view.prompt = `Both bonuses are already ${args.v === OFF ? 'off' : 'on'}.`;
+ else
+ view.prompt = `${bonus_names[args.t]} is already ${args.v === OFF ? 'off' : 'on'}.`;
gen_action('skip');
}
else if (args.t === ANY && args.v === ON) {
@@ -1248,18 +1225,18 @@ states.change_bonus = {
},
skip() {
const args = get_active_node_args();
- logi(`${bonus_names[args.t]} ${args.v === OFF ? 'off' : 'on'}`);
+ logi(`Bonus already ${args.v === OFF ? 'off' : 'on'}`);
resolve_active_and_proceed();
},
};
// Used for effects that allow play of an extra card
states.play_card = {
- inactive: 'play a card',
+ inactive: 'play another card',
prompt() {
gen_spend_hero_points();
- view.prompt = 'Play a card.';
+ view.prompt = 'Play another card.';
const faction = get_active_faction();
@@ -1286,7 +1263,7 @@ states.play_card = {
const args = get_active_node_args();
if (args && args.src === 'momentum') {
- log_header("~ Momentum ~\nC" + game.played_card, faction);
+ log_header("~ M" + MOMENTUM_MEDALLION_ID + " ~\nC" + game.played_card, faction);
} else {
log_header("~ Play Card ~\nC" + game.played_card, faction);
}
@@ -1302,10 +1279,9 @@ states.play_card = {
// Multiactive choose card state
states.choose_card = {
- inactive: 'choose a card',
+ inactive: 'play a card for this turn',
prompt(player: Player) {
- gen_spend_hero_points();
- view.prompt = 'Choose a card to play this turn.';
+ view.prompt = 'Play a card for this turn.';
const faction = player_faction_map[player];
@@ -1326,9 +1302,6 @@ states.choose_card = {
gen_action('skip');
}
},
- spend_hp() {
- resolve_spend_hp();
- },
card(c: CardId, player: Player) {
const faction = player_faction_map[player];
game.selected_cards[faction] = [c];
@@ -1352,35 +1325,47 @@ states.choose_card = {
};
states.choose_final_bid = {
- inactive: 'choose Final Bid',
- prompt() {
- view.prompt = 'Add a card to the Final Bid.';
- const faction = get_active_faction();
- for (let c of game.hands[faction]) {
- if (!game.selected_cards[faction].includes(c)) {
- gen_action_card(c);
+ inactive: 'add cards to the Final Bid',
+ prompt(player: Player) {
+ const faction = player_faction_map[player];
+
+ const number_selected = game.selected_cards[faction].length;
+ const number_hand = game.hands[faction].length;
+ if (number_selected < 3 && !(number_hand < 4 && number_selected === number_hand - 1)) {
+ for (let c of game.hands[faction]) {
+ if (!game.selected_cards[faction].includes(c)) {
+ gen_action_card(c);
+ }
}
}
- gen_action('done');
+
+ let n = 0
+ for (let c of game.selected_cards[faction]) {
+ n += (cards[c] as PlayerCard).strength
+ }
+ if (n > 0)
+ view.prompt = `Final Bid for Glory: Discard up to 3 cards. Your bid is ${n} strength.`
+ else
+ view.prompt = `Final Bid for Glory: Discard up to 3 cards for strength.`
+
+ gen_action('confirm');
+ if (game.selected_cards[faction].length > 0)
+ gen_action('undo');
},
- card(c: CardId) {
- const faction = get_active_faction();
+ card(c: CardId, player: Player) {
+ const faction = player_faction_map[player];
game.selected_cards[faction].push(c);
- const number_selected = game.selected_cards[faction].length;
-
- const number_hand = game.hands[faction].length;
- if (
- number_selected === 3 ||
- (number_hand < 4 && number_selected === number_hand - 1)
- ) {
+ },
+ undo(_, player: Player) {
+ const faction = player_faction_map[player];
+ game.selected_cards[faction].length--;
+ },
+ confirm(_, player: Player) {
+ set_delete(game.active as Player[], player);
+ if (game.active.length === 0) {
resolve_active_and_proceed();
- } else {
- next();
}
},
- done() {
- resolve_active_and_proceed(true);
- },
};
function setup_momentum() {
@@ -1401,7 +1386,7 @@ function setup_momentum() {
}
states.choose_medallion = {
- inactive: 'claim a medallion',
+ inactive: 'claim a Medallion',
prompt() {
gen_spend_hero_points();
view.prompt = 'Claim a Medallion.';
@@ -1457,9 +1442,15 @@ states.choose_medallion = {
};
states.confirm_turn = {
- inactive: 'confirm their turn',
+ inactive: 'confirm their move',
prompt() {
- view.prompt = 'You will not be able to undo this action.';
+ if (game.fascist === 2)
+ view.prompt = 'Fascist Test: Done.'
+ else if (game.fascist === 1) {
+ let f = get_active_node_args().f
+ view.prompt = `Fascist Event: ${f} needs to act.`
+ } else
+ view.prompt = 'You will not be able to undo this action.'
gen_action('confirm');
},
confirm() {
@@ -1468,7 +1459,7 @@ states.confirm_turn = {
};
states.confirm_fascist_turn = {
- inactive: 'confirm fascist turn',
+ inactive: 'end the Fascist turn',
prompt() {
view.prompt = "Done.";
gen_action('confirm');
@@ -1479,7 +1470,7 @@ states.confirm_fascist_turn = {
};
states.draw_card = {
- inactive: 'draw a card',
+ inactive: 'draw cards',
auto_resolve() {
const { src, v } = get_active_node_args();
if (src !== 'fascist_test') {
@@ -1515,22 +1506,19 @@ function draw_glory_from_bag() {
game.glory.push(faction);
- game.glory_current_year = game.glory_current_year = [
- false,
- false,
- false,
- ];
-
game.glory_current_year[faction] = true;
array_remove(game.bag_of_glory, index);
- logi(`Pulled T${faction} from the Bag`);
+ log(`Pulled T${faction} from the Bag.`);
}
states.draw_glory = {
inactive: 'draw from the Bag of Glory',
auto_resolve() {
+ // active player to trigger the draw
+ if (get_active_faction() === game.initiative)
+ return false;
draw_glory_from_bag();
return true;
},
@@ -1566,14 +1554,11 @@ states.end_of_year_discard = {
for (let c of tableau) gen_action_card(c);
}
- if (needs_to_discard_from_hand && needs_to_discard_from_tableau) {
- view.prompt = 'Discard a card from your hand or tableau';
- } else if (needs_to_discard_from_hand || needs_to_discard_from_tableau) {
- view.prompt = `Discard a card from your ${
- needs_to_discard_from_hand ? 'hand' : 'tableau'
- }`;
+ view.prompt = JSON.stringify({needs_to_discard_from_hand,needs_to_discard_from_tableau});
+ if (needs_to_discard_from_hand || needs_to_discard_from_tableau) {
+ view.prompt = 'End of Year: Discard cards from your hand and tableau.';
} else {
- view.prompt = 'Confirm discard';
+ view.prompt = 'End of Year: Done.';
view.actions.confirm = 1;
}
if (discarded[faction_id].h.length > 0 || discarded[faction_id].t.length > 0) {
@@ -1633,7 +1618,7 @@ states.end_of_year_discard = {
};
states.hero_points = {
- inactive: 'gain Hero points',
+ inactive: 'gain or lose Hero points',
auto_resolve() {
const { src, v } = get_active_node_args();
if (src !== 'fascist_test') {
@@ -1652,14 +1637,14 @@ states.hero_points = {
if (value < 0) {
view.prompt =
value < -1
- ? `Lose ${Math.abs(value)} Hero points`
- : 'Lose 1 Hero point';
+ ? `Lose ${Math.abs(value)} Hero points.`
+ : 'Lose a Hero point.';
gen_action('lose_hp');
return;
}
if (game.hero_points[POOL_ID] > 0) {
view.prompt =
- value > 1 ? `Gain ${value} Hero points.` : 'Gain 1 Hero point.';
+ value > 1 ? `Gain ${value} Hero points.` : 'Gain a Hero point.';
gen_action('gain_hp');
} else {
view.prompt = 'No Hero points available in pool.';
@@ -1704,7 +1689,7 @@ function resolve_player_with_most_hero_points(faction: FactionId) {
}
states.select_player_with_most_hero_points = {
- inactive: 'choose a Player',
+ inactive: 'choose who will gain or lose Hero points',
prompt() {
gen_spend_hero_points();
const { v } = get_active_node_args();
@@ -1774,6 +1759,7 @@ states.move_track = {
can_move_track = gen_move_track(track, game.tracks[track] + value) || can_move_track;
}
if (!can_move_track) {
+ view.prompt = view.prompt.replace("Move", "Cannot move")
gen_action('skip');
}
},
@@ -1801,6 +1787,14 @@ states.move_track = {
resolve_active_and_proceed();
},
skip() {
+ const node = get_active_node();
+ const track = node.a.t
+ if (track === GOVERNMENT)
+ logi(`Government +0`)
+ else if (track === LIBERTY_OR_COLLECTIVIZATION)
+ logi("Liberty or Collectivization +0")
+ else
+ logi(`${get_track_name(track)} +0`)
resolve_active_and_proceed();
},
};
@@ -1856,7 +1850,7 @@ function can_move_track_down(track_id): boolean {
// NOTE: we can probably remove this state. I don't think it's used anywhere anymore
states.move_track_up_or_down = {
- inactive: 'move a track',
+ inactive: 'move a Track',
auto_resolve() {
const { track_id, strength } = get_active_node_args();
const can_move_up = can_move_track_up(track_id);
@@ -1910,7 +1904,7 @@ states.peek_fascist_cards = {
inactive: 'peek at Fascist cards',
prompt() {
gen_spend_hero_points();
- view.prompt = 'Return one card to the top of the Fascist deck.';
+ view.prompt = 'Return one Fascist card to the top of the Fascist deck and discard the others.';
view.fascist_cards = game.fascist_cards;
for (const c of game.fascist_cards) {
gen_action_card(c);
@@ -1920,6 +1914,7 @@ states.peek_fascist_cards = {
resolve_spend_hp();
},
card(c: CardId) {
+ log(">Peeked at top three Fascist cards, returned one, and discarded the others")
game.top_of_events_deck = c;
for (const ec of game.fascist_cards) {
if (ec !== c) {
@@ -1956,13 +1951,13 @@ function set_player_turn_prompt({
else if (use_ap)
view.prompt = "Use Action Points."
else if (use_momentum)
- view.prompt = "Play a second card."
+ view.prompt = "Use Momentum Medallion."
else
- view.prompt = "Player Turn: Done."
+ view.prompt = "Player turn done."
}
states.player_turn = {
- inactive: 'play their turn',
+ inactive: 'play their card',
prompt() {
gen_spend_hero_points();
const faction_id = get_active_faction();
@@ -2070,11 +2065,18 @@ states.player_turn = {
},
};
+function remove_blank_marker(b: number) {
+ const track_id = Math.floor(b / 11);
+ const space_id = b % 11;
+ set_delete(game.triggered_track_effects, b);
+ logi(`Removed Blank from ${get_track_name(track_id)} ${space_id}`);
+}
+
states.remove_blank_marker = {
inactive: 'remove a Blank marker',
prompt() {
gen_spend_hero_points();
- view.prompt = 'Remove a Blank marker';
+ view.prompt = 'Remove a Blank marker.';
for (const b of game.triggered_track_effects) {
gen_action_blank_marker(b);
@@ -2088,15 +2090,31 @@ states.remove_blank_marker = {
resolve_spend_hp();
},
blank_marker(b: number) {
+ remove_blank_marker(b);
+ resolve_active_and_proceed();
+ },
+ skip() {
+ resolve_active_and_proceed();
+ },
+};
+
+states.remove_blank_marker_archives = {
+ inactive: 'remove a Blank marker',
+ prompt() {
+ view.prompt = 'Archives Medallion: Remove a Blank marker.';
+ for (const b of game.triggered_track_effects) {
+ gen_action_blank_marker(b);
+ }
+ if (game.triggered_track_effects.length === 0) {
+ view.prompt = 'No Blank marker to remove.';
+ gen_action('skip');
+ }
+ },
+ blank_marker(b: number) {
const faction = get_active_faction();
pay_hero_points(faction, 1);
-
- const track_id = Math.floor(b / 11);
- const space_id = b % 11;
- logp(`removed blank marker from ${get_track_name(track_id)} ${space_id}`);
- game.triggered_track_effects = game.triggered_track_effects.filter(
- (id) => id !== b
- );
+ log(">M" + ARCHIVES_MEDALLION_ID);
+ remove_blank_marker(b);
game.used_medallions.push(ARCHIVES_MEDALLION_ID);
resolve_active_and_proceed();
},
@@ -2134,7 +2152,7 @@ states.remove_attack_from_fronts = {
gen_action_front(id);
});
if (!is_front_with_attacks) {
- view.prompt = 'No valid Front to remove attacks from.';
+ view.prompt = 'No Front to remove attacks from.';
gen_action('skip');
}
},
@@ -2244,13 +2262,13 @@ states.spend_hero_points = {
gen_action('draw_card');
if (can_use_medallion(ARCHIVES_MEDALLION_ID, faction)) {
- gen_action('remove_blank_marker');
+ gen_action('archives');
if (game.triggered_track_effects.length === 0) {
- view.actions['remove_blank_marker'] = 0;
+ view.actions['archives'] = 0;
}
}
if (can_use_medallion(VOLUNTEERS_MEDALLION_ID, faction)) {
- gen_action('add_to_front');
+ gen_action('volunteers');
}
if (hero_points < 2) {
@@ -2285,11 +2303,13 @@ states.spend_hero_points = {
gen_spend_hero_points_move_track(GOVERNMENT, Math.floor(hero_points / 4));
},
- add_to_front() {
+ volunteers() {
const faction = get_active_faction();
+ log(">M" + VOLUNTEERS_MEDALLION_ID)
pay_hero_points(faction, 1);
insert_after_active_node(
create_state_node('add_to_front', faction, {
+ src: 'volunteers',
t: ANY,
v: 1,
})
@@ -2310,14 +2330,14 @@ states.spend_hero_points = {
draw_hand_cards(faction, 1);
resolve_active_and_proceed();
},
- remove_blank_marker() {
+ archives() {
const faction = get_active_faction();
if (game.used_medallions) {
game.used_medallions.push(ARCHIVES_MEDALLION_ID);
} else {
game.used_medallions = [ARCHIVES_MEDALLION_ID];
}
- insert_after_active_node(create_state_node('remove_blank_marker', faction));
+ insert_after_active_node(create_state_node('remove_blank_marker_archives', faction));
resolve_active_and_proceed();
},
tr0(x: number) {
@@ -2355,7 +2375,7 @@ states.spend_hero_points = {
Use the length of selected_cards[faction] to figure out where we are.
*/
states.swap_card_tableau_hand = {
- inactive: 'swap cards',
+ inactive: 'swap cards in their tableau and hand',
prompt() {
gen_spend_hero_points();
view.prompt = 'Swap a card in your tableau with a card in your hand.';
@@ -2476,11 +2496,12 @@ function trash_card(faction: FactionId) {
}
states.use_organization_medallion = {
- inactive: 'use Organization Medallion',
+ inactive: 'choose to use Organization Medallion',
prompt() {
- gen_spend_hero_points();
- view.prompt = 'Use Organization Medallion?';
-
+ // gen_spend_hero_points(); // confusing when available during this question
+ let { t, v } = get_active_node_args();
+ view.prompt = `Organization Medallion: Spend 1 Hero point to increase ${get_track_name(t)} movement?`;
+ gen_action(track_action_name[t], v)
gen_action('yes');
gen_action('no');
},
@@ -2491,42 +2512,39 @@ states.use_organization_medallion = {
const faction = get_active_faction();
pay_hero_points(faction, 1);
game.used_medallions.push(ORGANIZATION_MEDALLION_ID);
-
- // Value is the clicked location on the track
let { t, v } = get_active_node_args();
-
- // If player uses medallion we need to add or subtract
- // depending on direction of movement
- if (v > game.tracks[t]) {
- v++;
- } else {
- v--;
- }
-
- move_track(t, v - game.tracks[t]);
+ log("M" + ORGANIZATION_MEDALLION_ID + ":")
+ move_track_to(t, v);
resolve_active_and_proceed();
},
+ tr0() { this.yes() },
+ tr1() { this.yes() },
+ tr2() { this.yes() },
+ tr3() { this.yes() },
+ tr4() { this.yes() },
no() {
- const { t, v } = get_active_node_args();
-
- move_track(t, v);
resolve_active_and_proceed();
},
};
states.use_strategy_medallion = {
- inactive: 'use Strategy Medallion',
+ inactive: 'choose to use Strategy Medallion',
prompt() {
- gen_spend_hero_points();
- view.prompt = 'Use Strategy Medallion?';
-
+ // gen_spend_hero_points(); // confusing when available during this question
+ const { f } = get_active_node_args();
+ view.prompt = `Strategy Medallion: Add 1 strength to ${front_names[f]}?`;
+ gen_action_front(f)
gen_action('yes');
gen_action('no');
},
spend_hp() {
resolve_spend_hp();
},
+ front(_) {
+ this.yes()
+ },
yes() {
+ log(">M" + STRATEGY_MEDALLION_ID);
game.used_medallions.push(STRATEGY_MEDALLION_ID);
const { f } = get_active_node_args();
const faction = get_active_faction();
@@ -2960,6 +2978,7 @@ function resolve_fascist_test() {
? 2
: 0;
if (can_use_medallion(PROPAGANDA_MEDALLION_ID, faction)) {
+ log(">M" + PROPAGANDA_MEDALLION_ID);
hero_points_gain += 2;
}
if (hero_points_gain > 0) {
@@ -2991,12 +3010,13 @@ function resolve_fascist_test() {
function resolve_final_bid() {
let highest_bid = 0;
let winners: FactionId[] = [];
+ log("Final Bid for Glory:")
for (const f of get_player_order()) {
let player_bid = 0;
for (const c of game.selected_cards[f]) {
player_bid += (cards[c] as PlayerCard).strength;
}
- log(`${faction_player_map[f]} bid ${player_bid} cards`);
+ log(`>${faction_player_map[f]} ${player_bid} strength`);
if (player_bid === highest_bid) {
winners.push(f);
} else if (player_bid > highest_bid) {
@@ -3136,21 +3156,18 @@ function move_track_to(track_id: number, new_value: number) {
triggered_spaces.forEach((space_id) => {
const trigger = tracks[track_id].triggers[space_id];
+ const blank = get_blank_marker_id(track_id, space_id);
if (
trigger !== null &&
- !game.triggered_track_effects.includes(
- get_blank_marker_id(track_id, space_id)
- )
+ !game.triggered_track_effects.includes(blank) &&
+ !game.untriggered_track_effects.includes(blank)
) {
- if (space_id !== 0) {
- game.triggered_track_effects.push(
- get_blank_marker_id(track_id, space_id)
- );
- }
+ set_delete(game.triggered_track_effects, blank);
+ set_add(game.untriggered_track_effects, blank);
const node = resolve_effect(trigger, tracks[track_id].action);
if (node !== null) {
insert_after_active_node(node);
- insert_after_active_node(create_function_node('log_trigger', [track_id, space_id]));
+ insert_after_active_node(create_function_node('place_blank_marker', [track_id, space_id]));
}
}
});
@@ -3176,14 +3193,22 @@ function can_use_medallion(medallion_id: number, faction?: FactionId) {
function insert_use_organization_medallion_node(
track_id: number,
- value: number
+ delta: number
) {
const faction = get_active_faction();
-
+ if (delta > 0)
+ delta = 1
+ else if (delta < 0)
+ delta = -1
+ else
+ return;
+ let v = game.tracks[track_id] + delta
+ if (v < 0 || v > 10)
+ return;
insert_after_active_node(
create_state_node('use_organization_medallion', faction, {
t: track_id,
- v: value,
+ v: v,
})
);
}
@@ -3471,7 +3496,8 @@ function resolve_effect(effect: Effect, source?: EffectSource): EngineNode {
function win_final_bid(faction_id: FactionId) {
log_br();
- log(`${faction_player_map[faction_id]} won the Final Bid`);
+ log(`${faction_player_map[faction_id]} won the Final Bid:`);
+ logi("Placed T" + faction_id)
game.glory.push(faction_id);
}
@@ -3555,9 +3581,12 @@ function log_header(msg: string, prefix: string | number) {
log_br();
}
-function log_trigger(args) {
+function place_blank_marker(args) {
let [ track_id, space_id ] = args;
+ let blank = get_blank_marker_id(track_id, space_id);
log(`Trigger ${get_track_name(track_id)} ${space_id}:`);
+ set_delete(game.untriggered_track_effects, blank);
+ set_add(game.triggered_track_effects, blank);
resolve_active_and_proceed();
}
@@ -3729,13 +3758,36 @@ function get_source_name(source: EffectSource): string {
case 'tr3': return tracks[3].name + ' Trigger';
case 'tr4': return tracks[4].name + ' Trigger';
case 'track_icon':
- return 'Track Trigger';
+ throw "UNUSED"
+ case 'volunteers':
+ return 'Volunteers Medallion'
case MOMENTUM:
return 'Momentum';
}
return source;
}
+function get_source_inactive(source: EffectSource): string {
+ switch (source) {
+ case 'player_event':
+ return 'execute ' + cards[game.played_card].title;
+ case 'fascist_event':
+ return 'execute ' + cards[game.current_events[game.current_events.length - 1]].title;
+ case 'fascist_test':
+ return 'resolve Test';
+ case 'tr0': return 'trigger ' + tracks[0].name + ' icon';
+ case 'tr1': return 'trigger ' + tracks[1].name + ' icon';
+ case 'tr2': return 'trigger ' + tracks[2].name + ' icon';
+ case 'tr3': return 'trigger ' + tracks[3].name + ' icon';
+ case 'tr4': return 'trigger ' + tracks[4].name + ' icon';
+ case 'track_icon':
+ throw "UNUSED"
+ case MOMENTUM:
+ return 'use Momentum';
+ }
+ return source;
+}
+
function get_factions_with_most_hero_poins(): FactionId[] {
let most_hero_points = null;
let faction_ids = [];
@@ -3771,6 +3823,7 @@ function list_deck(id: FactionId | FascistId) {
if (id === FASCIST_ID) {
if (game.current_events.includes(card)) return;
if (game.discard[id].includes(card)) return;
+ if (game.fascist_cards && game.fascist_cards.includes(card)) return;
} else if (
game.hands[id].includes(card) ||
game.discard[id].includes(card) ||
@@ -3921,19 +3974,19 @@ function array_insert<T>(array: T[], index: number, item: T) {
// return false;
// }
-// function set_add<T>(set: T[], item: T) {
-// // eslint-disable-line @typescript-eslint/no-unused-vars
-// let a = 0;
-// let b = set.length - 1;
-// while (a <= b) {
-// const m = (a + b) >> 1;
-// const x = set[m];
-// if (item < x) b = m - 1;
-// else if (item > x) a = m + 1;
-// else return set;
-// }
-// return array_insert(set, a, item);
-// }
+function set_add<T>(set: T[], item: T) {
+ // eslint-disable-line @typescript-eslint/no-unused-vars
+ let a = 0;
+ let b = set.length - 1;
+ while (a <= b) {
+ const m = (a + b) >> 1;
+ const x = set[m];
+ if (item < x) b = m - 1;
+ else if (item > x) a = m + 1;
+ else return set;
+ }
+ return array_insert(set, a, item);
+}
// function set_delete<T>(set: T[], item: T) {
// let a = 0;
diff --git a/types.d.ts b/types.d.ts
index 3d61fbd..0b901d5 100644
--- a/types.d.ts
+++ b/types.d.ts
@@ -75,6 +75,7 @@ export interface Game {
tracks: number[];
trash: CardId[][];
triggered_track_effects: number[];
+ untriggered_track_effects: number[];
used_medallions: number[];
glory_current_year?: boolean[] | null;
fascist: 0 | 1 | 2;
@@ -190,7 +191,7 @@ export interface PlayerCard extends CardBase {
icons: Icon[];
}
-export type EffectSource = 'player_event' | 'fascist_event' | 'fascist_test' | 'track_icon' | 'momentum' | 'tr0' | 'tr1' | 'tr2' | 'tr3' | 'tr4';
+export type EffectSource = 'player_event' | 'fascist_event' | 'fascist_test' | 'track_icon' | 'momentum' | 'tr0' | 'tr1' | 'tr2' | 'tr3' | 'tr4' | 'volunteers';
export interface Effect {
type: