summaryrefslogtreecommitdiff
path: root/rules.js
diff options
context:
space:
mode:
authorFrans Bongers <fransbongers@franss-mbp.home>2024-11-28 22:36:10 +0100
committerFrans Bongers <fransbongers@franss-mbp.home>2024-11-28 22:36:10 +0100
commit629206f773d5fd4c9247db03e3a705c4dcdc77c4 (patch)
tree692b809c4bfc01a452b3ae110310a9103a720b44 /rules.js
parent9414fe91218a00fe9e44b48fdf40e51de5cb4479 (diff)
downloadland-and-freedom-629206f773d5fd4c9247db03e3a705c4dcdc77c4.tar.gz
setup game engine
Diffstat (limited to 'rules.js')
-rw-r--r--rules.js584
1 files changed, 485 insertions, 99 deletions
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 = [];