From 629206f773d5fd4c9247db03e3a705c4dcdc77c4 Mon Sep 17 00:00:00 2001 From: Frans Bongers Date: Thu, 28 Nov 2024 22:36:10 +0100 Subject: setup game engine --- rules.js | 584 ++++++++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 485 insertions(+), 99 deletions(-) (limited to 'rules.js') 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 = []; -- cgit v1.2.3