'use strict'; import data, { // LIBERTY, // COLLECTIVIZATION, // GOVERNMENT, // SOVIET_SUPPORT, // FOREIGN_AID, // MORALE_BONUS, // TEAMWORK_BONUS, // OFF, ON, EventCard, // StaticData, // PLAYER_WITH_MOST_HERO_POINTS, } from './data'; declare const brand: unique symbol; // branded typing type Brand = T & { [brand]: TBrand; }; export type CardId = Brand; export type Player = Brand; export type FactionId = Brand; interface Game { [index: number]: any; seed: number; log: number | string[]; undo: Game[]; turn: number; year: number; active: Player | null; state: string | null; bag_of_glory: Record; bonuses: number[]; current_events: CardId[]; fronts: { a: number; m: number; n: number; s: number; }; hands: Record; hero_points: Record; initiative: FactionId; tracks: number[]; result?: string; victory?: string; location?: string; selected?: string; // played_card: CardId // turn: Turn } export interface View { 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; const ANARCHISTS_ID = 'a' as FactionId; const COMMUNISTS_ID = 'c' as FactionId; const MODERATES_ID = 'm' as FactionId; const { cards, // fronts } = data; const faction_cards = { [ANARCHIST]: make_list(37, 54) as CardId[], [COMMUNIST]: make_list(19, 36) as CardId[], [MODERATE]: make_list(1, 18) as CardId[], }; const fascist_decks = { 1: make_list(55, 62), }; export const scenarios = ['Standard']; export const roles: Player[] = [ANARCHIST, COMMUNIST, MODERATE]; function gen_action(action, argument) { if (!(action in view.actions)) view.actions[action] = []; view.actions[action].push(argument); } function gen_action_space(space) { gen_action('space', space); } function gen_action_piece(piece) { gen_action('piece', piece); } // function gen_action_card(card) { // gen_action('card', card); // } export function action( state: any, player: Player, action: string, arg: unknown ) { game = state; let S = states[game.state]; if (action in S) S[action](arg, player); else if (action === 'undo' && game.undo && game.undo.length > 0) pop_undo(); else throw new Error('Invalid action: ' + action); return game; } // #region VIEW export { game_view as view }; function game_view(state: Game, player: Player) { console.log('game_view', state); console.log('player', player); game = state; const faction_id = get_faction_id(player); view = { log: game.log, prompt: null, location: game.location, selected: game.selected, current_events: game.current_events, fronts: game.fronts, hand: game.hands[faction_id], tracks: game.tracks }; if (player !== game.active) { let inactive = states[game.state].inactive || game.state; view.prompt = `Waiting for ${game.active} to ${inactive}.`; } else { view.actions = {}; states[game.state].prompt(); if (game.undo && game.undo.length > 0) view.actions.undo = 1; else view.actions.undo = 0; } return view; } // #endregion // #region setup export function setup(seed: number, _scenario: string, _options: unknown) { game = { seed: seed, state: null, active: ANARCHIST, bag_of_glory: { [ANARCHISTS_ID]: 1, [COMMUNISTS_ID]: 1, [MODERATES_ID]: 1, }, bonuses: [ON, ON], current_events: [], fronts: { 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]), ], }, hero_points: { [ANARCHISTS_ID]: 2, [COMMUNISTS_ID]: 2, [MODERATES_ID]: 0, }, initiative: MODERATES_ID, tracks: [5, 5, 6, 3, 3], log: [], undo: [], turn: 1, year: 1, }; fascist_event(); game.state = 'move'; return game; } // #endregion states.move = { inactive: 'move', prompt() { view.prompt = 'Move a piece.'; for (let p = 0; p < 32; ++p) gen_action_piece(p); }, // piece(p) { // game.selected = p // game.state = "move_to" // }, }; 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 // }, }; function pop_undo() { const save_log = game.log; const save_undo = game.undo; game = save_undo.pop()!; (save_log as string[]).length = game.log as number; game.log = save_log; game.undo = save_undo; } // #region CARDS // function draw_faction_card(faction: Player): CardId { // return draw_faction_cards(faction, 1)[0]; // } // function draw_faction_cards(faction: Player, count: number = 1): CardId[] { // const drawnCards = []; // } function draw_card(deck: CardId[]) { clear_undo(); let i = random(deck.length); let c = deck[i]; set_delete(deck, c); return c; } // #endregion // #region EVENTS 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() { 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 UTILITY function clear_undo() { if (game.undo.length > 0) game.undo = []; } function get_faction_id(player: 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 make_list(first: number, last: number) { let list = []; for (let i = first; i <= last; i++) list.push(i); return list; } function random(range: number): number { // An MLCG using integer arithmetic with doubles. // https://www.ams.org/journals/mcom/1999-68-225/S0025-5718-99-00996-5/S0025-5718-99-00996-5.pdf // m = 2**35 − 31 return (game.seed = (game.seed * 200105) % 34359738337) % range; } function set_delete(set: T[], item: T) { let a = 0; let b = set.length - 1; while (a <= b) { let m = (a + b) >> 1; let x = set[m]; if (item < x) b = m - 1; else if (item > x) a = m + 1; else { array_remove(set, m); return; } } } // #endregion // #region ARRAY function array_remove(array: T[], index: number) { let n = array.length; for (let i = index + 1; i < n; ++i) array[i - 1] = array[i]; array.length = n - 1; } // function array_insert(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(array: T[], index: number) { // let n = array.length; // for (let i = index + 2; i < n; ++i) array[i - 2] = array[i]; // array.length = n - 2; // } // function array_insert_pair( // array: (K | V)[], // index: number, // key: K, // value: V // ) { // for (let i = array.length; i > index; i -= 2) { // array[i] = array[i - 2]; // array[i + 1] = array[i - 1]; // } // array[index] = key; // array[index + 1] = value; // } // #endregion