From 9704b9f460e37ba6e461f06def50f4fec76f8fef Mon Sep 17 00:00:00 2001 From: Frans Bongers Date: Mon, 16 Dec 2024 21:11:53 +0100 Subject: update resolve card icons, track limits and spend hero points --- data.js | 2 +- data.ts | 2 +- rules.js | 142 ++++++++++++++++++++++----- rules.ts | 333 ++++++++++++++++++++++++++++++--------------------------------- 4 files changed, 276 insertions(+), 203 deletions(-) diff --git a/data.js b/data.js index d4fb1b2..2941af7 100644 --- a/data.js +++ b/data.js @@ -841,7 +841,7 @@ const data = { { id: 61, effects: [ - create_effect('attack', ARAGON, -2), + create_effect('attack', ARAGON, -4), create_effect('attack', CLOSEST_TO_VICTORY, -2), create_effect('hero_points', PLAYER_WITH_MOST_HERO_POINTS, -1), ], diff --git a/data.ts b/data.ts index 806c9b8..a083d8f 100644 --- a/data.ts +++ b/data.ts @@ -887,7 +887,7 @@ const data: StaticData = { { id: 61, effects: [ - create_effect('attack', ARAGON, -2), + create_effect('attack', ARAGON, -4), create_effect('attack', CLOSEST_TO_VICTORY, -2), create_effect('hero_points', PLAYER_WITH_MOST_HERO_POINTS, -1), ], diff --git a/rules.js b/rules.js index 667c7a0..1029056 100644 --- a/rules.js +++ b/rules.js @@ -22,7 +22,7 @@ const player_faction_map = { const front_names = { a: 'Aragon Front', m: 'Madrid Front', - n: 'Nothern Front', + n: 'Northern Front', s: 'Southern Front', d: 'the Front closest to Defeat', v: 'the Front closest to Victory', @@ -340,17 +340,21 @@ function setup(seed, _scenario, _options) { start_year(); return game; } -function draw_hand_cards() { - role_ids.forEach((role) => { - const deck = list_deck(role); - for (let i = 0; i < 5; i++) { - game.hands[role].push(draw_card(deck)); - } - }); +function draw_hand_cards(faction_id, count) { + const log = count === 1 + ? `${get_player(faction_id)} draws 1 card` + : `${get_player(faction_id)} draws ${count} cards`; + logi(log); + for (let i = 0; i < count; i++) { + const deck = list_deck(faction_id); + game.hands[faction_id].push(draw_card(deck)); + } } function start_year() { game.current_events = []; - draw_hand_cards(); + role_ids.forEach((role) => { + draw_hand_cards(role, 5); + }); start_turn(); } function start_turn() { @@ -377,6 +381,10 @@ states.activate_icon = { } }, add_to_front() { + insert_after_active_node(create_leaf_node('add_to_front', get_active_faction(), { + t: data_1.ANY, + v: get_icon_count_in_tableau('add_to_front'), + })); resolve_active_and_proceed(); }, collectivization() { @@ -404,6 +412,7 @@ states.activate_icon = { resolve_active_and_proceed(); }, draw_card() { + draw_hand_cards(get_active_faction(), get_icon_count_in_tableau('draw_card')); resolve_active_and_proceed(); }, foreign_aid() { @@ -411,10 +420,15 @@ states.activate_icon = { resolve_active_and_proceed(); }, government() { - move_track(data_1.GOVERNMENT, get_icon_count_in_tableau('government')); + const direction = game.active === data_1.COMMUNIST ? -1 : 1; + move_track(data_1.GOVERNMENT, direction * get_icon_count_in_tableau('government')); + resolve_active_and_proceed(); + }, + government_to_center() { + const direction = game.tracks[data_1.GOVERNMENT] >= 6 ? -1 : 1; + move_track(data_1.GOVERNMENT, direction * get_icon_count_in_tableau('government_to_center')); resolve_active_and_proceed(); }, - government_to_center() { }, liberty() { move_track(data_1.LIBERTY, get_icon_count_in_tableau('liberty')); resolve_active_and_proceed(); @@ -465,7 +479,7 @@ states.add_to_front = { }, front(f) { const value = get_active_node_args().v; - update_front(f, value); + update_front(f, value, get_active_faction()); resolve_active_and_proceed(); }, }; @@ -474,7 +488,6 @@ states.attack_front = { prompt() { const node = get_active_node(); const front = node.a.t; - view.prompt = 'Attack ' + front_names[front]; let targets = []; if (front === 'd' || front === 'v') { targets = get_fronts_closest_to(front); @@ -488,6 +501,10 @@ states.attack_front = { else { targets.push(front); } + view.prompt = + targets.length === 1 + ? `Attack ${front_names[targets[0]]}` + : 'Attack a front'; targets.forEach((id) => gen_action('front', id)); }, front(f) { @@ -524,7 +541,6 @@ states.choose_area_ap = { resolve_active_and_proceed(); }, standee(track_id) { - console.log('standee', track_id); insert_after_active_node({ t: leaf_node, p: get_active_faction_id(), @@ -718,11 +734,8 @@ states.player_turn = { resolve_active_and_proceed(); }, spend_hp() { - insert_before_active_node({ - t: leaf_node, - p: get_faction_id(game.active), - s: 'spend_hero_points', - }); + insert_before_active_node(create_leaf_node('spend_hero_points', get_active_faction())); + log('Spends Hero Points'); next(); }, }; @@ -731,17 +744,61 @@ states.spend_hero_points = { prompt() { view.prompt = 'Spend your Hero points'; gen_action('done'); + const hero_points = game.hero_points[get_active_faction()]; + if (hero_points === 0) { + return; + } gen_action('draw_card'); + if (hero_points < 2) { + return; + } + gen_action_standee(data_1.FOREIGN_AID); + gen_action_standee(data_1.SOVIET_SUPPORT); + for (const bonus of bonuses) { + if (game.bonuses[bonus] === data_1.OFF) { + gen_action_bonus(bonus); + } + } + if (hero_points < 3) { + return; + } + gen_action_standee(data_1.COLLECTIVIZATION); + gen_action_standee(data_1.LIBERTY); + if (hero_points < 4) { + return; + } + gen_action_standee(data_1.GOVERNMENT); }, done() { resolve_active_and_proceed(); }, + bonus(b) { + update_bonus(b, data_1.ON); + pay_hero_points(get_active_faction(), 2); + }, draw_card() { - game.hero_points[get_active_faction_id()]--; - log(`${game.active} draws a card`); - if (game.hero_points[get_active_faction_id()] === 0) { - resolve_active_and_proceed(); + const faction = get_active_faction(); + pay_hero_points(faction, 1); + draw_hand_cards(faction, 1); + }, + standee(track_id) { + let amount = 2; + if (track_id === data_1.LIBERTY || track_id === data_1.COLLECTIVIZATION) { + amount = 3; } + else if (track_id === data_1.GOVERNMENT) { + amount = 4; + } + const faction = get_active_faction(); + pay_hero_points(faction, amount); + insert_after_active_node(create_seq_node([ + create_leaf_node('move_track_up_or_down', faction, { + track_id, + strength: 1, + }), + create_leaf_node('spend_hero_points', faction), + ])); + resolve_active_and_proceed(); }, }; function pop_undo() { @@ -851,11 +908,36 @@ function get_fronts_to_add_to(target) { return [target]; } } +function get_max_value_for_track(track_id) { + switch (track_id) { + case data_1.LIBERTY: + return game.tracks[data_1.COLLECTIVIZATION] >= 8 ? 10 : 7; + case data_1.GOVERNMENT: + return game.tracks[data_1.FOREIGN_AID] >= 8 ? 10 : 7; + case data_1.COLLECTIVIZATION: + case data_1.SOVIET_SUPPORT: + case data_1.FOREIGN_AID: + default: + return 10; + } +} +function get_min_value_for_track(track_id) { + switch (track_id) { + case data_1.GOVERNMENT: + return game.tracks[data_1.SOVIET_SUPPORT] >= 8 ? 4 : 1; + case data_1.LIBERTY: + case data_1.COLLECTIVIZATION: + case data_1.SOVIET_SUPPORT: + case data_1.FOREIGN_AID: + default: + return 0; + } +} function move_track(track_id, change) { const current_value = game.tracks[track_id]; let new_value = current_value + change; - new_value = Math.max(new_value, track_id === data_1.GOVERNMENT ? 1 : 0); - new_value = Math.min(new_value, 10); + new_value = Math.max(new_value, get_min_value_for_track(track_id)); + new_value = Math.min(new_value, get_max_value_for_track(track_id)); game.tracks[track_id] = new_value; logi(`${get_track_name(track_id)} to ${new_value}`); check_initiative(); @@ -876,6 +958,10 @@ function move_track(track_id, change) { } }); } +function pay_hero_points(faction, amount) { + game.hero_points[faction] -= amount; + game.hero_points.pool += amount; +} function update_bonus(bonus_id, status) { if (game.bonuses[bonus_id] === status) { return; @@ -893,8 +979,14 @@ function update_front(front_id, change, faction_id = null) { game.fronts[front_id].contributions.length > 0) { change += 1; } + const value_before = game.fronts[front_id].value; game.fronts[front_id].value += change; logi(`${front_names[front_id]}: ${change > 0 ? '+' : ''}${change}`); + if (faction_id !== null && + value_before <= 0 && + game.fronts[front_id].value > 0) { + gain_hero_points(faction_id, 1); + } if (faction_id !== null && !game.fronts[front_id].contributions.includes(faction_id)) { game.fronts[front_id].contributions.push(faction_id); diff --git a/rules.ts b/rules.ts index 2fea19e..796f887 100644 --- a/rules.ts +++ b/rules.ts @@ -86,7 +86,7 @@ const player_faction_map: Record = { const front_names: Record = { a: 'Aragon Front', m: 'Madrid Front', - n: 'Nothern Front', + n: 'Northern Front', s: 'Southern Front', d: 'the Front closest to Defeat', v: 'the Front closest to Victory', @@ -490,13 +490,16 @@ export function setup(seed: number, _scenario: string, _options: unknown) { return game; } -function draw_hand_cards() { - role_ids.forEach((role) => { - const deck = list_deck(role); - for (let i = 0; i < 5; i++) { - game.hands[role].push(draw_card(deck)); - } - }); +function draw_hand_cards(faction_id: FactionId, count: number) { + const log = + count === 1 + ? `${get_player(faction_id)} draws 1 card` + : `${get_player(faction_id)} draws ${count} cards`; + logi(log); + for (let i = 0; i < count; i++) { + const deck = list_deck(faction_id); + game.hands[faction_id].push(draw_card(deck)); + } } // #endregion @@ -504,7 +507,9 @@ function draw_hand_cards() { function start_year() { // log_h1('Year ' + game.year); game.current_events = []; - draw_hand_cards(); + role_ids.forEach((role) => { + draw_hand_cards(role, 5); + }); start_turn(); } @@ -545,7 +550,12 @@ states.activate_icon = { } }, add_to_front() { - // insert state to select front + insert_after_active_node( + create_leaf_node('add_to_front', get_active_faction(), { + t: ANY, + v: get_icon_count_in_tableau('add_to_front'), + }) + ); resolve_active_and_proceed(); }, collectivization() { @@ -579,7 +589,10 @@ states.activate_icon = { resolve_active_and_proceed(); }, draw_card() { - // TODO: draw cards + draw_hand_cards( + get_active_faction(), + get_icon_count_in_tableau('draw_card') + ); resolve_active_and_proceed(); }, foreign_aid() { @@ -587,10 +600,18 @@ states.activate_icon = { resolve_active_and_proceed(); }, government() { - move_track(GOVERNMENT, get_icon_count_in_tableau('government')); + const direction = game.active === COMMUNIST ? -1 : 1; + move_track(GOVERNMENT, direction * get_icon_count_in_tableau('government')); + resolve_active_and_proceed(); + }, + government_to_center() { + const direction = game.tracks[GOVERNMENT] >= 6 ? -1 : 1; + move_track( + GOVERNMENT, + direction * get_icon_count_in_tableau('government_to_center') + ); resolve_active_and_proceed(); }, - government_to_center() {}, liberty() { move_track(LIBERTY, get_icon_count_in_tableau('liberty')); resolve_active_and_proceed(); @@ -642,7 +663,7 @@ states.add_to_front = { }, front(f: FrontId) { const value = get_active_node_args().v; - update_front(f, value); + update_front(f, value, get_active_faction()); resolve_active_and_proceed(); }, }; @@ -652,18 +673,23 @@ states.attack_front = { prompt() { const node = get_active_node(); const front = node.a.t; - view.prompt = 'Attack ' + front_names[front]; - let targets = []; - + + let targets: Array = []; + if (front === 'd' || front === 'v') { targets = get_fronts_closest_to(front); } else if (game.fronts[front].status === DEFEAT) { - targets = get_fronts_closest_to('d') + targets = get_fronts_closest_to('d'); } else if (game.fronts[front].status === VICTORY) { targets = get_fronts_to_add_to(ANY); } else { targets.push(front); } + view.prompt = + targets.length === 1 + ? `Attack ${front_names[targets[0]]}` + : 'Attack a front'; + targets.forEach((id) => gen_action('front', id)); }, front(f: FrontId) { @@ -702,7 +728,6 @@ states.choose_area_ap = { resolve_active_and_proceed(); }, standee(track_id: number) { - console.log('standee', track_id); insert_after_active_node({ t: leaf_node, p: get_active_faction_id(), @@ -912,85 +937,82 @@ states.player_turn = { resolve_active_and_proceed(); }, spend_hp() { - insert_before_active_node({ - t: leaf_node, - p: get_faction_id(game.active as Player), - s: 'spend_hero_points', - }); // insert spend hero points node before current node + // so it will return to current node after resolving + insert_before_active_node( + create_leaf_node('spend_hero_points', get_active_faction()) + ); + log('Spends Hero Points'); + next(); }, - // 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.chosen_cards[faction] = null; - // game.tableaus[faction].push(c); - // array_remove(game.hands[faction], game.hands[faction].indexOf(c)); - // 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: Effect = card.effects[node.a]; -// view.prompt = get_event_prompt(effect); - -// if (effect.type === 'track') { -// gen_action('standee', effect.target); -// } else if ( -// effect.type === 'hero_points' && -// effect.target === PLAYER_WITH_MOST_HERO_POINTS -// ) { -// const factions = get_factions_with_most_hero_poins(); -// for (let faction_id of factions) { -// gen_action(get_player(faction_id)); -// } -// } -// // for (let p = 0; p < 5; ++p) gen_action('standee', p); -// }, - -// Anarchist() { -// lose_hero_point(ANARCHISTS_ID, 1); -// resolve_active_and_proceed(); -// }, -// Communist() { -// lose_hero_point(ANARCHISTS_ID, 1); -// resolve_active_and_proceed(); -// }, -// Moderate() { -// lose_hero_point(ANARCHISTS_ID, 1); -// resolve_active_and_proceed(); -// }, -// }; - states.spend_hero_points = { inactive: 'spend Hero points', prompt() { view.prompt = 'Spend your Hero points'; gen_action('done'); + const hero_points = game.hero_points[get_active_faction()]; + if (hero_points === 0) { + return; + } gen_action('draw_card'); + + if (hero_points < 2) { + return; + } + gen_action_standee(FOREIGN_AID); + gen_action_standee(SOVIET_SUPPORT); + for (const bonus of bonuses) { + if (game.bonuses[bonus] === OFF) { + gen_action_bonus(bonus); + } + } + + if (hero_points < 3) { + return; + } + gen_action_standee(COLLECTIVIZATION); + gen_action_standee(LIBERTY); + + if (hero_points < 4) { + return; + } + gen_action_standee(GOVERNMENT); }, done() { resolve_active_and_proceed(); }, + bonus(b: number) { + update_bonus(b, ON); + pay_hero_points(get_active_faction(), 2); + }, draw_card() { - game.hero_points[get_active_faction_id()]--; - log(`${game.active} draws a card`); - // TODO: Draw card - if (game.hero_points[get_active_faction_id()] === 0) { - resolve_active_and_proceed(); + const faction = get_active_faction(); + pay_hero_points(faction, 1); + draw_hand_cards(faction, 1); + }, + standee(track_id: number) { + let amount = 2; + if (track_id === LIBERTY || track_id === COLLECTIVIZATION) { + amount = 3; + } else if (track_id === GOVERNMENT) { + amount = 4; } + const faction = get_active_faction(); + pay_hero_points(faction, amount); + insert_after_active_node( + create_seq_node([ + create_leaf_node('move_track_up_or_down', faction, { + track_id, + strength: 1, + }), + create_leaf_node('spend_hero_points', faction), + ]) + ); + resolve_active_and_proceed(); }, }; @@ -1131,26 +1153,50 @@ function resolve_fascist_test() { } // TODO: check for defeated / won fronts -function get_fronts_to_add_to(target: string): string[] { +function get_fronts_to_add_to(target: string): FrontId[] { console.log('get_fronts_to_add_to', target); if (target === CLOSEST_TO_DEFEAT || target === CLOSEST_TO_VICTORY) { return get_fronts_closest_to(target); } else if (target === ANY) { return FRONTS.filter((id) => game.fronts[id].status === null); } else { - return [target]; + return [target as FrontId]; + } +} + +function get_max_value_for_track(track_id: number) { + switch (track_id) { + case LIBERTY: + return game.tracks[COLLECTIVIZATION] >= 8 ? 10 : 7; + case GOVERNMENT: + return game.tracks[FOREIGN_AID] >= 8 ? 10 : 7; + case COLLECTIVIZATION: + case SOVIET_SUPPORT: + case FOREIGN_AID: + default: + return 10; + } +} + +function get_min_value_for_track(track_id: number) { + switch (track_id) { + case GOVERNMENT: + return game.tracks[SOVIET_SUPPORT] >= 8 ? 4 : 1; + case LIBERTY: + case COLLECTIVIZATION: + case SOVIET_SUPPORT: + case FOREIGN_AID: + default: + return 0; } } function move_track(track_id: number, change: number) { - // Check if track can be moved - // Updata values - // Check all triggers - // Add states for triggers const current_value = game.tracks[track_id]; let new_value = current_value + change; - new_value = Math.max(new_value, track_id === GOVERNMENT ? 1 : 0); - new_value = Math.min(new_value, 10); + new_value = Math.max(new_value, get_min_value_for_track(track_id)); + + new_value = Math.min(new_value, get_max_value_for_track(track_id)); game.tracks[track_id] = new_value; logi(`${get_track_name(track_id)} to ${new_value}`); @@ -1178,6 +1224,11 @@ function move_track(track_id: number, change: number) { }); } +function pay_hero_points(faction: FactionId, amount: number) { + game.hero_points[faction] -= amount; + game.hero_points.pool += amount; +} + function update_bonus(bonus_id: number, status: number) { if (game.bonuses[bonus_id] === status) { return; @@ -1206,9 +1257,16 @@ function update_front( ) { change += 1; } - + const value_before = game.fronts[front_id].value; game.fronts[front_id].value += change; logi(`${front_names[front_id]}: ${change > 0 ? '+' : ''}${change}`); + if ( + faction_id !== null && + value_before <= 0 && + game.fronts[front_id].value > 0 + ) { + gain_hero_points(faction_id, 1); + } // Add token to front if player contributed if ( @@ -1234,12 +1292,14 @@ function defeat_on_a_front(front_id: FrontId) { 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), - ])); + 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) { @@ -1338,88 +1398,9 @@ function lose_hero_point(faction: FactionId, value: number) { // #endregion -// #region EVENTS - -// 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: Effect) { -// let prompt = ''; -// switch (effect.type) { -// case 'attack': -// return 'Attack ' + front_names[effect.target as string]; -// case 'bonus': -// break; -// case 'hero_points': -// return 'Select player with most Hero points'; -// case 'track': -// return 'Decrease ' + tracks[effect.target].name; -// } -// return prompt; -// } - -// 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: 'd' | 'v') { +function get_fronts_closest_to(target: 'd' | 'v'): FrontId[] { const values = Object.values(game.fronts).reduce( (accrued: number[], current: Front) => { if (current.status === null) { @@ -1433,7 +1414,7 @@ function get_fronts_closest_to(target: 'd' | 'v') { target === 'd' ? Math.min(...values) : Math.max(...values); return Object.keys(game.fronts).filter( (frontId) => game.fronts[frontId].value === targetValue - ); + ) as FrontId[]; } // #endregion -- cgit v1.2.3