summaryrefslogtreecommitdiff
path: root/rules.ts
diff options
context:
space:
mode:
Diffstat (limited to 'rules.ts')
-rw-r--r--rules.ts278
1 files changed, 129 insertions, 149 deletions
diff --git a/rules.ts b/rules.ts
index e47abe9..98b10b9 100644
--- a/rules.ts
+++ b/rules.ts
@@ -2,7 +2,6 @@
import {
CardId,
- ChooseCardArgs,
Effect,
EffectSource,
EngineNode,
@@ -13,7 +12,8 @@ import {
FunctionNode,
Game,
Icon,
- LeafNode,
+ StateNode,
+ PlayCardArgs,
Player,
PlayerCard,
PlayerTurnArgs,
@@ -29,14 +29,6 @@ import data, {
ANARCHISTS_ID,
COMMUNISTS_ID,
MODERATES_ID,
- // LIBERTY,
- // COLLECTIVIZATION,
- // GOVERNMENT,
- // SOVIET_SUPPORT,
- // FOREIGN_AID,
- // MORALE_BONUS,
- // TEAMWORK_BONUS,
- // OFF,
LIBERTY,
CLOSEST_TO_DEFEAT,
CLOSEST_TO_VICTORY,
@@ -60,7 +52,6 @@ import data, {
TOWARDS_CENTER,
ARCHIVES_MEDALLION_ID,
INTELLIGENCE_MEDALLION_ID,
- // VOLUNTEERS_MEDALLION_ID,
ORGANIZATION_MEDALLION_ID,
STRATEGY_MEDALLION_ID,
PROPAGANDA_MEDALLION_ID,
@@ -75,24 +66,14 @@ import data, {
ARAGON,
FASCIST_ID,
NORTHERN,
- // StaticData,
- // PLAYER_WITH_MOST_HERO_POINTS,
} from './data';
-// interface State {
-// inactive: string;
-// prompt: () => void;
-// }
const OBSERVER = 'Observer';
const states = {} as States;
let game = {} as Game; // = null
var view = {} as View; // = null
-// export const ANARCHIST = 'Anarchist' as Player;
-// export const COMMUNIST = 'Communist' as Player;
-// export const MODERATE = 'Moderate' as Player;
-
const role_ids: FactionId[] = [ANARCHISTS_ID, COMMUNISTS_ID, MODERATES_ID];
const faction_player_map: Record<FactionId, Player> = {
@@ -185,25 +166,13 @@ function gen_spend_hero_points() {
}
}
-// 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: Game,
player: Player,
action: string,
arg: unknown
) {
- if (action !== 'undo') {
+ if (action !== 'undo' && state.state !== 'choose_card') {
state.undo = push_undo();
}
@@ -217,18 +186,18 @@ export function action(
// #region ENGINE
-const leaf_node = 'l';
+const state_node = 'l';
const seq_node = 's';
const function_node = 'f';
const resolved = 1;
-function create_leaf_node<T = any>(
+function create_state_node<T = any>(
state: string,
faction: FactionId | 'None' | 'all' | FactionId[],
args?: T
-): LeafNode {
+): StateNode {
return {
- t: leaf_node,
+ t: state_node,
s: state,
p: faction,
a: args,
@@ -256,7 +225,7 @@ function create_seq_node(children: EngineNode[]): SeqNode {
function checkpoint() {
if (game.undo.length > 0) {
insert_after_active_node(
- create_leaf_node('confirm_turn', get_active_faction())
+ create_state_node('confirm_turn', get_active_faction())
);
}
resolve_active_and_proceed();
@@ -265,22 +234,15 @@ function checkpoint() {
function setup_bag_of_glory() {
log_h1('Bag of Glory');
game.engine = [
- create_leaf_node('add_glory', game.initiative),
+ create_state_node('add_glory', game.initiative),
create_function_node('end_of_turn'),
];
next();
}
function setup_choose_card() {
- game.engine = [create_leaf_node('choose_card', 'all')];
-
- // const player_order = get_player_order();
- // game.engine = player_order.map((faction_id) =>
- // create_leaf_node('choose_card', faction_id)
- // );
- // Add extra confirm, otherwise first players card will be revealed
- // before confirm
- // game.engine.push(create_leaf_node('confirm_turn', player_order[2]));
+ game.engine = [create_state_node('choose_card', 'all')];
+
game.engine.push(create_function_node('setup_player_turn'));
next();
}
@@ -289,7 +251,7 @@ function setup_final_bid() {
log_h1('Final Bid');
const player_order = get_player_order();
game.engine = player_order.map((faction_id) =>
- create_leaf_node('choose_final_bid', faction_id)
+ create_state_node('choose_final_bid', faction_id)
);
game.engine.push(create_function_node('checkpoint'));
game.engine.push(create_function_node('resolve_final_bid'));
@@ -309,14 +271,14 @@ function setup_player_turn() {
game.engine = [
create_function_node('start_of_player_turn', { f: next_faction }),
- create_leaf_node('player_turn', next_faction),
+ create_state_node('player_turn', next_faction),
create_function_node('end_of_player_turn', { f: next_faction }),
];
next();
}
-// Check if player needs to discard cards. If so inserts leaf node
+// Check if player needs to discard cards. If so inserts state node
function check_end_of_year_discard() {
const { f: faction } = get_active_node_args();
@@ -324,7 +286,7 @@ function check_end_of_year_discard() {
game.hands[faction].length > get_hand_limit(faction) ||
game.tableaus[faction].length > game.year
) {
- insert_after_active_node(create_leaf_node('end_of_year_discard', faction));
+ insert_after_active_node(create_state_node('end_of_year_discard', faction));
}
resolve_active_and_proceed();
@@ -334,7 +296,7 @@ function end_of_player_turn() {
const { f: faction } = get_active_node_args();
if (get_next_faction_in_player_order(faction) === game.first_player) {
game.engine = [
- create_leaf_node('change_active_player', game.initiative),
+ create_state_node('change_active_player', game.initiative),
create_function_node('resolve_fascist_test'),
create_function_node('setup_bag_of_glory'),
];
@@ -391,9 +353,9 @@ const engine_functions: Record<string, Function> = {
function get_active(
engine: EngineNode[]
-): { parent: EngineNode[]; node: FunctionNode | LeafNode } | null {
+): { parent: EngineNode[]; node: FunctionNode | StateNode } | null {
for (let i of engine) {
- if ((i.t === leaf_node || i.t === function_node) && i.r !== resolved) {
+ if ((i.t === state_node || i.t === function_node) && i.r !== resolved) {
return { parent: engine, node: i };
}
if (i.t === seq_node) {
@@ -408,7 +370,7 @@ function get_active(
function get_active_node(
engine: EngineNode[] = game.engine
-): FunctionNode | LeafNode | null {
+): FunctionNode | StateNode | null {
const a = get_active(engine);
return a === null ? null : a.node;
}
@@ -416,10 +378,10 @@ function get_active_node(
function get_nodes_for_state(
state: string,
engine: EngineNode[] = game.engine
-): LeafNode[] {
+): StateNode[] {
let nodes = [];
for (let i of engine) {
- if (i.t === leaf_node && i.s === state) {
+ if (i.t === state_node && i.s === state) {
nodes.push(i);
}
if (i.t === seq_node) {
@@ -431,7 +393,7 @@ function get_nodes_for_state(
function get_active_node_args<T = any>(): T {
const node = get_active_node(game.engine);
- if (node.t === leaf_node || node.t === function_node) {
+ if (node.t === state_node || node.t === function_node) {
return node.a ?? {};
}
return null;
@@ -439,7 +401,7 @@ function get_active_node_args<T = any>(): T {
function update_active_node_args<T = any>(args: Partial<T>) {
const node = get_active_node(game.engine);
- if (node.t === leaf_node || node.t === function_node) {
+ if (node.t === state_node || node.t === function_node) {
node.a = {
...node.a,
...args,
@@ -476,7 +438,7 @@ function insert_before_active_node(
insert_before_or_after_active_node(node, 'before', engine);
}
-function get_next_active(p: LeafNode['p']): Player | Player[] | 'None' {
+function get_next_active(p: StateNode['p']): Player | Player[] | 'None' {
if (Array.isArray(p)) {
return p.map((faction) => faction_player_map[faction]);
}
@@ -513,7 +475,7 @@ function next(checkpoint = false) {
if (next_active !== current_active && game.undo.length > 0) {
insert_before_active_node(
- create_leaf_node('confirm_turn', get_active_faction())
+ create_state_node('confirm_turn', get_active_faction())
);
game.state = 'confirm_turn';
return;
@@ -587,14 +549,19 @@ function game_view(state: Game, current: Player | 'Observer') {
if (game.state === 'game_over') {
view.prompt = game.victory;
- } else if (current !== game.active) {
+ } else if (
+ current !== game.active &&
+ !game.active.includes(current as Player)
+ ) {
let inactive = states[game.state].inactive || game.state;
- view.prompt = `Waiting for ${game.active} to ${inactive}.`;
+ view.prompt = Array.isArray(game.active)
+ ? `Waiting for other players to ${inactive}.`
+ : `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;
+ states[game.state].prompt(current);
}
return view;
@@ -768,11 +735,6 @@ function start_turn() {
game.engine.push(create_function_node('setup_choose_card'));
}
next();
- // game.state = 'resolve_event';
- // game.active = faction_player_map[game.initiative];
- // game.state_data = {
- // current_effect: 0,
- // };
}
// region STATES
@@ -802,7 +764,7 @@ states.activate_icon = {
},
add_to_front() {
insert_after_active_node(
- create_leaf_node('add_to_front', get_active_faction(), {
+ create_state_node('add_to_front', get_active_faction(), {
t: ANY,
v: get_icon_count_in_tableau('add_to_front'),
})
@@ -1152,15 +1114,12 @@ states.choose_area_ap = {
resolve_active_and_proceed();
},
standee(track_id: number) {
- insert_after_active_node({
- t: leaf_node,
- p: get_active_faction(),
- s: 'move_track_up_or_down',
- a: {
+ insert_after_active_node(
+ create_state_node('move_track_up_or_down', get_active_faction(), {
track_id,
strength: get_active_node()?.a.strength,
- },
- });
+ })
+ );
resolve_active_and_proceed();
},
};
@@ -1205,18 +1164,16 @@ states.change_bonus = {
},
};
-states.choose_card = {
+// Used for effects that allow play of an extra card
+states.play_card = {
inactive: 'choose a card',
- prompt(player: Player) {
- console.log('player', player);
+ prompt() {
gen_spend_hero_points();
- const { src } = get_active_node_args<ChooseCardArgs>();
- view.prompt = 'Choose a card to play this turn';
- if (src === 'momentum') {
- view.prompt = 'Choose a card to play';
- }
- const faction = player_faction_map[player];
+ view.prompt = 'Choose a card to play';
+
+ const faction = get_active_faction();
+
const hand = game.hands[faction];
for (let c of hand) {
if (!game.selected_cards[faction].includes(c)) {
@@ -1227,14 +1184,55 @@ states.choose_card = {
spend_hp() {
resolve_spend_hp();
},
+ card(c: CardId) {
+ const faction = get_active_faction();
+ game.selected_cards[faction].push(c);
+
+ game.played_card = game.selected_cards[faction][0];
+
+ resolve_active_and_proceed();
+ },
+};
+
+// Multiactive choose card state
+states.choose_card = {
+ inactive: 'choose a card',
+ prompt(player: Player) {
+ gen_spend_hero_points();
+ view.prompt = 'Choose a card to play this turn';
+
+ const faction = player_faction_map[player];
+
+ if (game.selected_cards[faction].length === 0) {
+ view.actions.undo = 0;
+ const hand = game.hands[faction];
+ for (let c of hand) {
+ if (!game.selected_cards[faction].includes(c)) {
+ gen_action_card(c);
+ }
+ }
+ } else {
+ view.actions.undo = 1;
+ view.actions.confirm = 1;
+ view.prompt = 'Confirm your actions or undo';
+ }
+ },
+ spend_hp() {
+ resolve_spend_hp();
+ },
card(c: CardId, player: Player) {
const faction = player_faction_map[player];
game.selected_cards[faction].push(c);
- const { src } = get_active_node_args<ChooseCardArgs>();
- if (src === 'momentum') {
- game.played_card = game.selected_cards[faction][0];
+ },
+ undo(_, player: Player) {
+ const faction = player_faction_map[player];
+ game.selected_cards[faction] = [];
+ },
+ confirm(_, player: Player) {
+ set_delete(game.active as Player[], player);
+ if (game.active.length === 0) {
+ resolve_active_and_proceed();
}
- // resolve_active_and_proceed();
},
};
@@ -1286,13 +1284,12 @@ function setup_momentum() {
// right away or not. Depends on whether card for this turn has been fully resolved or not
// Get player turn node
- const node: LeafNode<PlayerTurnArgs> = get_nodes_for_state('player_turn')[0];
- console.log('node', node);
+ const node: StateNode<PlayerTurnArgs> = get_nodes_for_state('player_turn')[0];
const player_needs_to_play_card = game.selected_cards[faction].length > 0;
const { use_ap, use_morale_bonus, resolving_event } =
node.a ?? ({} as PlayerTurnArgs);
- // TO CHECK: or event needs to be fulle resolved
+
if (
player_needs_to_play_card ||
use_ap ||
@@ -1306,8 +1303,8 @@ function setup_momentum() {
};
} else {
// Player can resolve choosing a new card
- insert_before_active_node(
- create_leaf_node<ChooseCardArgs>('choose_card', faction, {
+ insert_after_active_node(
+ create_state_node<PlayCardArgs>('play_card', faction, {
src: 'momentum',
})
);
@@ -1744,7 +1741,7 @@ function resolve_spend_hp() {
// insert spend hero points node before current node
// so it will return to current node after resolving
insert_before_active_node(
- create_leaf_node('spend_hero_points', get_active_faction())
+ create_state_node('spend_hero_points', get_active_faction())
);
log('Spends Hero Points');
@@ -1758,12 +1755,6 @@ function set_player_turn_prompt({
use_momentum,
use_morale_bonus,
}: PlayerTurnArgs & { can_spend_hp: boolean; can_play_card: boolean }) {
- console.log('set_player_turn_prompt', {
- can_play_card,
- use_ap,
- use_morale_bonus,
- use_momentum,
- });
if (can_play_card && can_spend_hp) {
view.prompt = 'Play a card or spend Hero points';
} else if (can_play_card && !can_spend_hp) {
@@ -1809,7 +1800,6 @@ states.player_turn = {
game.faction_turn === faction_id && game.hero_points[faction_id] > 0;
const can_play_card = game.selected_cards[faction_id].length > 0;
- console.log('can_play_card', can_play_card);
if (use_momentum) {
gen_action('use_momentum');
if (use_ap || use_morale_bonus || can_play_card) {
@@ -1877,7 +1867,7 @@ states.player_turn = {
use_ap: false,
});
insert_before_active_node(
- create_leaf_node('choose_area_ap', faction, {
+ create_state_node('choose_area_ap', faction, {
strength,
})
);
@@ -1901,7 +1891,7 @@ states.player_turn = {
use_morale_bonus: false,
});
insert_before_active_node(
- create_leaf_node('activate_icon', get_active_faction())
+ create_state_node('activate_icon', get_active_faction())
);
next();
},
@@ -1995,7 +1985,7 @@ states.remove_attack_from_fronts = {
resolve_active_and_proceed();
} else if (card_id === 39 || card_id === 16) {
insert_after_active_node(
- create_leaf_node('attack_front', get_active_faction(), {
+ create_state_node('attack_front', get_active_faction(), {
t: ANY,
v: card_id === 39 ? -2 : -1 * removed_value,
n: card_id === 16 ? id : undefined,
@@ -2009,7 +1999,7 @@ states.remove_attack_from_fronts = {
const values: number[] = Object.values(f ?? {});
if (card_id === 39 && values.length > 0) {
insert_after_active_node(
- create_leaf_node('attack_front', get_active_faction(), {
+ create_state_node('attack_front', get_active_faction(), {
t: ANY,
v: -2,
})
@@ -2127,11 +2117,11 @@ states.spend_hero_points = {
pay_hero_points(faction, 1);
insert_after_active_node(
create_seq_node([
- create_leaf_node('add_to_front', faction, {
+ create_state_node('add_to_front', faction, {
t: ANY,
v: 1,
}),
- create_leaf_node('spend_hero_points', faction),
+ create_state_node('spend_hero_points', faction),
])
);
resolve_active_and_proceed();
@@ -2168,8 +2158,8 @@ states.spend_hero_points = {
}
insert_after_active_node(
create_seq_node([
- create_leaf_node('remove_blank_marker', faction),
- create_leaf_node('spend_hero_points', faction),
+ create_state_node('remove_blank_marker', faction),
+ create_state_node('spend_hero_points', faction),
])
);
resolve_active_and_proceed();
@@ -2188,11 +2178,11 @@ states.spend_hero_points = {
pay_hero_points(faction, amount);
insert_after_active_node(
create_seq_node([
- create_leaf_node('move_track_up_or_down', faction, {
+ create_state_node('move_track_up_or_down', faction, {
track_id,
strength: 1,
}),
- create_leaf_node('spend_hero_points', faction),
+ create_state_node('spend_hero_points', faction),
])
);
resolve_active_and_proceed();
@@ -2603,7 +2593,7 @@ function determine_winner() {
win_game(faction_player_map[winners[0]], highest_glory);
} else {
insert_after_active_node(
- create_leaf_node('break_tie_winner', game.initiative, {
+ create_state_node('break_tie_winner', game.initiative, {
winners,
glory: highest_glory,
})
@@ -2655,7 +2645,7 @@ function end_of_year() {
const engine = [];
for (let i = 0; i < glory_to_draw[game.year]; ++i) {
- engine.push(create_leaf_node('draw_glory', player_order[i % 3]));
+ engine.push(create_state_node('draw_glory', player_order[i % 3]));
}
engine.push(create_function_node('end_of_year_cleanup'));
@@ -2692,7 +2682,7 @@ function end_of_year_cleanup() {
function end_resolving_event_effects() {
// Get player turn node
- const node: LeafNode<PlayerTurnArgs> = get_nodes_for_state('player_turn')[0];
+ const node: StateNode<PlayerTurnArgs> = get_nodes_for_state('player_turn')[0];
// Update args
node.a = {
@@ -2750,9 +2740,7 @@ function gain_hero_points(
}
function game_over(result: Player | 'None', victory: string) {
- insert_after_active_node(create_leaf_node('game_over', 'None'));
- // game.state = 'game_over';
- // game.active = 'None';
+ insert_after_active_node(create_state_node('game_over', 'None'));
game.result = result;
game.victory = victory;
game.undo = [];
@@ -2862,7 +2850,7 @@ function resolve_final_bid() {
win_final_bid(winners[0]);
} else {
insert_after_active_node(
- create_leaf_node('break_tie_final_bid', game.initiative, { winners })
+ create_state_node('break_tie_final_bid', game.initiative, { winners })
);
}
@@ -2991,7 +2979,7 @@ function insert_use_organization_medallion_node(
const faction = get_active_faction();
insert_after_active_node(
- create_leaf_node('use_organization_medallion', faction, {
+ create_state_node('use_organization_medallion', faction, {
t: track_id,
v: value,
})
@@ -3051,7 +3039,7 @@ function update_front(
can_use_medallion(STRATEGY_MEDALLION_ID)
) {
insert_after_active_node(
- create_leaf_node('use_strategy_medallion', get_active_faction(), {
+ create_state_node('use_strategy_medallion', get_active_faction(), {
f: front_id,
})
);
@@ -3136,7 +3124,7 @@ function resolve_effect(effect: Effect, source?: EffectSource): EngineNode {
return create_function_node(effect.target as string);
}
if (effect.type === 'state') {
- return create_leaf_node(effect.target as string, faction, {
+ return create_state_node(effect.target as string, faction, {
v: effect.value,
src: source,
});
@@ -3144,7 +3132,7 @@ function resolve_effect(effect: Effect, source?: EffectSource): EngineNode {
// Default cases where effect type is mapped to a state
let state = effect_type_state_map[effect.type];
if (state !== undefined) {
- return create_leaf_node(state, faction, args);
+ return create_state_node(state, faction, args);
}
// Specific mapping based on target
@@ -3154,7 +3142,7 @@ function resolve_effect(effect: Effect, source?: EffectSource): EngineNode {
effect.type === 'hero_points' &&
effect.target === PLAYER_WITH_MOST_HERO_POINTS,
resolve: () => {
- return create_leaf_node(
+ return create_state_node(
'select_player_with_most_hero_points',
faction,
args
@@ -3166,7 +3154,7 @@ function resolve_effect(effect: Effect, source?: EffectSource): EngineNode {
resolve: () => {
return create_seq_node(
get_player_order().map((faction) =>
- create_leaf_node('hero_points', faction, args)
+ create_state_node('hero_points', faction, args)
)
);
},
@@ -3174,7 +3162,7 @@ function resolve_effect(effect: Effect, source?: EffectSource): EngineNode {
{
condition: effect.type === 'hero_points' && effect.target === SELF,
resolve: () => {
- return create_leaf_node('hero_points', faction, args);
+ return create_state_node('hero_points', faction, args);
},
},
{
@@ -3182,7 +3170,7 @@ function resolve_effect(effect: Effect, source?: EffectSource): EngineNode {
effect.type === 'hero_points' &&
role_ids.includes(effect.target as FactionId),
resolve: () => {
- return create_leaf_node(
+ return create_state_node(
'hero_points',
effect.target as FactionId,
args
@@ -3193,20 +3181,20 @@ function resolve_effect(effect: Effect, source?: EffectSource): EngineNode {
condition:
effect.type === 'hero_points' && effect.target === INITIATIVE_PLAYER,
resolve: () => {
- return create_leaf_node('hero_points', game.initiative, args);
+ return create_state_node('hero_points', game.initiative, args);
},
},
{
condition: effect.type === 'draw_card' && effect.target === SELF,
resolve: () => {
- return create_leaf_node('draw_card', faction, args);
+ return create_state_node('draw_card', faction, args);
},
},
{
condition:
effect.type === 'draw_card' && effect.target === INITIATIVE_PLAYER,
resolve: () => {
- return create_leaf_node('draw_card', game.initiative, args);
+ return create_state_node('draw_card', game.initiative, args);
},
},
{
@@ -3214,7 +3202,7 @@ function resolve_effect(effect: Effect, source?: EffectSource): EngineNode {
effect.type === 'draw_card' &&
role_ids.includes(effect.target as FactionId),
resolve: () => {
- return create_leaf_node('draw_card', effect.target as FactionId, args);
+ return create_state_node('draw_card', effect.target as FactionId, args);
},
},
{
@@ -3222,7 +3210,7 @@ function resolve_effect(effect: Effect, source?: EffectSource): EngineNode {
resolve: () => {
return create_seq_node(
get_player_order(get_active_faction()).map((faction) =>
- create_leaf_node('draw_card', faction, args)
+ create_state_node('draw_card', faction, args)
)
);
},
@@ -3230,19 +3218,19 @@ function resolve_effect(effect: Effect, source?: EffectSource): EngineNode {
{
condition: effect.type === 'draw_card' && effect.target === OTHER_PLAYERS,
resolve: () => {
- const leaf_nodes = get_player_order(get_active_faction()).map(
- (faction) => create_leaf_node('draw_card', faction, args)
+ const state_nodes = get_player_order(get_active_faction()).map(
+ (faction) => create_state_node('draw_card', faction, args)
);
- array_remove(leaf_nodes, 0); // Remove current player
- return create_seq_node(leaf_nodes);
+ array_remove(state_nodes, 0); // Remove current player
+ return create_seq_node(state_nodes);
},
},
{
condition: effect.type === 'play_card',
resolve: () => {
return create_seq_node([
- create_leaf_node('choose_card', faction, { src: source }),
- create_leaf_node('player_turn', faction, { src: source }),
+ create_state_node('play_card', faction, { src: source }),
+ create_state_node('player_turn', faction, { src: source }),
]);
},
},
@@ -3271,14 +3259,6 @@ function win_game(player: Player, glory: number) {
// #endregion
// #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[]): CardId {
clear_undo();