diff options
Diffstat (limited to 'rules.ts')
-rw-r--r-- | rules.ts | 388 |
1 files changed, 265 insertions, 123 deletions
@@ -1,5 +1,17 @@ 'use strict'; +import { + CardId, + EngineNode, + FactionId, + FunctionNode, + Game, + LeafNode, + Player, + States, + View, +} from './types'; + import data, { // LIBERTY, // COLLECTIVIZATION, @@ -17,89 +29,23 @@ import data, { GOVERNMENT, SOVIET_SUPPORT, FOREIGN_AID, + PLAYER_WITH_MOST_HERO_POINTS, // StaticData, // PLAYER_WITH_MOST_HERO_POINTS, } from './data'; -declare const brand: unique symbol; - -// branded typing -type Brand<T, TBrand extends string> = T & { - [brand]: TBrand; -}; -export type CardId = Brand<number, 'CardId'>; -export type Player = Brand<string, 'Player'>; -export type FactionId = Brand<string, 'FactionId'>; - -interface Game { - [index: number]: any; - seed: number; - log: string[]; - undo: Game[]; - turn: number; - year: number; - active: Player | null; - 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; - n: number; - s: number; - }; - hands: Record<FactionId, CardId[]>; - hero_points: Record<FactionId, number>; - initiative: FactionId; - tableaus: Record<FactionId, CardId[]>; - tracks: number[]; - - result?: string; - victory?: string; - - 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; - actions?: any; - victory?: string; - location?: string; - selected?: string; - fronts: Game['fronts']; - current_events: CardId[]; - hand: CardId[]; - tracks: number[]; -} - // interface State { // inactive: string; // prompt: () => void; // } -type States = { - [key: string]: any; -}; - const states = {} as States; let game = {} as Game; // = null var view = {} as View; // = null -const ANARCHIST = 'Anarchist' as Player; -const COMMUNIST = 'Communist' as Player; -const MODERATE = 'Moderate' as Player; +export const ANARCHIST = 'Anarchist' as Player; +export const COMMUNIST = 'Communist' as Player; +export const MODERATE = 'Moderate' as Player; const ANARCHISTS_ID = 'a' as FactionId; const COMMUNISTS_ID = 'c' as FactionId; @@ -147,6 +93,10 @@ const faction_cards = { [MODERATES_ID]: make_list(1, 18) as CardId[], }; +const medaillons = make_list(0, 8) as number[]; + +console.log('medaillons', medaillons); + const fascist_decks = { 1: make_list(55, 62), }; @@ -168,9 +118,9 @@ function gen_action_card(card_id: CardId) { gen_action('card', card_id); } -function gen_action_space(space) { - gen_action('space', space); -} +// function gen_action_space(space) { +// gen_action('space', space); +// } // function gen_action_piece(piece) { // gen_action('piece', piece); @@ -220,9 +170,10 @@ function setup_bag_of_glory() { function setup_choose_card() { console.log('setup_choose_card'); - game.engine = roles.map((role) => ({ + const player_order = get_player_order(); + game.engine = player_order.map((faction_id) => ({ t: leaf_node, - p: player_faction_map[role], + p: faction_id, s: 'choose_card', })); game.engine.push({ @@ -234,11 +185,8 @@ function setup_choose_card() { 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) => ({ + const player_order = get_player_order(); + game.engine = player_order.map((faction_id) => ({ t: seq_node, c: [ { @@ -268,38 +216,15 @@ const engine_functions: Record<string, Function> = { 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 { +function get_active( + engine: EngineNode[] +): { parent: EngineNode[]; node: 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 === leaf_node || i.t === function_node) && i.r !== resolved) { + return { parent: engine, node: i }; } if (i.t === seq_node) { - const next_child = get_active_node(i.c); + const next_child = get_active(i.c); if (next_child !== null) { return next_child; } @@ -308,6 +233,41 @@ function get_active_node(engine: EngineNode[]): FunctionNode | LeafNode | null { return null; } +// function get_active_node_parent(engine: EngineNode[]): EngineNode[] | null { +// for (let i of engine) { +// if ((i.t === leaf_node || i.t === function_node) && i.r !== resolved) { +// return engine; +// } +// if (i.t === seq_node) { +// const next_child = get_active_node_parent(i.c); +// if (next_child !== null) { +// return next_child; +// } +// } +// } +// return null; +// } + +function get_active_node(engine: EngineNode[]): FunctionNode | LeafNode | null { + const a = get_active(engine); + return a === null ? null : a.node; + // for (let i of engine) { + // if ((i.t === leaf_node || i.t === function_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) { @@ -316,6 +276,36 @@ function get_active_node_args(): any { return null; } +function insert_before_or_after_active_node( + node: EngineNode, + position: 'before' | 'after', + engine: EngineNode[] = game.engine +) { + const a = get_active(engine); + if (a === null) { + return; + } + const i = a.parent.indexOf(a.node); + console.log('insert_before_active_node', i); + if (i >= 0) { + array_insert(a.parent, i + (position == 'after' ? 1 : 0), node); + } +} + +function insert_after_active_node( + node: EngineNode, + engine: EngineNode[] = game.engine +) { + insert_before_or_after_active_node(node, 'after', engine); +} + +function insert_before_active_node( + node: EngineNode, + engine: EngineNode[] = game.engine +) { + insert_before_or_after_active_node(node, 'before', engine); +} + function next() { console.log('next'); const node = get_active_node(game.engine); @@ -364,9 +354,13 @@ function game_view(state: Game, player: Player) { prompt: null, location: game.location, selected: game.selected, + + bonuses: game.bonuses, current_events: game.current_events, fronts: game.fronts, hand: game.hands[faction_id], + medaillons: game.medaillons, + selected_card: game.cards_in_play[faction_id], tracks: game.tracks, }; @@ -398,6 +392,7 @@ export function setup(seed: number, _scenario: string, _options: unknown) { [COMMUNISTS_ID]: 1, [MODERATES_ID]: 1, }, + blank_markers: [[], [], [], [], []], bonuses: [ON, ON], current_events: [], engine: [], @@ -416,6 +411,7 @@ export function setup(seed: number, _scenario: string, _options: unknown) { [ANARCHISTS_ID]: 2, [COMMUNISTS_ID]: 2, [MODERATES_ID]: 0, + pool: 14, }, cards_in_play: { [ANARCHISTS_ID]: null, @@ -423,6 +419,13 @@ export function setup(seed: number, _scenario: string, _options: unknown) { [MODERATES_ID]: null, }, initiative: MODERATES_ID, + medaillons: [ + draw_item(medaillons), + draw_item(medaillons), + draw_item(medaillons), + draw_item(medaillons), + draw_item(medaillons), + ], tableaus: { [ANARCHISTS_ID]: [], [COMMUNISTS_ID]: [], @@ -528,6 +531,14 @@ states.resolve_event = { gen_action('front', effect.target); } else 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); }, @@ -545,6 +556,18 @@ states.resolve_event = { log_h3(`${track_names[effect.target]} decreased by ${Math.abs(value)}`); resolve_active_and_proceed(); }, + 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.choose_card = { @@ -567,8 +590,45 @@ states.choose_card = { states.player_turn = { inactive: 'play their turn', prompt() { - view.prompt = 'Play your card or spend Hero points'; - gen_action_card(game.cards_in_play[player_faction_map[game.active]]); + const faction_id = get_faction_id(game.active); + const hero_points = game.hero_points[faction_id]; + view.prompt = + hero_points === 0 + ? 'Play your card' + : 'Play your card or spend Hero points'; + + // const card = game.cards_in_play[faction_id]; + if (game.cards_in_play[faction_id] !== null) { + // gen_action_card(); + gen_action('play_for_ap'); + gen_action('play_for_event'); + } + if (hero_points > 0) { + gen_action('spend_hp'); + } + }, + play_for_ap() { + const faction_id = get_faction_id(game.active); + const card = game.cards_in_play[faction_id]; + log_h3(`${game.active} plays ${cards[card].title} for the Action Points`); + resolve_active_and_proceed(); + }, + play_for_event() { + const faction_id = get_faction_id(game.active); + const card = game.cards_in_play[faction_id]; + log_h3(`${game.active} plays ${cards[card].title} for the Event`); + game.cards_in_play[faction_id] = null; + game.tableaus[faction_id].push(card); + resolve_active_and_proceed(); + }, + spend_hp() { + insert_before_active_node({ + t: leaf_node, + p: get_faction_id(game.active), + s: 'spend_hero_points', + }); + // insert spend hero points node before current node + next(); }, card(c: CardId) { const faction = get_active_faction(); @@ -587,6 +647,27 @@ states.player_turn = { }, }; +states.spend_hero_points = { + inactive: 'spend Hero points', + prompt() { + view.prompt = 'Spend your Hero points'; + gen_action('done'); + + gen_action('draw_card'); + }, + done() { + resolve_active_and_proceed(); + }, + 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(); + } + }, +}; + // states.move_to = { // inactive: 'move', // prompt() { @@ -635,6 +716,8 @@ function resolve_fascist_test() { next(); } +function move_track(track: number, change: number) {} + // #endregion // #region CARDS @@ -648,16 +731,31 @@ function resolve_fascist_test() { // } function draw_card(deck: CardId[]): CardId { - console.log('draw_card_deck', deck); clear_undo(); let i = random(deck.length); - console.log('random ', i); let c = deck[i] as CardId; - console.log('draw_card_id', c); set_delete(deck, c); return c; } +function draw_item(ids: number[]): number { + let i = random(ids.length); + let r = ids[i] as CardId; + set_delete(ids, r); + return r; +} + +function lose_hero_point(faction: FactionId, value: number) { + const points_lost = Math.min(game.hero_points[faction], value); + game.hero_points.pool += points_lost; + game.hero_points[faction] -= points_lost; + if (points_lost === 1) { + log(`${get_player(faction)} loses 1 Hero Point`); + } else { + log(`${get_player(faction)} loses ${points_lost} Hero Points`); + } +} + // #endregion // #region EVENTS @@ -695,7 +793,7 @@ function get_event_prompt(effect: CardEffect) { case 'bonus': break; case 'hero_points': - break; + return 'Select player with most Hero points'; case 'track': return 'Decrease ' + track_names[effect.target]; } @@ -826,13 +924,57 @@ function get_active_faction(): FactionId { return player_faction_map[game.active]; } +function get_active_faction_id(): FactionId { + return player_faction_map[game.active]; +} + +function get_faction_id(player: Player): FactionId { + return player_faction_map[player]; +} + +function get_player(faction_id: FactionId) { + return faction_player_map[faction_id]; +} + +function get_player_order(first_player = game.initiative): FactionId[] { + const order = []; + let faction = first_player; + for (let i = 0; i < 3; ++i) { + order.push(faction); + faction = + game.year === 2 + ? get_previous_faction(faction) + : get_next_faction(faction); + } + + return order; +} + +function get_previous_faction(faction_id: FactionId): FactionId { + const index = role_ids.indexOf(faction_id); + if (index === 0) { + return role_ids[2]; + } + return role_ids[index - 1]; +} + 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; + if (index === 2) { + return role_ids[0]; } - return role_ids[next_index]; + return role_ids[index + 1]; +} + +function get_factions_with_most_hero_poins() { + const most_hero_points = Math.max(...Object.values(game.hero_points)); + const faction_ids = []; + Object.entries(game.hero_points).forEach(([faction, hero_points]) => { + if (hero_points === most_hero_points) { + faction_ids.push(faction); + } + }); + return faction_ids; } function make_list(first: number, last: number) { @@ -873,10 +1015,10 @@ function array_remove<T>(array: T[], index: number) { array.length = n - 1; } -// function array_insert<T>(array: T[], index: number, item: T) { -// for (let i = array.length; i > index; --i) array[i] = array[i - 1]; -// array[index] = item; -// } +function array_insert<T>(array: T[], index: number, item: T) { + for (let i = array.length; i > index; --i) array[i] = array[i - 1]; + array[index] = item; +} // function array_remove_pair<T>(array: T[], index: number) { // let n = array.length; |