diff options
Diffstat (limited to 'rules.ts')
-rw-r--r-- | rules.ts | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/rules.ts b/rules.ts new file mode 100644 index 0000000..a2a71ca --- /dev/null +++ b/rules.ts @@ -0,0 +1,423 @@ +'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, 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: number | string[]; + undo: Game[]; + turn: number; + year: number; + active: Player | null; + state: string | null; + bag_of_glory: Record<FactionId, number>; + bonuses: number[]; + current_events: CardId[]; + fronts: { + a: number; + m: number; + n: number; + s: number; + }; + hands: Record<FactionId, CardId[]>; + hero_points: Record<FactionId, number>; + 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; + + current_events: CardId[]; + hand: CardId[]; +} + +// 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, + hand: game.hands[faction_id], + }; + + 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<T>(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<T>(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<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; +// for (let i = index + 2; i < n; ++i) array[i - 2] = array[i]; +// array.length = n - 2; +// } + +// function array_insert_pair<K, V>( +// 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 |