summaryrefslogtreecommitdiff
path: root/rules.ts
diff options
context:
space:
mode:
Diffstat (limited to 'rules.ts')
-rw-r--r--rules.ts245
1 files changed, 198 insertions, 47 deletions
diff --git a/rules.ts b/rules.ts
index 3f0a2cf..6831f5e 100644
--- a/rules.ts
+++ b/rules.ts
@@ -175,12 +175,16 @@ function gen_action_standee(track_id: number) {
// }
export function action(
- state: any,
+ state: Game,
player: Player,
action: string,
arg: unknown
) {
- console.log('action', state, player, action, arg);
+ console.log('action', player, action, arg);
+ if (action !== 'undo') {
+ state.undo = push_undo();
+ }
+
game = state;
let S = states[game.state];
if (action in S) S[action](arg, player);
@@ -312,7 +316,7 @@ function get_active_node(
return a === null ? null : a.node;
}
-function get_active_node_args(): any {
+function get_active_node_args<T = any>(): T {
const node = get_active_node(game.engine);
if (node.t === leaf_node || node.t === function_node) {
return node.a;
@@ -360,19 +364,34 @@ function next() {
}
} else if (node.t === 'l') {
game.state = node.s;
- game.active = faction_player_map[node.p];
+
+ // 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];
+ 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';
+ return;
+ }
+ game.active = next_active;
}
}
-function resolve_active_node() {
+function resolve_active_node(checkpoint = false) {
const next_node = get_active_node(game.engine);
if (next_node !== null) {
next_node.r = resolved;
}
+ if (checkpoint) {
+ clear_undo();
+ }
}
-function resolve_active_and_proceed() {
- resolve_active_node();
+function resolve_active_and_proceed(checkpoint = false) {
+ resolve_active_node(checkpoint);
next();
}
@@ -388,7 +407,7 @@ function game_view(state: Game, player: Player) {
const faction_id = player_faction_map[player];
view = {
- engine: game.engine,
+ engine: game.engine, // TODO: remove
log: game.log,
prompt: null,
location: game.location,
@@ -407,6 +426,7 @@ function game_view(state: Game, player: Player) {
tableaus: game.tableaus,
tracks: game.tracks,
triggered_track_effects: game.triggered_track_effects,
+ undo: game.undo, // TODO: remove
used_medallions: game.used_medallions,
year: game.year,
};
@@ -991,6 +1011,36 @@ states.choose_medallion = {
},
};
+states.confirm_turn = {
+ inactive: 'confirm their turn',
+ prompt() {
+ view.prompt = 'Confirm your actions or undo';
+ gen_action('confirm');
+ },
+ confirm() {
+ resolve_active_and_proceed(true);
+ },
+};
+
+states.draw_card = {
+ inactive: 'draw a card',
+ prompt() {
+ const { v } = get_active_node_args();
+ view.prompt = v === 1 ? 'Draw a card' : `Draw ${v} cards`;
+ gen_action(v === 1 ? 'draw_card' : 'draw_cards');
+ },
+ draw_card() {
+ const { v } = get_active_node_args();
+ draw_hand_cards(get_active_faction(), v);
+ resolve_active_and_proceed();
+ },
+ draw_cards() {
+ const { v } = get_active_node_args();
+ draw_hand_cards(get_active_faction(), v);
+ resolve_active_and_proceed();
+ },
+};
+
states.end_of_year_discard = {
inactive: 'discard cards from hand and tableau',
prompt() {
@@ -1163,7 +1213,7 @@ states.player_turn = {
}
},
done() {
- resolve_active_and_proceed();
+ resolve_active_and_proceed(true);
},
play_for_ap() {
const faction_id = get_faction_id(game.active as Player);
@@ -1174,7 +1224,7 @@ states.player_turn = {
insert_before_active_node(
create_seq_node([
create_leaf_node('choose_area_ap', faction_id, {
- strength: (cards[card] as PlayerCard).strength,
+ strength: (cards[card] as PlayerCard).strength,
}),
create_function_node('check_activate_icon'),
])
@@ -1394,14 +1444,6 @@ states.use_strategy_medallion = {
// #endrregion
-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 unknown as number;
- game.log = save_log;
- game.undo = save_undo;
-}
// #region GAME FUNCTIONS
@@ -1932,7 +1974,14 @@ function resolve_effect(
if (effect.type === 'hero_points' && effect.target === SELF) {
state = 'gain_hero_points';
}
- return state === undefined ? null : create_leaf_node(state, faction, args);
+ if (effect.type === 'draw_card' && effect.target === SELF) {
+ state = 'draw_card';
+ }
+ if (state === undefined) {
+ console.log('----UNRESOLVED EFFECT----', effect);
+ return null;
+ }
+ return create_leaf_node(state, faction, args);
}
function win_final_bid(faction_id: FactionId) {
@@ -2067,10 +2116,6 @@ function log_h3(msg: string) {
// #region UTILITY
-function clear_undo() {
- console.log('game clear undo', game?.undo);
- if (game?.undo && game.undo.length > 0) game.undo = [];
-}
function get_active_faction(): FactionId {
return player_faction_map[game.active];
@@ -2185,28 +2230,6 @@ function make_list(first: number, last: number): number[] {
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;
- }
- }
-}
-
function list_deck(id: FactionId | 'fascist') {
const deck = [];
const card_list =
@@ -2221,7 +2244,8 @@ function list_deck(id: FactionId | 'fascist') {
id !== 'fascist' &&
(game.hands[id].includes(card) ||
game.discard[id].includes(card) ||
- game.tableaus[id].includes(card))
+ game.tableaus[id].includes(card) ||
+ game.trash[id].includes(card))
) {
return;
}
@@ -2242,7 +2266,71 @@ function draw_medallions() {
// #endregion
-// #region ARRAY
+// #region COMMON LIBRARY
+
+function clear_undo() {
+ if (game.undo) {
+ game.undo.length = 0;
+ }
+}
+
+function push_undo() {
+ if (game.undo) {
+ let copy = {} as Game;
+ for (let k in game) {
+ let v = game[k];
+ if (k === 'undo') continue;
+ else if (k === 'log') v = v.length;
+ else if (typeof v === 'object' && v !== null) v = object_copy(v);
+ copy[k] = v;
+ }
+ game.undo.push(copy);
+ }
+ return game.undo;
+}
+
+function pop_undo() {
+ if (game.undo) {
+ let save_log = game.log;
+ let save_undo = game.undo;
+ game = save_undo.pop();
+ (save_log as string[]).length = game.log as unknown as number;
+ game.log = save_log;
+ game.undo = save_undo;
+ }
+ next();
+}
+
+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;
+}
+
+// Fast deep copy for objects without cycles
+function object_copy(original) {
+ if (Array.isArray(original)) {
+ let n = original.length;
+ let copy = new Array(n);
+ for (let i = 0; i < n; ++i) {
+ let v = original[i];
+ if (typeof v === 'object' && v !== null) copy[i] = object_copy(v);
+ else copy[i] = v;
+ }
+ return copy;
+ } else {
+ let copy = {};
+ for (let i in original) {
+ let v = original[i];
+ if (typeof v === 'object' && v !== null) copy[i] = object_copy(v);
+ else copy[i] = v;
+ }
+ return copy;
+ }
+}
+
+// Array remove and insert (faster than splice)
function array_remove<T>(array: T[], index: number) {
let n = array.length;
@@ -2275,4 +2363,67 @@ function array_insert<T>(array: T[], index: number, item: T) {
// array[index + 1] = value;
// }
+// Set as plain sorted array
+
+function set_clear<T>(set: T[]) {
+ // eslint-disable-line @typescript-eslint/no-unused-vars
+ set.length = 0;
+}
+
+function set_has<T>(set: T[], item: T) {
+ let a = 0;
+ let b = set.length - 1;
+ while (a <= b) {
+ const m = (a + b) >> 1;
+ const x = set[m];
+ if (item < x) b = m - 1;
+ else if (item > x) a = m + 1;
+ else return true;
+ }
+ return false;
+}
+
+function set_add<T>(set: T[], item: T) {
+ // eslint-disable-line @typescript-eslint/no-unused-vars
+ let a = 0;
+ let b = set.length - 1;
+ while (a <= b) {
+ const m = (a + b) >> 1;
+ const x = set[m];
+ if (item < x) b = m - 1;
+ else if (item > x) a = m + 1;
+ else return set;
+ }
+ return array_insert(set, a, item);
+}
+
+// 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;
+// }
+// }
+// }
+
+function set_delete<T>(set: T[], item: T) {
+ // eslint-disable-line @typescript-eslint/no-unused-vars
+ let a = 0;
+ let b = set.length - 1;
+ while (a <= b) {
+ const m = (a + b) >> 1;
+ const x = set[m];
+ if (item < x) b = m - 1;
+ else if (item > x) a = m + 1;
+ else return array_remove(set, m);
+ }
+ return set;
+}
+
// #endregion