summaryrefslogtreecommitdiff
path: root/rules.ts
diff options
context:
space:
mode:
Diffstat (limited to 'rules.ts')
-rw-r--r--rules.ts388
1 files changed, 265 insertions, 123 deletions
diff --git a/rules.ts b/rules.ts
index 49f4687..0348814 100644
--- a/rules.ts
+++ b/rules.ts
@@ -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;