summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data.js9
-rw-r--r--data.ts34
-rw-r--r--play.js2
-rw-r--r--play.ts3
-rw-r--r--rules.js131
-rw-r--r--rules.ts217
-rw-r--r--types.d.ts30
7 files changed, 305 insertions, 121 deletions
diff --git a/data.js b/data.js
index 3de51ab..d4fb1b2 100644
--- a/data.js
+++ b/data.js
@@ -1,6 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
-exports.TRASH = exports.TOWARDS_CENTER = exports.SELF = exports.PLAYER_WITH_MOST_HERO_POINTS = exports.OTHER_PLAYERS = exports.ON = exports.OFF = exports.TEAMWORK_BONUS = exports.MORALE_BONUS = exports.FOREIGN_AID = exports.SOVIET_SUPPORT = exports.GOVERNMENT = exports.COLLECTIVIZATION = exports.CLOSEST_TO_VICTORY = exports.CLOSEST_TO_DEFEAT = exports.LIBERTY = exports.ANY = exports.MODERATES_ID = exports.COMMUNISTS_ID = exports.ANARCHISTS_ID = exports.MODERATE = exports.COMMUNIST = exports.ANARCHIST = void 0;
+exports.FRONTS = exports.DEFEAT = exports.VICTORY = exports.TRASH = exports.TOWARDS_CENTER = exports.SELF = exports.PLAYER_WITH_MOST_HERO_POINTS = exports.OTHER_PLAYERS = exports.ON = exports.OFF = exports.TEAMWORK_BONUS = exports.MORALE_BONUS = exports.FOREIGN_AID = exports.SOVIET_SUPPORT = exports.GOVERNMENT = exports.COLLECTIVIZATION = exports.CLOSEST_TO_VICTORY = exports.CLOSEST_TO_DEFEAT = exports.LIBERTY = exports.ANY = exports.MODERATES_ID = exports.COMMUNISTS_ID = exports.ANARCHISTS_ID = exports.MODERATE = exports.COMMUNIST = exports.ANARCHIST = void 0;
+exports.create_effect = create_effect;
const LIBERTY = 0;
exports.LIBERTY = LIBERTY;
const COLLECTIVIZATION = 1;
@@ -31,6 +32,8 @@ const CLOSEST_TO_DEFEAT = 'd';
exports.CLOSEST_TO_DEFEAT = CLOSEST_TO_DEFEAT;
const CLOSEST_TO_VICTORY = 'v';
exports.CLOSEST_TO_VICTORY = CLOSEST_TO_VICTORY;
+const FRONTS = [ARAGON, MADRID, NORTHERN, SOUTHERN];
+exports.FRONTS = FRONTS;
const TOWARDS_CENTER = 10;
exports.TOWARDS_CENTER = TOWARDS_CENTER;
const AWAY_FROM_CENTER = 11;
@@ -54,6 +57,10 @@ const COMMUNIST = 'Communist';
exports.COMMUNIST = COMMUNIST;
const MODERATE = 'Moderate';
exports.MODERATE = MODERATE;
+const VICTORY = 'Victory';
+exports.VICTORY = VICTORY;
+const DEFEAT = 'Defeat';
+exports.DEFEAT = DEFEAT;
function create_effect(type, target, value) {
return {
type,
diff --git a/data.ts b/data.ts
index 1ab4a98..806c9b8 100644
--- a/data.ts
+++ b/data.ts
@@ -1,4 +1,4 @@
-import { Card, Effect, FactionId, Player, StaticData } from './types';
+import { Card, Effect, FactionId, FrontId, Player, StaticData } from './types';
const LIBERTY = 0;
const COLLECTIVIZATION = 1;
@@ -22,6 +22,7 @@ const NORTHERN = 'n';
const SOUTHERN = 's';
const CLOSEST_TO_DEFEAT = 'd';
const CLOSEST_TO_VICTORY = 'v';
+const FRONTS: FrontId[] = [ARAGON, MADRID, NORTHERN, SOUTHERN];
const TOWARDS_CENTER = 10;
const AWAY_FROM_CENTER = 11;
@@ -39,7 +40,23 @@ const ANARCHIST = 'Anarchist' as Player;
const COMMUNIST = 'Communist' as Player;
const MODERATE = 'Moderate' as Player;
+const VICTORY = 'Victory';
+const DEFEAT = 'Defeat';
+
+function create_effect(
+ type: Effect['type'],
+ target: Effect['target'],
+ value: Effect['value']
+): Effect {
+ return {
+ type,
+ target,
+ value,
+ };
+}
+
export {
+ create_effect,
ANARCHIST,
COMMUNIST,
MODERATE,
@@ -63,19 +80,12 @@ export {
SELF,
TOWARDS_CENTER,
TRASH,
+ VICTORY,
+ DEFEAT,
+ FRONTS,
};
-function create_effect(
- type: Effect['type'],
- target: Effect['target'],
- value: Effect['value']
-): Effect {
- return {
- type,
- target,
- value,
- };
-}
+
const data: StaticData = {
cards: [
diff --git a/play.js b/play.js
index 38d98dd..ffa817a 100644
--- a/play.js
+++ b/play.js
@@ -289,7 +289,7 @@ function on_update() {
}
for (let front_id of Object.keys(view.fronts)) {
const front_data = view.fronts[front_id];
- ui.fronts[front_id].value.replaceChildren(front_data.value);
+ ui.fronts[front_id].value.replaceChildren(front_data.status !== null ? front_data.status : front_data.value);
ui.fronts[front_id].contributions.replaceChildren();
for (let faction_id of front_data.contributions) {
ui.fronts[front_id].contributions.appendChild(ui.tokens_on_front[front_id][faction_id]);
diff --git a/play.ts b/play.ts
index e192d6d..b97839b 100644
--- a/play.ts
+++ b/play.ts
@@ -381,7 +381,8 @@ function on_update() {
for (let front_id of Object.keys(view.fronts)) {
const front_data = view.fronts[front_id];
- ui.fronts[front_id].value.replaceChildren(front_data.value);
+ // ui.fronts[front_id].value.replaceChildren(front_data.value);
+ ui.fronts[front_id].value.replaceChildren(front_data.status !== null ? front_data.status : front_data.value);
ui.fronts[front_id].contributions.replaceChildren();
for(let faction_id of front_data.contributions) {
ui.fronts[front_id].contributions.appendChild(ui.tokens_on_front[front_id][faction_id]);
diff --git a/rules.js b/rules.js
index f9cd8e8..667c7a0 100644
--- a/rules.js
+++ b/rules.js
@@ -189,9 +189,7 @@ function insert_before_active_node(node, engine = game.engine) {
insert_before_or_after_active_node(node, 'before', engine);
}
function next() {
- console.log('next');
const node = get_active_node(game.engine);
- console.log('node', node);
if (node.t === function_node && engine_functions[node.f]) {
const args = node.a;
if (args) {
@@ -241,7 +239,10 @@ function game_view(state, player) {
triggered_track_effects: game.triggered_track_effects,
year: game.year,
};
- if (player !== game.active) {
+ if (game.state === 'game_over') {
+ view.prompt = game.victory;
+ }
+ else if (player !== game.active) {
let inactive = states[game.state].inactive || game.state;
view.prompt = `Waiting for ${game.active} to ${inactive}.`;
}
@@ -275,18 +276,22 @@ function setup(seed, _scenario, _options) {
a: {
value: -2,
contributions: [],
+ status: null,
},
m: {
value: -2,
contributions: [],
+ status: null,
},
n: {
value: -2,
contributions: [],
+ status: null,
},
s: {
value: -2,
contributions: [],
+ status: null,
},
},
glory: [],
@@ -470,13 +475,20 @@ states.attack_front = {
const node = get_active_node();
const front = node.a.t;
view.prompt = 'Attack ' + front_names[front];
+ let targets = [];
if (front === 'd' || front === 'v') {
- const fronts = get_fronts_closest_to(front);
- fronts.forEach((id) => gen_action('front', id));
+ targets = get_fronts_closest_to(front);
+ }
+ else if (game.fronts[front].status === data_1.DEFEAT) {
+ targets = get_fronts_closest_to('d');
+ }
+ else if (game.fronts[front].status === data_1.VICTORY) {
+ targets = get_fronts_to_add_to(data_1.ANY);
}
else {
- gen_action_front(front);
+ targets.push(front);
}
+ targets.forEach((id) => gen_action('front', id));
},
front(f) {
const node = get_active_node();
@@ -579,13 +591,18 @@ states.gain_hero_points = {
},
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;
- }
+ gain_hero_points(get_active_faction(), value);
resolve_active_and_proceed();
},
};
+states.game_over = {
+ get inactive() {
+ return game.victory;
+ },
+ prompt() {
+ view.prompt = game.victory;
+ },
+};
states.lose_hero_points = {
inactive: 'choose a Player',
prompt() {
@@ -780,11 +797,35 @@ function end_of_year() {
game.year++;
start_year();
}
+function gain_hero_points_in_player_order(factions, value) {
+ for (const f of get_player_order()) {
+ if (factions.includes(f)) {
+ gain_hero_points(f, value);
+ }
+ }
+}
+function gain_hero_points(faction_id, value) {
+ if (game.hero_points.pool === 0) {
+ return;
+ }
+ const gain = Math.min(game.hero_points.pool, value);
+ game.hero_points.pool -= gain;
+ game.hero_points[faction_id] += gain;
+ logi(`${get_player(faction_id)} +${gain} ${gain === 1 ? 'Hero Point' : 'Hero Points'}`);
+}
+function game_over(result, victory) {
+ insert_after_active_node(create_leaf_node('game_over', 'None'));
+ game.result = result;
+ game.victory = victory;
+ log_br();
+ log(game.victory);
+}
function resolve_fascist_test() {
- console.log('resolve fascist test');
log_h2('Fascist Test', 'fascist');
const test = get_current_event().test;
- const test_passed = game.fronts[test.front].value >= test.value;
+ const status = game.fronts[test.front].status;
+ const test_passed = status === data_1.VICTORY ||
+ (status !== data_1.DEFEAT && game.fronts[test.front].value >= test.value);
if (test_passed) {
log('The Test is passed');
}
@@ -804,7 +845,7 @@ function get_fronts_to_add_to(target) {
return get_fronts_closest_to(target);
}
else if (target === data_1.ANY) {
- return ['a', 'm', 'n', 's'];
+ return data_1.FRONTS.filter((id) => game.fronts[id].status === null);
}
else {
return [target];
@@ -842,21 +883,47 @@ function update_bonus(bonus_id, status) {
game.bonuses[bonus_id] = status;
logi(`${bonus_names[bonus_id]} ${status === data_1.ON ? 'on' : 'off'}`);
}
-function update_front(f, change, faction_id = null) {
- const player_token_on_front = faction_id !== null && game.fronts[f].contributions.includes(faction_id);
+function update_front(front_id, change, faction_id = null) {
+ const player_token_on_front = faction_id !== null &&
+ game.fronts[front_id].contributions.includes(faction_id);
if (game.bonuses[data_1.TEAMWORK_BONUS] === data_1.ON &&
change > 0 &&
faction_id !== null &&
!player_token_on_front &&
- game.fronts[f].contributions.length > 0) {
+ game.fronts[front_id].contributions.length > 0) {
change += 1;
}
- game.fronts[f].value += change;
- logi(`${front_names[f]}: ${change > 0 ? '+' : ''}${change}`);
+ game.fronts[front_id].value += change;
+ logi(`${front_names[front_id]}: ${change > 0 ? '+' : ''}${change}`);
if (faction_id !== null &&
- !game.fronts[f].contributions.includes(faction_id)) {
- game.fronts[f].contributions.push(faction_id);
+ !game.fronts[front_id].contributions.includes(faction_id)) {
+ game.fronts[front_id].contributions.push(faction_id);
}
+ if (game.fronts[front_id].value >= 10) {
+ victory_on_a_front(front_id);
+ }
+ else if (game.fronts[front_id].value <= -10) {
+ defeat_on_a_front(front_id);
+ }
+}
+function defeat_on_a_front(front_id) {
+ game.fronts[front_id].status = data_1.DEFEAT;
+ log('Defeat on ' + get_front_name(front_id));
+ if (front_id === 'm' || get_defeated_front_count() == 2) {
+ game_over('None', 'All players lose the game!');
+ return;
+ }
+ insert_after_active_node(create_effects_node([
+ (0, data_1.create_effect)('bonus', data_1.MORALE_BONUS, data_1.OFF),
+ (0, data_1.create_effect)('track', data_1.COLLECTIVIZATION, -1),
+ (0, data_1.create_effect)('track', data_1.SOVIET_SUPPORT, -1),
+ (0, data_1.create_effect)('track', data_1.FOREIGN_AID, -1),
+ ]));
+}
+function victory_on_a_front(front_id) {
+ game.fronts[front_id].status = data_1.VICTORY;
+ log('Victory on ' + get_front_name(front_id));
+ gain_hero_points_in_player_order(game.fronts[front_id].contributions, 3);
}
function create_effects_node(effects) {
const nodes = effects.reduce((accrued, current) => {
@@ -923,7 +990,12 @@ function lose_hero_point(faction, value) {
}
}
function get_fronts_closest_to(target) {
- const values = Object.values(game.fronts).map((front) => front.value);
+ const values = Object.values(game.fronts).reduce((accrued, current) => {
+ if (current.status === null) {
+ accrued.push(current.value);
+ }
+ return accrued;
+ }, []);
const targetValue = target === 'd' ? Math.min(...values) : Math.max(...values);
return Object.keys(game.fronts).filter((frontId) => game.fronts[frontId].value === targetValue);
}
@@ -966,9 +1038,21 @@ function get_active_faction_id() {
function get_faction_id(player) {
return player_faction_map[player];
}
+function get_front_name(id) {
+ return front_names[id];
+}
function get_current_event() {
return cards[game.current_events[game.current_events.length - 1]];
}
+function get_defeated_front_count() {
+ let count = 0;
+ for (const front_id of data_1.FRONTS) {
+ if (game.fronts[front_id].status === data_1.DEFEAT) {
+ count++;
+ }
+ }
+ return count;
+}
function get_icon_count_in_tableau(icon, faction = get_active_faction_id()) {
let count = 0;
for (const c of game.tableaus[faction]) {
@@ -1059,10 +1143,11 @@ function list_deck(id) {
const deck = [];
const card_list = id === 'fascist' ? fascist_decks[game.year] : faction_cards[id];
card_list.forEach((card) => {
- if (id === 'fascist' && (game.discard.f.includes(card))) {
+ if (id === 'fascist' && game.discard.f.includes(card)) {
return;
}
- else if (id !== 'fascist' && (game.hands[id].includes(card) || game.discard[id].includes(card))) {
+ else if (id !== 'fascist' &&
+ (game.hands[id].includes(card) || game.discard[id].includes(card))) {
return;
}
deck.push(card);
diff --git a/rules.ts b/rules.ts
index c6f01ab..2fea19e 100644
--- a/rules.ts
+++ b/rules.ts
@@ -6,6 +6,8 @@ import {
EngineNode,
EventCard,
FactionId,
+ Front,
+ FrontId,
FunctionNode,
Game,
Icon,
@@ -46,6 +48,10 @@ import data, {
TEAMWORK_BONUS,
MORALE_BONUS,
OFF,
+ VICTORY,
+ DEFEAT,
+ FRONTS,
+ create_effect,
// StaticData,
// PLAYER_WITH_MOST_HERO_POINTS,
} from './data';
@@ -177,7 +183,7 @@ const resolved = 1;
function create_leaf_node(
state: string,
- faction: FactionId,
+ faction: FactionId | 'None',
args?: any
): LeafNode {
return {
@@ -272,41 +278,11 @@ function get_active(
return null;
}
-// function get_active_node_parent(engine: EngineNode[]): EngineNode[] | null {
-// for (let i of engine) {
-// if ((i.t === leaf_node || i.t === function_node) && i.r !== resolved) {
-// return engine;
-// }
-// if (i.t === seq_node) {
-// const next_child = get_active_node_parent(i.c);
-// if (next_child !== null) {
-// return next_child;
-// }
-// }
-// }
-// return null;
-// }
-
function get_active_node(
engine: EngineNode[] = game.engine
): FunctionNode | LeafNode | null {
const a = get_active(engine);
return a === null ? null : a.node;
- // for (let i of engine) {
- // if ((i.t === leaf_node || i.t === function_node) && i.r !== resolved) {
- // return i;
- // }
- // // if (i.t === function_node && i.r !== resolved) {
- // // return i;
- // // }
- // if (i.t === seq_node) {
- // const next_child = get_active_node(i.c);
- // if (next_child !== null) {
- // return next_child;
- // }
- // }
- // }
- // return null;
}
function get_active_node_args(): any {
@@ -348,9 +324,7 @@ function insert_before_active_node(
}
function next() {
- console.log('next');
const node = get_active_node(game.engine);
- console.log('node', node);
if (node.t === function_node && engine_functions[node.f]) {
const args = node.a;
if (args) {
@@ -410,7 +384,9 @@ function game_view(state: Game, player: Player) {
year: game.year,
};
- if (player !== game.active) {
+ if (game.state === 'game_over') {
+ view.prompt = game.victory;
+ } else if (player !== game.active) {
let inactive = states[game.state].inactive || game.state;
view.prompt = `Waiting for ${game.active} to ${inactive}.`;
} else {
@@ -448,18 +424,22 @@ export function setup(seed: number, _scenario: string, _options: unknown) {
a: {
value: -2,
contributions: [],
+ status: null,
},
m: {
value: -2,
contributions: [],
+ status: null,
},
n: {
value: -2,
contributions: [],
+ status: null,
},
s: {
value: -2,
contributions: [],
+ status: null,
},
},
glory: [],
@@ -660,7 +640,7 @@ states.add_to_front = {
gen_action_front(f);
}
},
- front(f: string) {
+ front(f: FrontId) {
const value = get_active_node_args().v;
update_front(f, value);
resolve_active_and_proceed();
@@ -673,14 +653,20 @@ states.attack_front = {
const node = get_active_node();
const front = node.a.t;
view.prompt = 'Attack ' + front_names[front];
+ let targets = [];
+
if (front === 'd' || front === 'v') {
- const fronts = get_fronts_closest_to(front);
- fronts.forEach((id) => gen_action('front', id));
+ targets = get_fronts_closest_to(front);
+ } else if (game.fronts[front].status === DEFEAT) {
+ targets = get_fronts_closest_to('d')
+ } else if (game.fronts[front].status === VICTORY) {
+ targets = get_fronts_to_add_to(ANY);
} else {
- gen_action_front(front);
+ targets.push(front);
}
+ targets.forEach((id) => gen_action('front', id));
},
- front(f: string) {
+ front(f: FrontId) {
const node = get_active_node();
const value = node.a.v;
update_front(f, value);
@@ -710,7 +696,7 @@ states.choose_area_ap = {
// TODO: insert action in case other bonus is OFF and AP left
resolve_active_and_proceed();
},
- front(f: string) {
+ front(f: FrontId) {
const s: number = get_active_node_args().strength;
update_front(f, s, get_active_faction_id());
resolve_active_and_proceed();
@@ -786,14 +772,20 @@ states.gain_hero_points = {
},
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;
- }
+ gain_hero_points(get_active_faction(), value);
resolve_active_and_proceed();
},
};
+states.game_over = {
+ get inactive() {
+ return game.victory;
+ },
+ prompt() {
+ view.prompt = game.victory;
+ },
+};
+
states.lose_hero_points = {
inactive: 'choose a Player',
prompt() {
@@ -865,7 +857,7 @@ states.move_track_up_or_down = {
states.player_turn = {
inactive: 'play their turn',
prompt() {
- const faction_id = get_faction_id(game.active);
+ const faction_id = get_faction_id(game.active as Player);
const can_spend_hp = game.hero_points[faction_id] > 0;
const can_play_card = game.hands[faction_id].includes(
game.chosen_cards[faction_id]
@@ -893,7 +885,7 @@ states.player_turn = {
resolve_active_and_proceed();
},
play_for_ap() {
- const faction_id = get_faction_id(game.active);
+ const faction_id = get_faction_id(game.active as Player);
const card = game.chosen_cards[faction_id];
log_h3(`${game.active} plays ${cards[card].title} for the Action Points`);
array_remove(game.hands[faction_id], game.hands[faction_id].indexOf(card));
@@ -909,7 +901,7 @@ states.player_turn = {
next();
},
play_for_event() {
- const faction_id = get_faction_id(game.active);
+ const faction_id = get_faction_id(game.active as Player);
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);
@@ -922,7 +914,7 @@ states.player_turn = {
spend_hp() {
insert_before_active_node({
t: leaf_node,
- p: get_faction_id(game.active),
+ p: get_faction_id(game.active as Player),
s: 'spend_hero_points',
});
// insert spend hero points node before current node
@@ -1083,12 +1075,46 @@ function end_of_year() {
start_year();
}
+function gain_hero_points_in_player_order(factions: FactionId[], value) {
+ for (const f of get_player_order()) {
+ if (factions.includes(f)) {
+ gain_hero_points(f, value);
+ }
+ }
+}
+
+function gain_hero_points(faction_id: FactionId, value: number) {
+ if (game.hero_points.pool === 0) {
+ return;
+ }
+ const gain = Math.min(game.hero_points.pool, value);
+ game.hero_points.pool -= gain;
+ game.hero_points[faction_id] += gain;
+ logi(
+ `${get_player(faction_id)} +${gain} ${
+ gain === 1 ? 'Hero Point' : 'Hero Points'
+ }`
+ );
+}
+
+function game_over(result: Player | 'None', victory: string) {
+ insert_after_active_node(create_leaf_node('game_over', 'None'));
+ // game.state = 'game_over';
+ // game.active = 'None';
+ game.result = result;
+ game.victory = victory;
+ log_br();
+ log(game.victory);
+}
+
function resolve_fascist_test() {
- console.log('resolve fascist test');
log_h2('Fascist Test', 'fascist');
const test = get_current_event().test;
- const test_passed = game.fronts[test.front].value >= test.value;
+ const status = game.fronts[test.front].status;
+ const test_passed =
+ status === VICTORY ||
+ (status !== DEFEAT && game.fronts[test.front].value >= test.value);
if (test_passed) {
log('The Test is passed');
} else {
@@ -1110,7 +1136,7 @@ function get_fronts_to_add_to(target: string): string[] {
if (target === CLOSEST_TO_DEFEAT || target === CLOSEST_TO_VICTORY) {
return get_fronts_closest_to(target);
} else if (target === ANY) {
- return ['a', 'm', 'n', 's'];
+ return FRONTS.filter((id) => game.fronts[id].status === null);
} else {
return [target];
}
@@ -1162,31 +1188,66 @@ function update_bonus(bonus_id: number, status: number) {
// TODO: acccount for victory / defeat of front
function update_front(
- f: string,
+ // f: string,
+ front_id: FrontId,
change: number,
faction_id: FactionId | null = null
) {
+ // Check teamwork bonus
const player_token_on_front =
- faction_id !== null && game.fronts[f].contributions.includes(faction_id);
+ faction_id !== null &&
+ game.fronts[front_id].contributions.includes(faction_id);
if (
game.bonuses[TEAMWORK_BONUS] === ON &&
change > 0 &&
faction_id !== null &&
!player_token_on_front &&
- game.fronts[f].contributions.length > 0
+ game.fronts[front_id].contributions.length > 0
) {
change += 1;
}
- game.fronts[f].value += change;
- logi(`${front_names[f]}: ${change > 0 ? '+' : ''}${change}`);
+
+ game.fronts[front_id].value += change;
+ logi(`${front_names[front_id]}: ${change > 0 ? '+' : ''}${change}`);
+
+ // Add token to front if player contributed
if (
faction_id !== null &&
- !game.fronts[f].contributions.includes(faction_id)
+ !game.fronts[front_id].contributions.includes(faction_id)
) {
- game.fronts[f].contributions.push(faction_id);
+ game.fronts[front_id].contributions.push(faction_id);
+ }
+
+ // Check victory / defeat on a front
+ if (game.fronts[front_id].value >= 10) {
+ victory_on_a_front(front_id);
+ } else if (game.fronts[front_id].value <= -10) {
+ defeat_on_a_front(front_id);
}
}
+function defeat_on_a_front(front_id: FrontId) {
+ game.fronts[front_id].status = DEFEAT;
+ log('Defeat on ' + get_front_name(front_id));
+ // Check game end
+ if (front_id === 'm' || get_defeated_front_count() == 2) {
+ game_over('None', 'All players lose the game!');
+ return;
+ }
+ insert_after_active_node(create_effects_node([
+ create_effect('bonus', MORALE_BONUS, OFF),
+ create_effect('track', COLLECTIVIZATION, -1),
+ create_effect('track', SOVIET_SUPPORT, -1),
+ create_effect('track', FOREIGN_AID, -1),
+ ]));
+}
+
+function victory_on_a_front(front_id: FrontId) {
+ game.fronts[front_id].status = VICTORY;
+ log('Victory on ' + get_front_name(front_id));
+ gain_hero_points_in_player_order(game.fronts[front_id].contributions, 3);
+}
+
function create_effects_node(effects: Effect[]): EngineNode {
const nodes = effects.reduce((accrued: EngineNode[], current: Effect) => {
const node = resolve_effect(current);
@@ -1359,7 +1420,15 @@ function lose_hero_point(faction: FactionId, value: number) {
// #region FRONTS
function get_fronts_closest_to(target: 'd' | 'v') {
- const values = Object.values(game.fronts).map((front) => front.value);
+ const values = Object.values(game.fronts).reduce(
+ (accrued: number[], current: Front) => {
+ if (current.status === null) {
+ accrued.push(current.value);
+ }
+ return accrued;
+ },
+ []
+ );
const targetValue =
target === 'd' ? Math.min(...values) : Math.max(...values);
return Object.keys(game.fronts).filter(
@@ -1453,12 +1522,26 @@ function get_faction_id(player: Player): FactionId {
return player_faction_map[player];
}
+function get_front_name(id: FrontId | 'd' | 'v') {
+ return front_names[id];
+}
+
function get_current_event(): EventCard {
return cards[
game.current_events[game.current_events.length - 1]
] as EventCard;
}
+function get_defeated_front_count() {
+ let count = 0;
+ for (const front_id of FRONTS) {
+ if (game.fronts[front_id].status === DEFEAT) {
+ count++;
+ }
+ }
+ return count;
+}
+
function get_icon_count_in_tableau(
icon: Icon,
faction: FactionId = get_active_faction_id()
@@ -1560,15 +1643,19 @@ function set_delete<T>(set: T[], item: T) {
function list_deck(id: FactionId | 'fascist') {
const deck = [];
- const card_list = id === 'fascist' ? fascist_decks[game.year] : faction_cards[id];
+ const card_list =
+ id === 'fascist' ? fascist_decks[game.year] : faction_cards[id];
card_list.forEach((card) => {
- if (id === 'fascist' && (game.discard.f.includes(card))) {
- return
- } else if (id !== 'fascist' && (game.hands[id].includes(card) || game.discard[id].includes(card))) {
+ if (id === 'fascist' && game.discard.f.includes(card)) {
+ return;
+ } else if (
+ id !== 'fascist' &&
+ (game.hands[id].includes(card) || game.discard[id].includes(card))
+ ) {
return;
}
- deck.push(card)
- })
+ deck.push(card);
+ });
return deck;
}
diff --git a/types.d.ts b/types.d.ts
index da8fe25..f0c4772 100644
--- a/types.d.ts
+++ b/types.d.ts
@@ -10,6 +10,12 @@ export type CardId = Brand<number, 'CardId'>;
export type FactionId = Brand<string, 'FactionId'>;
export type FrontId = 'a' | 'm' | 'n' | 's';
+export interface Front {
+ value: number;
+ contributions: FactionId[];
+ status: 'Victory' | 'Defeat' | null;
+}
+
export interface Game {
[index: number]: any;
seed: number;
@@ -17,7 +23,7 @@ export interface Game {
undo: Game[];
turn: number;
year: number;
- active: Player | null;
+ active: Player | 'None' | null;
state: string | null;
bag_of_glory: FactionId[];
blank_markers: number[][];
@@ -27,22 +33,10 @@ export interface Game {
discard: Record<FactionId | 'f', number[]>;
engine: EngineNode[];
fronts: {
- a: {
- value: number;
- contributions: FactionId[];
- };
- m: {
- value: number;
- contributions: FactionId[];
- };
- n: {
- value: number;
- contributions: FactionId[];
- };
- s: {
- value: number;
- contributions: FactionId[];
- };
+ a: Front;
+ m: Front;
+ n: Front;
+ s: Front;
};
glory: FactionId[];
hands: Record<FactionId, CardId[]>;
@@ -112,7 +106,7 @@ export interface SeqNode {
export interface LeafNode {
t: 'l';
s: string; // State
- p: FactionId; // Player
+ p: FactionId | 'None'; // Player
a?: any; // args
r?: 0 | 1; // 1 if resolved
}