summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--land-and-freedom.css34
-rw-r--r--land-and-freedom.scss32
-rw-r--r--play.js71
-rw-r--r--play.ts84
-rw-r--r--rules.js584
-rw-r--r--rules.ts685
-rw-r--r--tsconfig.json2
7 files changed, 1255 insertions, 237 deletions
diff --git a/land-and-freedom.css b/land-and-freedom.css
index 6b4db57..e4c95a9 100644
--- a/land-and-freedom.css
+++ b/land-and-freedom.css
@@ -328,6 +328,21 @@ main {
background-repeat: no-repeat;
border-radius: 4px;
box-shadow: 0 0 0 1px #333;
+ transition-property: top, left;
+ transition-duration: 700ms;
+ transition-timing-function: ease;
+}
+
+.card.action,
+.front.action,
+.standee.action {
+ box-shadow: 0 0 0 3px white;
+}
+
+.card.action:hover,
+.front.action:hover,
+.standee.action:hover {
+ box-shadow: 0 0 0 3px yellow;
}
.standee[data-standee-id="0"] {
@@ -349,3 +364,22 @@ main {
.standee[data-standee-id="4"] {
background-image: url("images/standees/standee_4.png");
}
+
+#log {
+ background-color: floralwhite;
+}
+
+#log .h1, #log .h2 {
+ font-size: 10px;
+ padding-top: 2px;
+ padding-bottom: 2px;
+ text-align: center;
+}
+
+#log .h1 {
+ background-color: hsl(4, 40%, 73%);
+}
+
+#log .h2 {
+ background-color: hsl(250, 40%, 83%);
+}
diff --git a/land-and-freedom.scss b/land-and-freedom.scss
index 42f75e2..fc8e962 100644
--- a/land-and-freedom.scss
+++ b/land-and-freedom.scss
@@ -1,6 +1,9 @@
// @use "sass:math";
@use 'sass:map';
+$selectable-color: white; // yellow;
+$selected-color: yellow; //blue;
+
main {
// background-color: rgb(213, 196, 131);
background-color: darkolivegreen;
@@ -106,11 +109,40 @@ main {
background-repeat: no-repeat;
border-radius: 4px;
box-shadow: 0 0 0 1px #333;
+ transition-property: top, left;
+ transition-duration: 700ms;
+ transition-timing-function: ease;
// opacity: 0.6;
}
+.card.action,
+.front.action,
+.standee.action {
+ box-shadow: 0 0 0 3px $selectable-color;
+}
+
+.card.action:hover,
+.front.action:hover,
+.standee.action:hover {
+ box-shadow: 0 0 0 3px $selected-color;
+}
+
+// .standee.action:hover {
+// box-shadow: 0 0 0 2px blue;
+// }
+
@for $i from 0 through 4 {
.standee[data-standee-id='#{$i}'] {
background-image: url('images/standees/standee_#{$i}.png');
}
}
+
+#log { background-color: floralwhite; }
+#log .h1, #log .h2 {
+ font-size: 10px;
+ padding-top: 2px;
+ padding-bottom: 2px;
+ text-align: center;
+}
+#log .h1 { background-color: hsl(4, 40%, 73%); }
+#log .h2 { background-color: hsl(250, 40%, 83%); } \ No newline at end of file
diff --git a/play.js b/play.js
index af1eccb..9c1b6f2 100644
--- a/play.js
+++ b/play.js
@@ -46,30 +46,30 @@ const LAYOUT_TRACKS = [
spaces.appendChild(element);
const frontValueElement = (ui.frontValues[front.id] = document.createElement('span'));
frontValueElement.classList.add('value');
+ register_action(element, 'front', id);
element.appendChild(frontValueElement);
});
})();
console.log('ui', ui);
-// @ts-ignore
-function register_action(e, _action, _id) {
- // e.my_action = action
- // e.my_id = id
+function register_action(e, action, id) {
+ e.my_action = action;
+ e.my_id = id;
e.onmousedown = on_click_action;
action_register.push(e);
}
function on_click_action(evt) {
+ console.log('on_click_action', evt);
if (evt.button === 0)
if (send_action(evt.target.my_action, evt.target.my_id))
evt.stopPropagation();
}
-// function is_action(action, arg) {
-// if (arg === undefined) return !!(view.actions && view.actions[action] === 1);
-// return !!(
-// view.actions &&
-// view.actions[action] &&
-// view.actions[action].includes(arg)
-// );
-// }
+function is_action(action, arg) {
+ if (arg === undefined)
+ return !!(view.actions && view.actions[action] === 1);
+ return !!(view.actions &&
+ view.actions[action] &&
+ view.actions[action].includes(arg));
+}
let on_init_once = false;
function on_init() {
console.log('on_init');
@@ -97,6 +97,7 @@ function on_init() {
e.className = 'standee';
e.setAttribute('data-standee-id', '' + s);
register_action(e, 'standee', s);
+ ui.tracks.appendChild(ui.standees[s]);
}
console.log('standees', ui.standees);
// create card elements
@@ -106,6 +107,7 @@ function on_init() {
e.setAttribute('data-card-id', '' + data.cards[c].id);
register_action(e, 'card', c);
}
+ console.log('action_register', action_register[0]);
}
// @ts-ignore
function on_update() {
@@ -128,15 +130,54 @@ function on_update() {
for (let c of view.hand)
ui.hand.appendChild(ui.cards[c]);
for (let i = 0; i < view.tracks.length; i++) {
- ui.tracks.appendChild(ui.standees[i]);
+ // ui.tracks.appendChild(ui.standees[i]);
ui.standees[i].style.left = LAYOUT_TRACKS[i][view.tracks[i]][0] + 'px';
ui.standees[i].style.top = LAYOUT_TRACKS[i][view.tracks[i]][1] + 'px';
}
for (let frontId of Object.keys(view.fronts)) {
ui.frontValues[frontId].replaceChildren(view.fronts[frontId]);
}
- // for (let e of action_register)
- // e.classList.toggle('action', is_action(e.my_action, e.my_id));
+ for (let e of action_register)
+ e.classList.toggle('action', is_action(e.my_action, e.my_id));
action_button('next', 'Next');
action_button('undo', 'Undo');
+ action_button('add_glory', 'Add Glory');
+}
+// @ts-ignore
+function on_log(text) {
+ let p = document.createElement("div");
+ if (text.match(/^>/)) {
+ text = text.substring(1);
+ p.className = 'i';
+ }
+ text = text.replace(/&/g, "&amp;");
+ text = text.replace(/</g, "&lt;");
+ text = text.replace(/>/g, "&gt;");
+ // text = text.replace(/C(\d+)/g, sub_card_name)
+ // text = text.replace(/S(\d+)/g, sub_space_name)
+ // text = text.replace(/U(\d+)/g, sub_unit_name)
+ // TODO dice icons
+ // text = text.replace(/\bD\d\b/g, sub_icon)
+ if (text.match(/^\.h1/)) {
+ text = text.substring(4);
+ p.className = 'h1';
+ }
+ else if (text.match(/^\.h2/)) {
+ text = text.substring(4);
+ p.className = 'h2';
+ }
+ else if (text.match(/^\.h3\.allies/)) {
+ text = text.substring(10);
+ p.className = 'h3 allies';
+ }
+ else if (text.match(/^\.h3\.germans/)) {
+ text = text.substring(11);
+ p.className = 'h3 germans';
+ }
+ else if (text.match(/^\.h3/)) {
+ text = text.substring(4);
+ p.className = 'h3';
+ }
+ p.innerHTML = text;
+ return p;
}
diff --git a/play.ts b/play.ts
index e941c05..26e942c 100644
--- a/play.ts
+++ b/play.ts
@@ -63,38 +63,40 @@ const LAYOUT_TRACKS = [
spaces.appendChild(element);
const frontValueElement = (ui.frontValues[front.id] = document.createElement('span'));
frontValueElement.classList.add('value');
+ register_action(element, 'front', id);
element.appendChild(frontValueElement);
});
})();
console.log('ui', ui);
-// @ts-ignore
+
function register_action(
- e: HTMLElement,
- _action: string,
- _id: string | number
+ e: HTMLElement & {my_action?: string; my_id?: string | number},
+ action: string,
+ id: string | number
) {
- // e.my_action = action
- // e.my_id = id
+ e.my_action = action
+ e.my_id = id
e.onmousedown = on_click_action;
action_register.push(e);
}
function on_click_action(evt) {
+ console.log('on_click_action', evt);
if (evt.button === 0)
if (send_action(evt.target.my_action, evt.target.my_id))
evt.stopPropagation();
}
-// function is_action(action, arg) {
-// if (arg === undefined) return !!(view.actions && view.actions[action] === 1);
-// return !!(
-// view.actions &&
-// view.actions[action] &&
-// view.actions[action].includes(arg)
-// );
-// }
+function is_action(action, arg) {
+ if (arg === undefined) return !!(view.actions && view.actions[action] === 1);
+ return !!(
+ view.actions &&
+ view.actions[action] &&
+ view.actions[action].includes(arg)
+ );
+}
let on_init_once = false;
@@ -128,6 +130,7 @@ function on_init() {
e.className = 'standee';
e.setAttribute('data-standee-id', '' + s)
register_action(e, 'standee', s);
+ ui.tracks.appendChild(ui.standees[s]);
}
console.log('standees', ui.standees);
@@ -139,6 +142,8 @@ function on_init() {
e.setAttribute('data-card-id', '' + data.cards[c].id)
register_action(e, 'card', c);
}
+
+ console.log('action_register',action_register[0]);
}
// @ts-ignore
@@ -165,7 +170,7 @@ function on_update() {
for (let c of view.hand) ui.hand.appendChild(ui.cards[c]);
for (let i = 0; i < view.tracks.length; i++) {
- ui.tracks.appendChild(ui.standees[i]);
+ // ui.tracks.appendChild(ui.standees[i]);
ui.standees[i].style.left = LAYOUT_TRACKS[i][view.tracks[i]][0] + 'px';
ui.standees[i].style.top = LAYOUT_TRACKS[i][view.tracks[i]][1] + 'px';
}
@@ -174,9 +179,54 @@ function on_update() {
ui.frontValues[frontId].replaceChildren(view.fronts[frontId]);
}
- // for (let e of action_register)
- // e.classList.toggle('action', is_action(e.my_action, e.my_id));
+ for (let e of action_register)
+ e.classList.toggle('action', is_action(e.my_action, e.my_id));
action_button('next', 'Next');
action_button('undo', 'Undo');
+ action_button('add_glory', 'Add Glory');
}
+
+// @ts-ignore
+function on_log(text) {
+ let p = document.createElement("div")
+
+ if (text.match(/^>/)) {
+ text = text.substring(1)
+ p.className = 'i'
+ }
+
+ text = text.replace(/&/g, "&amp;")
+ text = text.replace(/</g, "&lt;")
+ text = text.replace(/>/g, "&gt;")
+ // text = text.replace(/C(\d+)/g, sub_card_name)
+ // text = text.replace(/S(\d+)/g, sub_space_name)
+ // text = text.replace(/U(\d+)/g, sub_unit_name)
+
+ // TODO dice icons
+ // text = text.replace(/\bD\d\b/g, sub_icon)
+
+ if (text.match(/^\.h1/)) {
+ text = text.substring(4)
+ p.className = 'h1'
+ }
+ else if (text.match(/^\.h2/)) {
+ text = text.substring(4)
+ p.className = 'h2'
+ }
+ else if (text.match(/^\.h3\.allies/)) {
+ text = text.substring(10)
+ p.className = 'h3 allies'
+ }
+ else if (text.match(/^\.h3\.germans/)) {
+ text = text.substring(11)
+ p.className = 'h3 germans'
+ }
+ else if (text.match(/^\.h3/)) {
+ text = text.substring(4)
+ p.className = 'h3'
+ }
+
+ p.innerHTML = text
+ return p
+} \ No newline at end of file
diff --git a/rules.js b/rules.js
index 1704761..99af9ab 100644
--- a/rules.js
+++ b/rules.js
@@ -14,13 +14,39 @@ const MODERATE = 'Moderate';
const ANARCHISTS_ID = 'a';
const COMMUNISTS_ID = 'c';
const MODERATES_ID = 'm';
+const role_ids = [ANARCHISTS_ID, COMMUNISTS_ID, MODERATES_ID];
+const faction_player_map = {
+ [ANARCHISTS_ID]: ANARCHIST,
+ [COMMUNISTS_ID]: COMMUNIST,
+ [MODERATES_ID]: MODERATE,
+};
+const player_faction_map = {
+ [ANARCHIST]: ANARCHISTS_ID,
+ [COMMUNIST]: COMMUNISTS_ID,
+ [MODERATE]: MODERATES_ID,
+};
+const front_names = {
+ a: 'the Aragon Front',
+ m: 'the Madrid Front',
+ n: 'the Nothern Front',
+ s: 'the Southern Front',
+ d: 'the Front closest to Defeat',
+ v: 'the Front closest to Victory',
+};
+const track_names = {
+ [data_1.LIBERTY]: 'Liberty',
+ [data_1.COLLECTIVIZATION]: 'Collectivization',
+ [data_1.GOVERNMENT]: 'Government',
+ [data_1.SOVIET_SUPPORT]: 'Soviet Support',
+ [data_1.FOREIGN_AID]: 'Foreign Aid',
+};
const { cards,
// fronts
} = data_1.default;
const faction_cards = {
- [ANARCHIST]: make_list(37, 54),
- [COMMUNIST]: make_list(19, 36),
- [MODERATE]: make_list(1, 18),
+ [ANARCHISTS_ID]: make_list(37, 54),
+ [COMMUNISTS_ID]: make_list(19, 36),
+ [MODERATES_ID]: make_list(1, 18),
};
const fascist_decks = {
1: make_list(55, 62),
@@ -28,20 +54,29 @@ const fascist_decks = {
exports.scenarios = ['Standard'];
exports.roles = [ANARCHIST, COMMUNIST, MODERATE];
function gen_action(action, argument) {
- if (!(action in view.actions))
- view.actions[action] = [];
- view.actions[action].push(argument);
+ if (argument === undefined) {
+ view.actions[action] = 1;
+ }
+ else {
+ if (!(action in view.actions))
+ view.actions[action] = [];
+ view.actions[action].push(argument);
+ }
+}
+function gen_action_card(card_id) {
+ gen_action('card', card_id);
}
function gen_action_space(space) {
gen_action('space', space);
}
-function gen_action_piece(piece) {
- gen_action('piece', piece);
-}
+// function gen_action_piece(piece) {
+// gen_action('piece', piece);
+// }
// function gen_action_card(card) {
// gen_action('card', card);
// }
function action(state, player, action, arg) {
+ console.log('action', state, player, action, arg);
game = state;
let S = states[game.state];
if (action in S)
@@ -52,12 +87,132 @@ function action(state, player, action, arg) {
throw new Error('Invalid action: ' + action);
return game;
}
+// #region ENGINE
+const leaf_node = 'l';
+const seq_node = 's';
+const function_node = 'f';
+const resolved = 1;
+function setup_bag_of_glory() {
+ console.log('setup_bag_of_glory');
+ game.engine = [
+ {
+ t: leaf_node,
+ p: game.initiative,
+ s: 'add_glory',
+ },
+ {
+ t: function_node,
+ f: 'end_of_turn',
+ },
+ ];
+ next();
+}
+function setup_choose_card() {
+ console.log('setup_choose_card');
+ game.engine = exports.roles.map((role) => ({
+ t: leaf_node,
+ p: player_faction_map[role],
+ s: 'choose_card',
+ }));
+ game.engine.push({
+ t: function_node,
+ f: 'setup_player_turn',
+ });
+ next();
+}
+function setup_player_turn() {
+ console.log('setup_player_turn');
+ // TODO: reverse order in second round
+ const first = game.initiative;
+ const second = get_next_faction(first);
+ const third = get_next_faction(second);
+ game.engine = [first, second, third].map((faction_id) => ({
+ t: seq_node,
+ c: [
+ {
+ t: leaf_node,
+ s: 'player_turn',
+ p: faction_id,
+ },
+ ],
+ }));
+ game.engine.push({
+ t: function_node,
+ f: 'resolve_fascist_test',
+ });
+ game.engine.push({
+ t: function_node,
+ f: 'setup_bag_of_glory',
+ });
+ next();
+}
+const engine_functions = {
+ end_of_turn,
+ end_of_year,
+ setup_bag_of_glory,
+ setup_choose_card,
+ setup_player_turn,
+ resolve_fascist_test,
+};
+function get_active_node(engine) {
+ for (let i of engine) {
+ if (i.t === leaf_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() {
+ const node = get_active_node(game.engine);
+ if (node.t === leaf_node) {
+ return node.a;
+ }
+ return null;
+}
+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]) {
+ resolve_active_node();
+ const args = node.a;
+ if (args) {
+ engine_functions[node.f](args);
+ }
+ else {
+ engine_functions[node.f]();
+ }
+ }
+ else if (node.t === 'l') {
+ game.state = node.s;
+ game.active = faction_player_map[node.p];
+ }
+}
+function resolve_active_node() {
+ const next_node = get_active_node(game.engine);
+ console.log('resolve_active_node', next_node);
+ if (next_node !== null) {
+ next_node.r = resolved;
+ }
+}
+function resolve_active_and_proceed() {
+ resolve_active_node();
+ next();
+}
function game_view(state, player) {
- console.log('game_view', state);
- console.log('player', player);
game = state;
- const faction_id = get_faction_id(player);
+ const faction_id = player_faction_map[player];
view = {
+ engine: game.engine,
log: game.log,
prompt: null,
location: game.location,
@@ -65,7 +220,7 @@ function game_view(state, player) {
current_events: game.current_events,
fronts: game.fronts,
hand: game.hands[faction_id],
- tracks: game.tracks
+ tracks: game.tracks,
};
if (player !== game.active) {
let inactive = states[game.state].inactive || game.state;
@@ -82,8 +237,9 @@ function game_view(state, player) {
return view;
}
// #endregion
-// #region setup
+// #region SETUP
function setup(seed, _scenario, _options) {
+ // game.seed = seed;
game = {
seed: seed,
state: null,
@@ -95,80 +251,196 @@ function setup(seed, _scenario, _options) {
},
bonuses: [data_1.ON, data_1.ON],
current_events: [],
+ engine: [],
fronts: {
- a: 2,
- m: 2,
- n: 2,
- s: 2,
+ a: -2,
+ m: -2,
+ n: -2,
+ s: -2,
},
hands: {
- [ANARCHISTS_ID]: [
- draw_card(faction_cards[ANARCHIST]),
- draw_card(faction_cards[ANARCHIST]),
- draw_card(faction_cards[ANARCHIST]),
- draw_card(faction_cards[ANARCHIST]),
- draw_card(faction_cards[ANARCHIST]),
- ],
- [COMMUNISTS_ID]: [
- draw_card(faction_cards[COMMUNIST]),
- draw_card(faction_cards[COMMUNIST]),
- draw_card(faction_cards[COMMUNIST]),
- draw_card(faction_cards[COMMUNIST]),
- draw_card(faction_cards[COMMUNIST]),
- ],
- [MODERATES_ID]: [
- draw_card(faction_cards[MODERATE]),
- draw_card(faction_cards[MODERATE]),
- draw_card(faction_cards[MODERATE]),
- draw_card(faction_cards[MODERATE]),
- draw_card(faction_cards[MODERATE]),
- ],
+ [ANARCHISTS_ID]: [],
+ [COMMUNISTS_ID]: [],
+ [MODERATES_ID]: [],
},
hero_points: {
[ANARCHISTS_ID]: 2,
[COMMUNISTS_ID]: 2,
[MODERATES_ID]: 0,
},
+ cards_in_play: {
+ [ANARCHISTS_ID]: null,
+ [COMMUNISTS_ID]: null,
+ [MODERATES_ID]: null,
+ },
initiative: MODERATES_ID,
+ tableaus: {
+ [ANARCHISTS_ID]: [],
+ [COMMUNISTS_ID]: [],
+ [MODERATES_ID]: [],
+ },
tracks: [5, 5, 6, 3, 3],
log: [],
undo: [],
turn: 1,
year: 1,
+ state_data: null,
};
- fascist_event();
- game.state = 'move';
+ start_year();
return game;
}
+function draw_hand_cards() {
+ role_ids.forEach((role) => {
+ const deck = faction_cards[role];
+ for (let i = 0; i < 5; i++) {
+ game.hands[role];
+ game.hands[role].push(draw_card(deck));
+ }
+ });
+}
// #endregion
-states.move = {
- inactive: 'move',
+function start_year() {
+ log_h1('Year ' + game.year);
+ draw_hand_cards();
+ start_turn();
+}
+function start_turn() {
+ log_h2('Turn ' + game.turn);
+ const deck = fascist_decks[game.year];
+ const cardId = draw_card(deck);
+ game.current_events.push(cardId);
+ const card = cards[cardId];
+ log_h3('Fascist Event: ' + card.title);
+ game.engine = card.effects.map((_effect, index) => ({
+ t: leaf_node,
+ s: 'resolve_event',
+ p: game.initiative,
+ a: index,
+ }));
+ game.engine.push({
+ t: 'f',
+ f: 'setup_choose_card',
+ });
+ next();
+ // game.state = 'resolve_event';
+ // game.active = faction_player_map[game.initiative];
+ // game.state_data = {
+ // current_effect: 0,
+ // };
+}
+// region STATES
+states.add_glory = {
+ inactive: 'add tokens to the Bag of Glory',
prompt() {
- view.prompt = 'Move a piece.';
- for (let p = 0; p < 32; ++p)
- gen_action_piece(p);
+ view.prompt = 'Add tokens to the Bag of Glory';
+ gen_action('add_glory');
+ },
+ add_glory() {
+ console.log('add_glory');
+ let number = 1;
+ if (game.turn === 4) {
+ number++;
+ }
+ game.bag_of_glory[get_active_faction()] += number;
+ if (number === 1) {
+ log_h3(`${game.active} adds 1 token to the Bag of Glory`);
+ }
+ else {
+ log_h3(`${game.active} adds ${number} tokens to the Bag of Glory`);
+ }
+ resolve_active_and_proceed();
+ },
+};
+states.resolve_event = {
+ inactive: 'resolve Fascist Event',
+ prompt() {
+ const card = get_current_event();
+ const node = get_active_node(game.engine);
+ const effect = card.effects[node.a];
+ view.prompt = get_event_prompt(effect);
+ if (effect.type === 'attack' &&
+ (effect.target === 'd' || effect.target === 'v')) {
+ const fronts = get_fronts_closest_to(effect.target);
+ fronts.forEach((id) => gen_action('front', id));
+ }
+ else if (effect.type === 'attack') {
+ gen_action('front', effect.target);
+ }
+ else if (effect.type === 'track') {
+ gen_action('standee', effect.target);
+ }
+ // for (let p = 0; p < 5; ++p) gen_action('standee', p);
+ },
+ front(f) {
+ const card = get_current_event();
+ const value = card.effects[get_active_node_args()].value;
+ game.fronts[f] -= value;
+ log_h3(`${value} attacks added to ${front_names[f]}`);
+ resolve_active_and_proceed();
+ },
+ standee(s) {
+ const effect = get_current_event().effects[get_active_node_args()];
+ const value = effect.value;
+ game.tracks[s] += value;
+ log_h3(`${track_names[effect.target]} decreased by ${Math.abs(value)}`);
+ resolve_active_and_proceed();
+ },
+};
+states.choose_card = {
+ inactive: 'choose a card',
+ prompt() {
+ view.prompt = 'Choose a card to play this turn';
+ const hand = game.hands[player_faction_map[game.active]];
+ for (let c of hand)
+ gen_action_card(c);
+ },
+ card(c) {
+ log_h3(`${game.active} chooses a card`);
+ if (!game.cards_in_play) {
+ game.cards_in_play = {};
+ }
+ game.cards_in_play[player_faction_map[game.active]] = c;
+ resolve_active_and_proceed();
},
- // piece(p) {
- // game.selected = p
- // game.state = "move_to"
- // },
};
-states.move_to = {
- inactive: 'move',
+states.player_turn = {
+ inactive: 'play their turn',
prompt() {
- view.prompt = 'Move the piece to a space.';
- for (let s = 0; s < 64; ++s)
- gen_action_space(s);
+ view.prompt = 'Play your card or spend Hero points';
+ gen_action_card(game.cards_in_play[player_faction_map[game.active]]);
+ },
+ card(c) {
+ const faction = get_active_faction();
+ log_h3(`${game.active} plays ${cards[c].title} to their tableau`);
+ if (!game.tableaus) {
+ game.tableaus = {
+ [ANARCHISTS_ID]: [],
+ [COMMUNISTS_ID]: [],
+ [MODERATES_ID]: [],
+ };
+ }
+ game.cards_in_play[faction] = null;
+ game.tableaus[faction].push(c);
+ array_remove(game.hands[faction], game.hands[faction].indexOf(c));
+ resolve_active_and_proceed();
},
- // space(to) {
- // game.location[game.selected] = to
- // game.state = "move"
- // if (game.active === PLAYER1)
- // game.active = PLAYER2
- // else
- // game.active = PLAYER1
- // },
};
+// states.move_to = {
+// inactive: 'move',
+// prompt() {
+// view.prompt = 'Move the piece to a space.';
+// for (let s = 0; s < 64; ++s) gen_action_space(s);
+// },
+// // space(to) {
+// // game.location[game.selected] = to
+// // game.state = "move"
+// // if (game.active === PLAYER1)
+// // game.active = PLAYER2
+// // else
+// // game.active = PLAYER1
+// // },
+// };
+// #endrregion
function pop_undo() {
const save_log = game.log;
const save_undo = game.undo;
@@ -177,6 +449,25 @@ function pop_undo() {
game.log = save_log;
game.undo = save_undo;
}
+// #region GAME FUNCTIONS
+function end_of_turn() {
+ // REMOVE playre tplems from the Fronts;
+ log_h2('End of turn');
+ if (game.turn === 4) {
+ end_of_year();
+ }
+ else {
+ game.turn++;
+ start_turn();
+ }
+}
+function end_of_year() { }
+function resolve_fascist_test() {
+ console.log('resolve fascist test');
+ log_h2('Fascist test is resolved');
+ next();
+}
+// #endregion
// #region CARDS
// function draw_faction_card(faction: Player): CardId {
// return draw_faction_cards(faction, 1)[0];
@@ -185,61 +476,156 @@ function pop_undo() {
// const drawnCards = [];
// }
function draw_card(deck) {
+ console.log('draw_card_deck', deck);
clear_undo();
let i = random(deck.length);
+ console.log('random ', i);
let c = deck[i];
+ console.log('draw_card_id', c);
set_delete(deck, c);
return c;
}
// #endregion
// #region EVENTS
-function resolve_event_attack(target, value) {
- switch (target) {
- case 'v':
+function get_current_event() {
+ return cards[game.current_events[game.current_events.length - 1]];
+}
+// function get_front_name(frontId: string) {
+// switch (frontId) {
+// case 'a':
+// return 'the Aragon Front';
+// case 'm':
+// return 'the Madrid Front';
+// case 'n':
+// return 'the Nothern Front';
+// case 's':
+// return 'the Southern Front';
+// case 'd':
+// return 'the Front closest to Defeat';
+// case 'v':
+// return 'the Front closest to Victory';
+// default:
+// return '';
+// }
+// }
+function get_event_prompt(effect) {
+ let prompt = '';
+ switch (effect.type) {
+ case 'attack':
+ return 'Attack ' + front_names[effect.target];
+ case 'bonus':
break;
- case 'd':
+ case 'hero_points':
break;
- default:
- game.fronts[target] += value;
+ case 'track':
+ return 'Decrease ' + track_names[effect.target];
}
+ return prompt;
}
-function fascist_event() {
- const deck = fascist_decks[game.year];
- const cardId = draw_card(deck);
- game.current_events.push(cardId);
- const card = cards[cardId];
- card.effects.forEach((effect) => {
- switch (effect.type) {
- case 'attack':
- resolve_event_attack(effect.target, effect.value);
- break;
- case 'bonus':
- break;
- case 'hero_points':
- break;
- case 'track':
- game.tracks[effect.target] += effect.value;
- break;
- }
- });
+// function resolve_event_attack(target: string | number, value: number) {
+// switch (target) {
+// case 'v':
+// break;
+// case 'd':
+// break;
+// default:
+// game.fronts[target] += value;
+// }
+// }
+// function fascist_event() {
+// // log_h1('Year ' + game.year);
+// const deck = fascist_decks[game.year];
+// const cardId = draw_card(deck);
+// game.current_events.push(cardId);
+// const card = cards[cardId] as EventCard;
+// card.effects.forEach((effect) => {
+// switch (effect.type) {
+// case 'attack':
+// resolve_event_attack(effect.target, effect.value);
+// break;
+// case 'bonus':
+// break;
+// case 'hero_points':
+// break;
+// case 'track':
+// game.tracks[effect.target] += effect.value;
+// break;
+// }
+// });
+// }
+// #endregion
+// #region FRONTS
+function get_fronts_closest_to(target) {
+ const values = Object.values(game.fronts);
+ const targetValue = target === 'd' ? Math.min(...values) : Math.max(...values);
+ return Object.keys(game.fronts).filter((frontId) => game.fronts[frontId] === targetValue);
}
// #endregion
+// #region LOGGING
+function log_br() {
+ if (game.log.length > 0 && game.log[game.log.length - 1] !== '')
+ game.log.push('');
+}
+function log(msg) {
+ game.log.push(msg);
+}
+// function logevent(cap: Card) {
+// game.log.push(`E${cap}.`)
+// }
+// function logcap(cap: Card) {
+// game.log.push(`C${cap}.`)
+// }
+// function logi(msg: string) {
+// game.log.push('>' + msg);
+// }
+// function logii(msg: string) {
+// game.log.push('>>' + msg);
+// }
+function log_h1(msg) {
+ log_br();
+ log('.h1 ' + msg);
+ log_br();
+}
+function log_h2(msg) {
+ log_br();
+ log('.h2 ' + msg);
+ log_br();
+}
+// function log_h2_active(msg: string) {
+// log_br();
+// log('.h2 ' + msg);
+// log_br();
+// }
+// function log_h2_common(msg: string) {
+// log_br();
+// log('.h2 ' + msg);
+// log_br();
+// }
+function log_h3(msg) {
+ log_br();
+ log('.h3 ' + msg);
+}
+// function log_h4(msg: string) {
+// log_br();
+// log('.h4 ' + msg);
+// }
+// #endregion LOGGING
// #region UTILITY
function clear_undo() {
- if (game.undo.length > 0)
+ console.log('game clear undo', game?.undo);
+ if (game?.undo && game.undo.length > 0)
game.undo = [];
}
-function get_faction_id(player) {
- switch (player) {
- case ANARCHIST:
- return ANARCHISTS_ID;
- case COMMUNIST:
- return COMMUNISTS_ID;
- case MODERATE:
- return MODERATES_ID;
- default:
- throw new Error('Unknown player');
+function get_active_faction() {
+ return player_faction_map[game.active];
+}
+function get_next_faction(faction_id) {
+ const index = role_ids.indexOf(faction_id);
+ let next_index = index + 1;
+ if (next_index === role_ids.length) {
+ next_index = 0;
}
+ return role_ids[next_index];
}
function make_list(first, last) {
let list = [];
diff --git a/rules.ts b/rules.ts
index 389900d..49f4687 100644
--- a/rules.ts
+++ b/rules.ts
@@ -11,6 +11,12 @@ import data, {
// OFF,
ON,
EventCard,
+ CardEffect,
+ LIBERTY,
+ COLLECTIVIZATION,
+ GOVERNMENT,
+ SOVIET_SUPPORT,
+ FOREIGN_AID,
// StaticData,
// PLAYER_WITH_MOST_HERO_POINTS,
} from './data';
@@ -28,7 +34,7 @@ export type FactionId = Brand<string, 'FactionId'>;
interface Game {
[index: number]: any;
seed: number;
- log: number | string[];
+ log: string[];
undo: Game[];
turn: number;
year: number;
@@ -36,7 +42,9 @@ interface Game {
state: string | null;
bag_of_glory: Record<FactionId, number>;
bonuses: number[];
+ cards_in_play: Record<FactionId, CardId>;
current_events: CardId[];
+ engine: EngineNode[];
fronts: {
a: number;
m: number;
@@ -46,6 +54,7 @@ interface Game {
hands: Record<FactionId, CardId[]>;
hero_points: Record<FactionId, number>;
initiative: FactionId;
+ tableaus: Record<FactionId, CardId[]>;
tracks: number[];
result?: string;
@@ -54,12 +63,14 @@ interface Game {
location?: string;
selected?: string;
+ state_data: any;
// played_card: CardId
// turn: Turn
}
export interface View {
+ engine: Game['engine'];
log: number | string[];
active?: string | null;
prompt: string | null;
@@ -94,15 +105,46 @@ const ANARCHISTS_ID = 'a' as FactionId;
const COMMUNISTS_ID = 'c' as FactionId;
const MODERATES_ID = 'm' as FactionId;
+const role_ids = [ANARCHISTS_ID, COMMUNISTS_ID, MODERATES_ID];
+
+const faction_player_map: Record<FactionId, Player> = {
+ [ANARCHISTS_ID]: ANARCHIST,
+ [COMMUNISTS_ID]: COMMUNIST,
+ [MODERATES_ID]: MODERATE,
+};
+
+const player_faction_map: Record<Player, FactionId> = {
+ [ANARCHIST]: ANARCHISTS_ID,
+ [COMMUNIST]: COMMUNISTS_ID,
+ [MODERATE]: MODERATES_ID,
+};
+
+const front_names: Record<string, string> = {
+ a: 'the Aragon Front',
+ m: 'the Madrid Front',
+ n: 'the Nothern Front',
+ s: 'the Southern Front',
+ d: 'the Front closest to Defeat',
+ v: 'the Front closest to Victory',
+};
+
+const track_names: Record<number, string> = {
+ [LIBERTY]: 'Liberty',
+ [COLLECTIVIZATION]: 'Collectivization',
+ [GOVERNMENT]: 'Government',
+ [SOVIET_SUPPORT]: 'Soviet Support',
+ [FOREIGN_AID]: 'Foreign Aid',
+};
+
const {
cards,
// fronts
} = data;
const faction_cards = {
- [ANARCHIST]: make_list(37, 54) as CardId[],
- [COMMUNIST]: make_list(19, 36) as CardId[],
- [MODERATE]: make_list(1, 18) as CardId[],
+ [ANARCHISTS_ID]: make_list(37, 54) as CardId[],
+ [COMMUNISTS_ID]: make_list(19, 36) as CardId[],
+ [MODERATES_ID]: make_list(1, 18) as CardId[],
};
const fascist_decks = {
@@ -113,18 +155,26 @@ export const scenarios = ['Standard'];
export const roles: Player[] = [ANARCHIST, COMMUNIST, MODERATE];
-function gen_action(action, argument) {
- if (!(action in view.actions)) view.actions[action] = [];
- view.actions[action].push(argument);
+function gen_action(action: string, argument?: number | string) {
+ if (argument === undefined) {
+ view.actions![action] = 1;
+ } else {
+ if (!(action in view.actions)) view.actions[action] = [];
+ view.actions[action].push(argument);
+ }
+}
+
+function gen_action_card(card_id: CardId) {
+ gen_action('card', card_id);
}
function gen_action_space(space) {
gen_action('space', space);
}
-function gen_action_piece(piece) {
- gen_action('piece', piece);
-}
+// function gen_action_piece(piece) {
+// gen_action('piece', piece);
+// }
// function gen_action_card(card) {
// gen_action('card', card);
@@ -136,6 +186,7 @@ export function action(
action: string,
arg: unknown
) {
+ console.log('action', state, player, action, arg);
game = state;
let S = states[game.state];
if (action in S) S[action](arg, player);
@@ -144,18 +195,171 @@ export function action(
return game;
}
+// #region ENGINE
+
+const leaf_node = 'l';
+const seq_node = 's';
+const function_node = 'f';
+const resolved = 1;
+
+function setup_bag_of_glory() {
+ console.log('setup_bag_of_glory');
+ game.engine = [
+ {
+ t: leaf_node,
+ p: game.initiative,
+ s: 'add_glory',
+ },
+ {
+ t: function_node,
+ f: 'end_of_turn',
+ },
+ ];
+ next();
+}
+
+function setup_choose_card() {
+ console.log('setup_choose_card');
+ game.engine = roles.map((role) => ({
+ t: leaf_node,
+ p: player_faction_map[role],
+ s: 'choose_card',
+ }));
+ game.engine.push({
+ t: function_node,
+ f: 'setup_player_turn',
+ });
+ next();
+}
+
+function setup_player_turn() {
+ console.log('setup_player_turn');
+ // TODO: reverse order in second round
+ const first = game.initiative;
+ const second = get_next_faction(first);
+ const third = get_next_faction(second);
+ game.engine = [first, second, third].map((faction_id) => ({
+ t: seq_node,
+ c: [
+ {
+ t: leaf_node,
+ s: 'player_turn',
+ p: faction_id,
+ },
+ ],
+ }));
+ game.engine.push({
+ t: function_node,
+ f: 'resolve_fascist_test',
+ });
+ game.engine.push({
+ t: function_node,
+ f: 'setup_bag_of_glory',
+ });
+ next();
+}
+
+const engine_functions: Record<string, Function> = {
+ end_of_turn,
+ end_of_year,
+ setup_bag_of_glory,
+ setup_choose_card,
+ setup_player_turn,
+ resolve_fascist_test,
+};
+
+type EngineNode = FunctionNode | LeafNode | SeqNode;
+
+interface FunctionNode {
+ t: 'f';
+ f: string; // function to be triggered
+ a?: any; // args
+ r?: 0 | 1; // 1 if resolved
+}
+
+interface SeqNode {
+ t: 's'; // Type
+ c: EngineNode[];
+}
+
+interface LeafNode {
+ t: 'l';
+ s: string; // State
+ p: FactionId; // Player
+ a?: any; // args
+ r?: 0 | 1; // 1 if resolved
+}
+
+function get_active_node(engine: EngineNode[]): FunctionNode | LeafNode | null {
+ for (let i of engine) {
+ if (i.t === leaf_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 {
+ const node = get_active_node(game.engine);
+ if (node.t === leaf_node) {
+ return node.a;
+ }
+ return null;
+}
+
+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]) {
+ resolve_active_node();
+ const args = node.a;
+ if (args) {
+ engine_functions[node.f](args);
+ } else {
+ engine_functions[node.f]();
+ }
+ } else if (node.t === 'l') {
+ game.state = node.s;
+ game.active = faction_player_map[node.p];
+ }
+}
+
+function resolve_active_node() {
+ const next_node = get_active_node(game.engine);
+ console.log('resolve_active_node', next_node);
+ if (next_node !== null) {
+ next_node.r = resolved;
+ }
+}
+
+function resolve_active_and_proceed() {
+ resolve_active_node();
+ next();
+}
+
+// #endregion
+
// #region VIEW
export { game_view as view };
function game_view(state: Game, player: Player) {
- console.log('game_view', state);
- console.log('player', player);
game = state;
- const faction_id = get_faction_id(player);
+ const faction_id = player_faction_map[player];
view = {
+ engine: game.engine,
log: game.log,
prompt: null,
location: game.location,
@@ -163,7 +367,7 @@ function game_view(state: Game, player: Player) {
current_events: game.current_events,
fronts: game.fronts,
hand: game.hands[faction_id],
- tracks: game.tracks
+ tracks: game.tracks,
};
if (player !== game.active) {
@@ -181,9 +385,10 @@ function game_view(state: Game, player: Player) {
// #endregion
-// #region setup
+// #region SETUP
export function setup(seed: number, _scenario: string, _options: unknown) {
+ // game.seed = seed;
game = {
seed: seed,
state: null,
@@ -195,94 +400,243 @@ export function setup(seed: number, _scenario: string, _options: unknown) {
},
bonuses: [ON, ON],
current_events: [],
+ engine: [],
fronts: {
- a: 2,
- m: 2,
- n: 2,
- s: 2,
+ a: -2,
+ m: -2,
+ n: -2,
+ s: -2,
},
hands: {
- [ANARCHISTS_ID]: [
- draw_card(faction_cards[ANARCHIST]),
- draw_card(faction_cards[ANARCHIST]),
- draw_card(faction_cards[ANARCHIST]),
- draw_card(faction_cards[ANARCHIST]),
- draw_card(faction_cards[ANARCHIST]),
- ],
- [COMMUNISTS_ID]: [
- draw_card(faction_cards[COMMUNIST]),
- draw_card(faction_cards[COMMUNIST]),
- draw_card(faction_cards[COMMUNIST]),
- draw_card(faction_cards[COMMUNIST]),
- draw_card(faction_cards[COMMUNIST]),
- ],
- [MODERATES_ID]: [
- draw_card(faction_cards[MODERATE]),
- draw_card(faction_cards[MODERATE]),
- draw_card(faction_cards[MODERATE]),
- draw_card(faction_cards[MODERATE]),
- draw_card(faction_cards[MODERATE]),
- ],
+ [ANARCHISTS_ID]: [],
+ [COMMUNISTS_ID]: [],
+ [MODERATES_ID]: [],
},
hero_points: {
[ANARCHISTS_ID]: 2,
[COMMUNISTS_ID]: 2,
[MODERATES_ID]: 0,
},
+ cards_in_play: {
+ [ANARCHISTS_ID]: null,
+ [COMMUNISTS_ID]: null,
+ [MODERATES_ID]: null,
+ },
initiative: MODERATES_ID,
+ tableaus: {
+ [ANARCHISTS_ID]: [],
+ [COMMUNISTS_ID]: [],
+ [MODERATES_ID]: [],
+ },
tracks: [5, 5, 6, 3, 3],
log: [],
undo: [],
turn: 1,
year: 1,
+ state_data: null,
};
- fascist_event();
-
- game.state = 'move';
-
+ start_year();
return game;
}
+function draw_hand_cards() {
+ role_ids.forEach((role) => {
+ const deck = faction_cards[role];
+ for (let i = 0; i < 5; i++) {
+ game.hands[role];
+ game.hands[role].push(draw_card(deck));
+ }
+ });
+}
+
// #endregion
-states.move = {
- inactive: 'move',
+function start_year() {
+ log_h1('Year ' + game.year);
+ draw_hand_cards();
+ start_turn();
+}
+
+function start_turn() {
+ log_h2('Turn ' + game.turn);
+
+ const deck = fascist_decks[game.year];
+ const cardId = draw_card(deck);
+ game.current_events.push(cardId);
+
+ const card = cards[cardId] as EventCard;
+ log_h3('Fascist Event: ' + card.title);
+
+ game.engine = card.effects.map((_effect, index) => ({
+ t: leaf_node,
+ s: 'resolve_event',
+ p: game.initiative,
+ a: index,
+ }));
+ game.engine.push({
+ t: 'f',
+ f: 'setup_choose_card',
+ });
+ next();
+ // game.state = 'resolve_event';
+ // game.active = faction_player_map[game.initiative];
+ // game.state_data = {
+ // current_effect: 0,
+ // };
+}
+
+// region STATES
+
+states.add_glory = {
+ inactive: 'add tokens to the Bag of Glory',
+ prompt() {
+ view.prompt = 'Add tokens to the Bag of Glory';
+ gen_action('add_glory');
+ },
+ add_glory() {
+ console.log('add_glory');
+ let number = 1;
+ if (game.turn === 4) {
+ number++;
+ }
+ game.bag_of_glory[get_active_faction()] += number;
+ if (number === 1) {
+ log_h3(`${game.active} adds 1 token to the Bag of Glory`);
+ } else {
+ log_h3(`${game.active} adds ${number} tokens to the Bag of Glory`);
+ }
+ resolve_active_and_proceed();
+ },
+};
+
+states.resolve_event = {
+ inactive: 'resolve Fascist Event',
+ prompt() {
+ const card = get_current_event();
+ const node = get_active_node(game.engine) as LeafNode;
+ const effect: CardEffect = card.effects[node.a];
+ view.prompt = get_event_prompt(effect);
+
+ if (
+ effect.type === 'attack' &&
+ (effect.target === 'd' || effect.target === 'v')
+ ) {
+ const fronts = get_fronts_closest_to(effect.target);
+ fronts.forEach((id) => gen_action('front', id));
+ } else if (effect.type === 'attack') {
+ gen_action('front', effect.target);
+ } else if (effect.type === 'track') {
+ gen_action('standee', effect.target);
+ }
+ // for (let p = 0; p < 5; ++p) gen_action('standee', p);
+ },
+ front(f: string) {
+ const card = get_current_event();
+ const value = card.effects[get_active_node_args()].value;
+ game.fronts[f] -= value;
+ log_h3(`${value} attacks added to ${front_names[f]}`);
+ resolve_active_and_proceed();
+ },
+ standee(s: string) {
+ const effect = get_current_event().effects[get_active_node_args()];
+ const value = effect.value;
+ game.tracks[s] += value;
+ log_h3(`${track_names[effect.target]} decreased by ${Math.abs(value)}`);
+ resolve_active_and_proceed();
+ },
+};
+
+states.choose_card = {
+ inactive: 'choose a card',
prompt() {
- view.prompt = 'Move a piece.';
- for (let p = 0; p < 32; ++p) gen_action_piece(p);
+ view.prompt = 'Choose a card to play this turn';
+ const hand = game.hands[player_faction_map[game.active]];
+ for (let c of hand) gen_action_card(c);
+ },
+ card(c: CardId) {
+ log_h3(`${game.active} chooses a card`);
+ if (!game.cards_in_play) {
+ game.cards_in_play = {};
+ }
+ game.cards_in_play[player_faction_map[game.active]] = c;
+ resolve_active_and_proceed();
},
- // piece(p) {
- // game.selected = p
- // game.state = "move_to"
- // },
};
-states.move_to = {
- inactive: 'move',
+states.player_turn = {
+ inactive: 'play their turn',
prompt() {
- view.prompt = 'Move the piece to a space.';
- for (let s = 0; s < 64; ++s) gen_action_space(s);
+ view.prompt = 'Play your card or spend Hero points';
+ gen_action_card(game.cards_in_play[player_faction_map[game.active]]);
+ },
+ card(c: CardId) {
+ const faction = get_active_faction();
+ log_h3(`${game.active} plays ${cards[c].title} to their tableau`);
+ if (!game.tableaus) {
+ game.tableaus = {
+ [ANARCHISTS_ID]: [],
+ [COMMUNISTS_ID]: [],
+ [MODERATES_ID]: [],
+ };
+ }
+ game.cards_in_play[faction] = null;
+ game.tableaus[faction].push(c);
+ array_remove(game.hands[faction], game.hands[faction].indexOf(c));
+ resolve_active_and_proceed();
},
- // space(to) {
- // game.location[game.selected] = to
- // game.state = "move"
- // if (game.active === PLAYER1)
- // game.active = PLAYER2
- // else
- // game.active = PLAYER1
- // },
};
+// states.move_to = {
+// inactive: 'move',
+// prompt() {
+// view.prompt = 'Move the piece to a space.';
+// for (let s = 0; s < 64; ++s) gen_action_space(s);
+// },
+// // space(to) {
+// // game.location[game.selected] = to
+// // game.state = "move"
+// // if (game.active === PLAYER1)
+// // game.active = PLAYER2
+// // else
+// // game.active = PLAYER1
+// // },
+// };
+
+// #endrregion
+
function pop_undo() {
const save_log = game.log;
const save_undo = game.undo;
game = save_undo.pop()!;
- (save_log as string[]).length = game.log as number;
+ (save_log as string[]).length = game.log as unknown as number;
game.log = save_log;
game.undo = save_undo;
}
+// #region GAME FUNCTIONS
+
+function end_of_turn() {
+ // REMOVE playre tplems from the Fronts;
+ log_h2('End of turn');
+ if (game.turn === 4) {
+ end_of_year();
+ } else {
+ game.turn++;
+ start_turn();
+ }
+}
+
+function end_of_year() {}
+
+function resolve_fascist_test() {
+ console.log('resolve fascist test');
+ log_h2('Fascist test is resolved');
+ next();
+}
+
+// #endregion
+
// #region CARDS
// function draw_faction_card(faction: Player): CardId {
// return draw_faction_cards(faction, 1)[0];
@@ -293,10 +647,13 @@ function pop_undo() {
// }
-function draw_card(deck: CardId[]) {
+function draw_card(deck: CardId[]): CardId {
+ console.log('draw_card_deck', deck);
clear_undo();
let i = random(deck.length);
- let c = deck[i];
+ console.log('random ', i);
+ let c = deck[i] as CardId;
+ console.log('draw_card_id', c);
set_delete(deck, c);
return c;
}
@@ -305,59 +662,177 @@ function draw_card(deck: CardId[]) {
// #region EVENTS
-function resolve_event_attack(target: string | number, value: number) {
- switch (target) {
- case 'v':
+function get_current_event(): EventCard {
+ return cards[
+ game.current_events[game.current_events.length - 1]
+ ] as EventCard;
+}
+
+// function get_front_name(frontId: string) {
+// switch (frontId) {
+// case 'a':
+// return 'the Aragon Front';
+// case 'm':
+// return 'the Madrid Front';
+// case 'n':
+// return 'the Nothern Front';
+// case 's':
+// return 'the Southern Front';
+// case 'd':
+// return 'the Front closest to Defeat';
+// case 'v':
+// return 'the Front closest to Victory';
+// default:
+// return '';
+// }
+// }
+
+function get_event_prompt(effect: CardEffect) {
+ let prompt = '';
+ switch (effect.type) {
+ case 'attack':
+ return 'Attack ' + front_names[effect.target as string];
+ case 'bonus':
break;
- case 'd':
+ case 'hero_points':
break;
- default:
- game.fronts[target] += value;
+ case 'track':
+ return 'Decrease ' + track_names[effect.target];
}
+ return prompt;
}
-function fascist_event() {
- const deck = fascist_decks[game.year];
- const cardId = draw_card(deck);
- game.current_events.push(cardId);
+// function resolve_event_attack(target: string | number, value: number) {
+// switch (target) {
+// case 'v':
+// break;
+// case 'd':
+// break;
+// default:
+// game.fronts[target] += value;
+// }
+// }
- const card = cards[cardId] as EventCard;
+// function fascist_event() {
+// // log_h1('Year ' + game.year);
+// const deck = fascist_decks[game.year];
+// const cardId = draw_card(deck);
+// game.current_events.push(cardId);
+
+// const card = cards[cardId] as EventCard;
+
+// card.effects.forEach((effect) => {
+// switch (effect.type) {
+// case 'attack':
+// resolve_event_attack(effect.target, effect.value);
+// break;
+// case 'bonus':
+// break;
+// case 'hero_points':
+// break;
+// case 'track':
+// game.tracks[effect.target] += effect.value;
+// break;
+// }
+// });
+// }
- card.effects.forEach((effect) => {
- switch (effect.type) {
- case 'attack':
- resolve_event_attack(effect.target, effect.value);
- break;
- case 'bonus':
- break;
- case 'hero_points':
- break;
- case 'track':
- game.tracks[effect.target] += effect.value;
- break;
- }
- });
+// #endregion
+
+// #region FRONTS
+
+function get_fronts_closest_to(target: 'd' | 'v') {
+ const values = Object.values(game.fronts);
+ const targetValue =
+ target === 'd' ? Math.min(...values) : Math.max(...values);
+ return Object.keys(game.fronts).filter(
+ (frontId) => game.fronts[frontId] === targetValue
+ );
}
// #endregion
+// #region LOGGING
+
+function log_br() {
+ if (game.log.length > 0 && game.log[game.log.length - 1] !== '')
+ game.log.push('');
+}
+
+function log(msg: string) {
+ game.log.push(msg);
+}
+
+// function logevent(cap: Card) {
+// game.log.push(`E${cap}.`)
+// }
+
+// function logcap(cap: Card) {
+// game.log.push(`C${cap}.`)
+// }
+
+// function logi(msg: string) {
+// game.log.push('>' + msg);
+// }
+
+// function logii(msg: string) {
+// game.log.push('>>' + msg);
+// }
+
+function log_h1(msg: string) {
+ log_br();
+ log('.h1 ' + msg);
+ log_br();
+}
+
+function log_h2(msg: string) {
+ log_br();
+ log('.h2 ' + msg);
+ log_br();
+}
+
+// function log_h2_active(msg: string) {
+// log_br();
+// log('.h2 ' + msg);
+// log_br();
+// }
+
+// function log_h2_common(msg: string) {
+// log_br();
+// log('.h2 ' + msg);
+// log_br();
+// }
+
+function log_h3(msg: string) {
+ log_br();
+ log('.h3 ' + msg);
+}
+
+// function log_h4(msg: string) {
+// log_br();
+// log('.h4 ' + msg);
+// }
+
+// #endregion LOGGING
+
// #region UTILITY
function clear_undo() {
- if (game.undo.length > 0) game.undo = [];
-}
-
-function get_faction_id(player: Player) {
- switch (player) {
- case ANARCHIST:
- return ANARCHISTS_ID;
- case COMMUNIST:
- return COMMUNISTS_ID;
- case MODERATE:
- return MODERATES_ID;
- default:
- throw new Error('Unknown player');
+ console.log('game clear undo', game?.undo);
+ if (game?.undo && game.undo.length > 0) game.undo = [];
+}
+
+function get_active_faction(): FactionId {
+ return player_faction_map[game.active];
+}
+
+function get_next_faction(faction_id: FactionId): FactionId {
+ const index = role_ids.indexOf(faction_id);
+ let next_index = index + 1;
+ if (next_index === role_ids.length) {
+ next_index = 0;
}
+ return role_ids[next_index];
}
function make_list(first: number, last: number) {
diff --git a/tsconfig.json b/tsconfig.json
index 825d119..6517e5c 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,6 +1,6 @@
{
"compilerOptions": {
- "target": "es2016",
+ "target": "es2021",
"declaration": false,
"sourceMap": false,
"module": "commonjs",