diff options
author | Frans Bongers <fransbongers@franss-mbp.home> | 2025-02-16 19:51:08 +0100 |
---|---|---|
committer | Frans Bongers <fransbongers@franss-mbp.home> | 2025-02-16 19:53:04 +0100 |
commit | dbd1660fc0f297d2b7b571af6038e53d6596161c (patch) | |
tree | 9966947e32fadfd457d6c227159b7ba3165a263b | |
parent | e798a3c84d9c50e6723ab0fd7f22a9328a81199e (diff) | |
download | land-and-freedom-dbd1660fc0f297d2b7b571af6038e53d6596161c.tar.gz |
initial multiactive updates
-rw-r--r-- | play.ts | 2 | ||||
-rw-r--r-- | rules.js | 115 | ||||
-rw-r--r-- | rules.ts | 145 | ||||
-rw-r--r-- | types.d.ts | 6 |
4 files changed, 158 insertions, 110 deletions
@@ -1,6 +1,6 @@ 'use strict'; -import { CardId, StaticData, View } from './types'; +import { StaticData, View } from './types'; declare function action_button(action: string, text: string): void; // declare function register_action(element: HTMLElement, type: string, s: number): void; @@ -134,9 +134,7 @@ function setup_bag_of_glory() { next(); } function setup_choose_card() { - const player_order = get_player_order(); - game.engine = player_order.map((faction_id) => create_leaf_node('choose_card', faction_id)); - game.engine.push(create_leaf_node('confirm_turn', player_order[2])); + game.engine = [create_leaf_node('choose_card', 'all')]; game.engine.push(create_function_node('setup_player_turn')); next(); } @@ -289,6 +287,20 @@ function insert_after_active_node(node, engine = game.engine) { function insert_before_active_node(node, engine = game.engine) { insert_before_or_after_active_node(node, 'before', engine); } +function get_next_active(p) { + if (Array.isArray(p)) { + return p.map((faction) => faction_player_map[faction]); + } + if (p === 'all') { + return exports.roles; + } + if (p === 'None') { + return 'None'; + } + else { + return faction_player_map[p]; + } +} function next(checkpoint = false) { if (checkpoint) { clear_undo(); @@ -306,7 +318,7 @@ function next(checkpoint = false) { else if (node.t === 'l') { game.state = node.s; const current_active = game.active; - const next_active = faction_player_map[node.p]; + const next_active = get_next_active(node.p); if (next_active !== current_active && game.undo.length > 0) { insert_before_active_node(create_leaf_node('confirm_turn', get_active_faction())); game.state = 'confirm_turn'; @@ -332,6 +344,7 @@ function game_view(state, current) { game = state; const faction = current === OBSERVER ? null : player_faction_map[current]; view = { + active: game.active, engine: game.engine, log: game.log, prompt: null, @@ -480,20 +493,24 @@ function draw_hand_cards(faction_id, count) { if (game.medallions[faction_id].includes(data_1.INTELLIGENCE_MEDALLION_ID)) { count++; } - const drawn_cards = count; + let drawn_cards = 0; if (deck.length < count) { count = count - deck.length; + drawn_cards += deck.length; game.hands[faction_id] = game.hands[faction_id].concat(deck); game.discard[faction_id] = []; } + for (let i = 0; i < count; i++) { + const deck = list_deck(faction_id); + if (deck.length > 0) { + game.hands[faction_id].push(draw_card(deck)); + drawn_cards++; + } + } const log = drawn_cards === 1 ? `${get_player(faction_id)} draws 1 card` : `${get_player(faction_id)} draws ${drawn_cards} 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.year++; @@ -717,10 +734,17 @@ states.add_to_front = { gen_spend_hero_points(); const args = get_active_node_args(); const possible_fronts = get_fronts_to_add_to(args.t); - view.prompt = - possible_fronts.length === 1 - ? `Add strength to ${front_names[possible_fronts[0]]}` - : 'Add strength to a Front'; + const number_of_fronts = possible_fronts.length; + if (number_of_fronts === 0) { + view.prompt = 'No valid front to target. You must skip'; + gen_action('skip'); + } + else if (number_of_fronts === 1) { + view.prompt = `Add strength to ${front_names[possible_fronts[0]]}`; + } + else { + view.prompt = 'Add strength to a Front'; + } for (let f of possible_fronts) { gen_action_front(f); } @@ -736,55 +760,43 @@ states.add_to_front = { update_front(f, value, get_active_faction()); resolve_active_and_proceed(); }, + skip() { + resolve_active_and_proceed(); + }, }; states.attack_front = { inactive: 'attack a Front', prompt() { gen_spend_hero_points(); const { t: target, n, src } = get_active_node_args(); - let fronts = []; - if (target === data_1.ANY) { - fronts = get_fronts_to_add_to(data_1.ANY, n); - } - else if (target === 'd' || target === 'v') { - fronts = get_fronts_closest_to(target); - } - else if (game.fronts[target].status === data_1.DEFEAT) { - fronts = get_fronts_closest_to('d'); + const possible_fronts = get_fronts_to_add_to(target, n); + const number_of_fronts = possible_fronts.length; + if (number_of_fronts === 0) { + view.prompt = 'No valid front to target. You must skip'; + gen_action('skip'); } - else if (game.fronts[target].status === data_1.VICTORY) { - fronts = get_fronts_to_add_to(data_1.ANY); + else if (number_of_fronts === 1) { + view.prompt = `Attack ${front_names[possible_fronts[0]]}`; } else { - fronts.push(target); + view.prompt = 'Attack a Front'; } - view.prompt = - fronts.length === 1 - ? `Attack ${front_names[fronts[0]]}` - : 'Attack a front'; - let prefix = ''; if (src) { - prefix = `${get_source_name(src)}: `; - } - if (fronts.length === 1) { - view.prompt = prefix - ? `${prefix}attack ${front_names[fronts[0]]}` - : `Attack ${front_names[fronts[0]]}`; - } - else { - view.prompt = prefix ? `${prefix}attack a front` : `Attack a front`; + view.prompt = add_prompt_prefix(view.prompt, get_source_name(src)); } - fronts.forEach((id) => gen_action('front', id)); + possible_fronts.forEach((id) => gen_action('front', id)); }, spend_hp() { resolve_spend_hp(); }, front(f) { - const node = get_active_node(); - const value = node.a.v; + const value = get_active_node_args().v; update_front(f, value); resolve_active_and_proceed(); }, + skip() { + resolve_active_and_proceed(); + }, }; states.break_tie_final_bid = { inactive: 'break tie for Final Bid', @@ -840,7 +852,7 @@ states.change_active_player = { }, prompt() { view.prompt = ''; - } + }, }; states.choose_area_ap = { inactive: 'choose area to use Action Points', @@ -928,14 +940,15 @@ states.change_bonus = { }; states.choose_card = { inactive: 'choose a card', - prompt() { + prompt(player) { + console.log('player', player); gen_spend_hero_points(); const { src } = get_active_node_args(); view.prompt = 'Choose a card to play this turn'; if (src === 'momentum') { view.prompt = 'Choose a card to play'; } - const faction = get_active_faction(); + const faction = player_faction_map[player]; const hand = game.hands[faction]; for (let c of hand) { if (!game.selected_cards[faction].includes(c)) { @@ -946,14 +959,13 @@ states.choose_card = { spend_hp() { resolve_spend_hp(); }, - card(c) { - const faction = get_active_faction(); + card(c, player) { + const faction = player_faction_map[player]; game.selected_cards[faction].push(c); const { src } = get_active_node_args(); if (src === 'momentum') { game.played_card = game.selected_cards[faction][0]; } - resolve_active_and_proceed(); }, }; states.choose_final_bid = { @@ -1074,12 +1086,12 @@ states.draw_card = { draw_card() { const { v } = get_active_node_args(); draw_hand_cards(get_active_faction(), v); - resolve_active_and_proceed(); + resolve_active_and_proceed(true); }, draw_cards() { const { v } = get_active_node_args(); draw_hand_cards(get_active_faction(), v); - resolve_active_and_proceed(); + resolve_active_and_proceed(true); }, }; states.draw_glory = { @@ -2627,6 +2639,9 @@ function get_fronts_closest_to(target) { } return accrued; }, []); + if (values.length === 0) { + return []; + } const targetValue = target === 'd' ? Math.min(...values) : Math.max(...values); return Object.keys(game.fronts).filter((frontId) => game.fronts[frontId].value === targetValue); } @@ -224,7 +224,7 @@ const resolved = 1; function create_leaf_node<T = any>( state: string, - faction: FactionId | 'None', + faction: FactionId | 'None' | 'all' | FactionId[], args?: T ): LeafNode { return { @@ -272,13 +272,15 @@ function setup_bag_of_glory() { } function setup_choose_card() { - const player_order = get_player_order(); - game.engine = player_order.map((faction_id) => - create_leaf_node('choose_card', faction_id) - ); + game.engine = [create_leaf_node('choose_card', 'all')]; + + // const player_order = get_player_order(); + // game.engine = player_order.map((faction_id) => + // create_leaf_node('choose_card', faction_id) + // ); // Add extra confirm, otherwise first players card will be revealed // before confirm - game.engine.push(create_leaf_node('confirm_turn', player_order[2])); + // game.engine.push(create_leaf_node('confirm_turn', player_order[2])); game.engine.push(create_function_node('setup_player_turn')); next(); } @@ -474,6 +476,20 @@ function insert_before_active_node( insert_before_or_after_active_node(node, 'before', engine); } +function get_next_active(p: LeafNode['p']): Player | Player[] | 'None' { + if (Array.isArray(p)) { + return p.map((faction) => faction_player_map[faction]); + } + if (p === 'all') { + return roles; + } + if (p === 'None') { + return 'None'; + } else { + return faction_player_map[p]; + } +} + function next(checkpoint = false) { if (checkpoint) { clear_undo(); @@ -492,7 +508,9 @@ function next(checkpoint = false) { // Control switches to another player and player can undo // so ask to confirm turn const current_active = game.active; - const next_active = faction_player_map[node.p]; + + const next_active = get_next_active(node.p); + if (next_active !== current_active && game.undo.length > 0) { insert_before_active_node( create_leaf_node('confirm_turn', get_active_faction()) @@ -532,6 +550,7 @@ function game_view(state: Game, current: Player | 'Observer') { current === OBSERVER ? null : player_faction_map[current]; view = { + active: game.active, engine: game.engine, // TODO: remove log: game.log, prompt: null, @@ -694,25 +713,28 @@ function draw_hand_cards(faction_id: FactionId, count: number) { if (game.medallions[faction_id].includes(INTELLIGENCE_MEDALLION_ID)) { count++; } - const drawn_cards = count; + let drawn_cards = 0; // Draw all remaining cards if (deck.length < count) { count = count - deck.length; + drawn_cards += deck.length; game.hands[faction_id] = game.hands[faction_id].concat(deck); game.discard[faction_id] = []; } + for (let i = 0; i < count; i++) { + const deck = list_deck(faction_id); + if (deck.length > 0) { + game.hands[faction_id].push(draw_card(deck)); + drawn_cards++; + } + } const log = drawn_cards === 1 ? `${get_player(faction_id)} draws 1 card` : `${get_player(faction_id)} draws ${drawn_cards} 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 @@ -956,10 +978,18 @@ states.add_to_front = { gen_spend_hero_points(); const args = get_active_node_args(); const possible_fronts = get_fronts_to_add_to(args.t); - view.prompt = - possible_fronts.length === 1 - ? `Add strength to ${front_names[possible_fronts[0]]}` - : 'Add strength to a Front'; + + const number_of_fronts = possible_fronts.length; + + if (number_of_fronts === 0) { + view.prompt = 'No valid front to target. You must skip'; + gen_action('skip'); + } else if (number_of_fronts === 1) { + view.prompt = `Add strength to ${front_names[possible_fronts[0]]}`; + } else { + view.prompt = 'Add strength to a Front'; + } + for (let f of possible_fronts) { gen_action_front(f); } @@ -975,6 +1005,9 @@ states.add_to_front = { update_front(f, value, get_active_faction()); resolve_active_and_proceed(); }, + skip() { + resolve_active_and_proceed(); + }, }; states.attack_front = { @@ -983,47 +1016,35 @@ states.attack_front = { gen_spend_hero_points(); const { t: target, n, src } = get_active_node_args(); - let fronts: Array<FrontId> = []; + const possible_fronts = get_fronts_to_add_to(target, n); + const number_of_fronts = possible_fronts.length; - if (target === ANY) { - fronts = get_fronts_to_add_to(ANY, n); - } else if (target === 'd' || target === 'v') { - fronts = get_fronts_closest_to(target); - } else if (game.fronts[target].status === DEFEAT) { - fronts = get_fronts_closest_to('d'); - } else if (game.fronts[target].status === VICTORY) { - fronts = get_fronts_to_add_to(ANY); + if (number_of_fronts === 0) { + view.prompt = 'No valid front to target. You must skip'; + gen_action('skip'); + } else if (number_of_fronts === 1) { + view.prompt = `Attack ${front_names[possible_fronts[0]]}`; } else { - fronts.push(target); + view.prompt = 'Attack a Front'; } - view.prompt = - fronts.length === 1 - ? `Attack ${front_names[fronts[0]]}` - : 'Attack a front'; - let prefix = ''; if (src) { - prefix = `${get_source_name(src)}: `; - } - if (fronts.length === 1) { - view.prompt = prefix - ? `${prefix}attack ${front_names[fronts[0]]}` - : `Attack ${front_names[fronts[0]]}`; - } else { - view.prompt = prefix ? `${prefix}attack a front` : `Attack a front`; + view.prompt = add_prompt_prefix(view.prompt, get_source_name(src)); } - fronts.forEach((id) => gen_action('front', id)); + possible_fronts.forEach((id) => gen_action('front', id)); }, spend_hp() { resolve_spend_hp(); }, front(f: FrontId) { - const node = get_active_node(); - const value = node.a.v; + const value = get_active_node_args().v; update_front(f, value); resolve_active_and_proceed(); }, + skip() { + resolve_active_and_proceed(); + }, }; states.break_tie_final_bid = { @@ -1076,7 +1097,7 @@ states.break_tie_winner = { }; /** - * Change does not do anything, but it will change + * Change does not do anything, but it will change * active player and trigger a confirm for the previous * state */ @@ -1087,8 +1108,8 @@ states.change_active_player = { }, prompt() { view.prompt = ''; - } -} + }, +}; states.choose_area_ap = { inactive: 'choose area to use Action Points', @@ -1186,7 +1207,8 @@ states.change_bonus = { states.choose_card = { inactive: 'choose a card', - prompt() { + prompt(player: Player) { + console.log('player', player); gen_spend_hero_points(); const { src } = get_active_node_args<ChooseCardArgs>(); view.prompt = 'Choose a card to play this turn'; @@ -1194,7 +1216,7 @@ states.choose_card = { view.prompt = 'Choose a card to play'; } - const faction = get_active_faction(); + const faction = player_faction_map[player]; const hand = game.hands[faction]; for (let c of hand) { if (!game.selected_cards[faction].includes(c)) { @@ -1205,14 +1227,14 @@ states.choose_card = { spend_hp() { resolve_spend_hp(); }, - card(c: CardId) { - const faction = get_active_faction(); + card(c: CardId, player: Player) { + const faction = player_faction_map[player]; game.selected_cards[faction].push(c); const { src } = get_active_node_args<ChooseCardArgs>(); if (src === 'momentum') { game.played_card = game.selected_cards[faction][0]; } - resolve_active_and_proceed(); + // resolve_active_and_proceed(); }, }; @@ -1362,12 +1384,12 @@ states.draw_card = { draw_card() { const { v } = get_active_node_args(); draw_hand_cards(get_active_faction(), v); - resolve_active_and_proceed(); + resolve_active_and_proceed(true); }, draw_cards() { const { v } = get_active_node_args(); draw_hand_cards(get_active_faction(), v); - resolve_active_and_proceed(); + resolve_active_and_proceed(true); }, }; @@ -1384,7 +1406,7 @@ states.draw_glory = { game.glory.push(faction); - // TODO: remove if statement, just here atm to not break + // TODO: remove if statement, just here atm to not break // running games if (!game.glory_current_year) { game.glory_current_year = game.glory_current_year = { @@ -1397,7 +1419,11 @@ states.draw_glory = { array_remove(game.bag_of_glory, index); - log(`${get_player(get_active_faction())} draws <ft${faction}> from the Bag of Glory`) + log( + `${get_player( + get_active_faction() + )} draws <ft${faction}> from the Bag of Glory` + ); resolve_active_and_proceed(true); }, }; @@ -3299,6 +3325,13 @@ function get_fronts_closest_to(target: 'd' | 'v'): FrontId[] { }, [] ); + + // Possible if all fronts have either been + // defeated or are victorious + if (values.length === 0) { + return []; + } + const targetValue = target === 'd' ? Math.min(...values) : Math.max(...values); return Object.keys(game.fronts).filter( @@ -3382,7 +3415,7 @@ function add_prompt_prefix(prompt: string, prefix: string) { } function get_active_faction(): FactionId { - return player_faction_map[game.active]; + return player_faction_map[game.active as Player]; } function get_blank_marker_id(track_id: number, space_id: number) { @@ -20,7 +20,7 @@ export interface Game { // TODO: why is this needed? [index: number]: any; // RTT - active: Player | 'None' | null; + active: Player | Player[] | 'None' | null; log: string[]; seed: number; state: string | null; @@ -78,7 +78,7 @@ export interface Game { export interface View { engine: Game['engine']; log: number | string[]; - active?: string | null; + active: string | string[] | null; prompt: string | null; state: Game['state']; actions?: any; @@ -131,7 +131,7 @@ export interface SeqNode { export interface LeafNode<T = any> { t: 'l'; s: string; // State - p: FactionId | 'None'; // Player + p: FactionId | 'None' | 'all' | FactionId[]; // Player a?: T; // args r?: 0 | 1; // 1 if resolved } |