summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrans Bongers <fransbongers@franss-mbp.home>2025-02-16 19:51:08 +0100
committerFrans Bongers <fransbongers@franss-mbp.home>2025-02-16 19:53:04 +0100
commitdbd1660fc0f297d2b7b571af6038e53d6596161c (patch)
tree9966947e32fadfd457d6c227159b7ba3165a263b
parente798a3c84d9c50e6723ab0fd7f22a9328a81199e (diff)
downloadland-and-freedom-dbd1660fc0f297d2b7b571af6038e53d6596161c.tar.gz
initial multiactive updates
-rw-r--r--play.ts2
-rw-r--r--rules.js115
-rw-r--r--rules.ts145
-rw-r--r--types.d.ts6
4 files changed, 158 insertions, 110 deletions
diff --git a/play.ts b/play.ts
index 0e1eea6..5080b37 100644
--- a/play.ts
+++ b/play.ts
@@ -1,6 +1,6 @@
'use strict';
-import { CardId, StaticData, View } from './types';
+import { StaticData, View } from './types';
declare function action_button(action: string, text: string): void;
// declare function register_action(element: HTMLElement, type: string, s: number): void;
diff --git a/rules.js b/rules.js
index abff1d8..455475f 100644
--- a/rules.js
+++ b/rules.js
@@ -134,9 +134,7 @@ function setup_bag_of_glory() {
next();
}
function setup_choose_card() {
- const player_order = get_player_order();
- game.engine = player_order.map((faction_id) => create_leaf_node('choose_card', faction_id));
- game.engine.push(create_leaf_node('confirm_turn', player_order[2]));
+ game.engine = [create_leaf_node('choose_card', 'all')];
game.engine.push(create_function_node('setup_player_turn'));
next();
}
@@ -289,6 +287,20 @@ function insert_after_active_node(node, engine = game.engine) {
function insert_before_active_node(node, engine = game.engine) {
insert_before_or_after_active_node(node, 'before', engine);
}
+function get_next_active(p) {
+ if (Array.isArray(p)) {
+ return p.map((faction) => faction_player_map[faction]);
+ }
+ if (p === 'all') {
+ return exports.roles;
+ }
+ if (p === 'None') {
+ return 'None';
+ }
+ else {
+ return faction_player_map[p];
+ }
+}
function next(checkpoint = false) {
if (checkpoint) {
clear_undo();
@@ -306,7 +318,7 @@ function next(checkpoint = false) {
else if (node.t === 'l') {
game.state = node.s;
const current_active = game.active;
- const next_active = faction_player_map[node.p];
+ const next_active = get_next_active(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';
@@ -332,6 +344,7 @@ function game_view(state, current) {
game = state;
const faction = current === OBSERVER ? null : player_faction_map[current];
view = {
+ active: game.active,
engine: game.engine,
log: game.log,
prompt: null,
@@ -480,20 +493,24 @@ function draw_hand_cards(faction_id, count) {
if (game.medallions[faction_id].includes(data_1.INTELLIGENCE_MEDALLION_ID)) {
count++;
}
- const drawn_cards = count;
+ let drawn_cards = 0;
if (deck.length < count) {
count = count - deck.length;
+ drawn_cards += deck.length;
game.hands[faction_id] = game.hands[faction_id].concat(deck);
game.discard[faction_id] = [];
}
+ for (let i = 0; i < count; i++) {
+ const deck = list_deck(faction_id);
+ if (deck.length > 0) {
+ game.hands[faction_id].push(draw_card(deck));
+ drawn_cards++;
+ }
+ }
const log = drawn_cards === 1
? `${get_player(faction_id)} draws 1 card`
: `${get_player(faction_id)} draws ${drawn_cards} cards`;
logi(log);
- for (let i = 0; i < count; i++) {
- const deck = list_deck(faction_id);
- game.hands[faction_id].push(draw_card(deck));
- }
}
function start_year() {
game.year++;
@@ -717,10 +734,17 @@ states.add_to_front = {
gen_spend_hero_points();
const args = get_active_node_args();
const possible_fronts = get_fronts_to_add_to(args.t);
- view.prompt =
- possible_fronts.length === 1
- ? `Add strength to ${front_names[possible_fronts[0]]}`
- : 'Add strength to a Front';
+ const number_of_fronts = possible_fronts.length;
+ if (number_of_fronts === 0) {
+ view.prompt = 'No valid front to target. You must skip';
+ gen_action('skip');
+ }
+ else if (number_of_fronts === 1) {
+ view.prompt = `Add strength to ${front_names[possible_fronts[0]]}`;
+ }
+ else {
+ view.prompt = 'Add strength to a Front';
+ }
for (let f of possible_fronts) {
gen_action_front(f);
}
@@ -736,55 +760,43 @@ states.add_to_front = {
update_front(f, value, get_active_faction());
resolve_active_and_proceed();
},
+ skip() {
+ resolve_active_and_proceed();
+ },
};
states.attack_front = {
inactive: 'attack a Front',
prompt() {
gen_spend_hero_points();
const { t: target, n, src } = get_active_node_args();
- let fronts = [];
- if (target === data_1.ANY) {
- fronts = get_fronts_to_add_to(data_1.ANY, n);
- }
- else if (target === 'd' || target === 'v') {
- fronts = get_fronts_closest_to(target);
- }
- else if (game.fronts[target].status === data_1.DEFEAT) {
- fronts = get_fronts_closest_to('d');
+ const possible_fronts = get_fronts_to_add_to(target, n);
+ const number_of_fronts = possible_fronts.length;
+ if (number_of_fronts === 0) {
+ view.prompt = 'No valid front to target. You must skip';
+ gen_action('skip');
}
- else if (game.fronts[target].status === data_1.VICTORY) {
- fronts = get_fronts_to_add_to(data_1.ANY);
+ else if (number_of_fronts === 1) {
+ view.prompt = `Attack ${front_names[possible_fronts[0]]}`;
}
else {
- fronts.push(target);
+ view.prompt = 'Attack a Front';
}
- view.prompt =
- fronts.length === 1
- ? `Attack ${front_names[fronts[0]]}`
- : 'Attack a front';
- let prefix = '';
if (src) {
- prefix = `${get_source_name(src)}: `;
- }
- if (fronts.length === 1) {
- view.prompt = prefix
- ? `${prefix}attack ${front_names[fronts[0]]}`
- : `Attack ${front_names[fronts[0]]}`;
- }
- else {
- view.prompt = prefix ? `${prefix}attack a front` : `Attack a front`;
+ view.prompt = add_prompt_prefix(view.prompt, get_source_name(src));
}
- fronts.forEach((id) => gen_action('front', id));
+ possible_fronts.forEach((id) => gen_action('front', id));
},
spend_hp() {
resolve_spend_hp();
},
front(f) {
- const node = get_active_node();
- const value = node.a.v;
+ const value = get_active_node_args().v;
update_front(f, value);
resolve_active_and_proceed();
},
+ skip() {
+ resolve_active_and_proceed();
+ },
};
states.break_tie_final_bid = {
inactive: 'break tie for Final Bid',
@@ -840,7 +852,7 @@ states.change_active_player = {
},
prompt() {
view.prompt = '';
- }
+ },
};
states.choose_area_ap = {
inactive: 'choose area to use Action Points',
@@ -928,14 +940,15 @@ states.change_bonus = {
};
states.choose_card = {
inactive: 'choose a card',
- prompt() {
+ prompt(player) {
+ console.log('player', player);
gen_spend_hero_points();
const { src } = get_active_node_args();
view.prompt = 'Choose a card to play this turn';
if (src === 'momentum') {
view.prompt = 'Choose a card to play';
}
- const faction = get_active_faction();
+ const faction = player_faction_map[player];
const hand = game.hands[faction];
for (let c of hand) {
if (!game.selected_cards[faction].includes(c)) {
@@ -946,14 +959,13 @@ states.choose_card = {
spend_hp() {
resolve_spend_hp();
},
- card(c) {
- const faction = get_active_faction();
+ card(c, player) {
+ const faction = player_faction_map[player];
game.selected_cards[faction].push(c);
const { src } = get_active_node_args();
if (src === 'momentum') {
game.played_card = game.selected_cards[faction][0];
}
- resolve_active_and_proceed();
},
};
states.choose_final_bid = {
@@ -1074,12 +1086,12 @@ states.draw_card = {
draw_card() {
const { v } = get_active_node_args();
draw_hand_cards(get_active_faction(), v);
- resolve_active_and_proceed();
+ resolve_active_and_proceed(true);
},
draw_cards() {
const { v } = get_active_node_args();
draw_hand_cards(get_active_faction(), v);
- resolve_active_and_proceed();
+ resolve_active_and_proceed(true);
},
};
states.draw_glory = {
@@ -2627,6 +2639,9 @@ function get_fronts_closest_to(target) {
}
return accrued;
}, []);
+ if (values.length === 0) {
+ return [];
+ }
const targetValue = target === 'd' ? Math.min(...values) : Math.max(...values);
return Object.keys(game.fronts).filter((frontId) => game.fronts[frontId].value === targetValue);
}
diff --git a/rules.ts b/rules.ts
index db25c1b..e47abe9 100644
--- a/rules.ts
+++ b/rules.ts
@@ -224,7 +224,7 @@ const resolved = 1;
function create_leaf_node<T = any>(
state: string,
- faction: FactionId | 'None',
+ faction: FactionId | 'None' | 'all' | FactionId[],
args?: T
): LeafNode {
return {
@@ -272,13 +272,15 @@ function setup_bag_of_glory() {
}
function setup_choose_card() {
- const player_order = get_player_order();
- game.engine = player_order.map((faction_id) =>
- create_leaf_node('choose_card', faction_id)
- );
+ 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.push(create_leaf_node('confirm_turn', player_order[2]));
game.engine.push(create_function_node('setup_player_turn'));
next();
}
@@ -474,6 +476,20 @@ function insert_before_active_node(
insert_before_or_after_active_node(node, 'before', engine);
}
+function get_next_active(p: LeafNode['p']): Player | Player[] | 'None' {
+ if (Array.isArray(p)) {
+ return p.map((faction) => faction_player_map[faction]);
+ }
+ if (p === 'all') {
+ return roles;
+ }
+ if (p === 'None') {
+ return 'None';
+ } else {
+ return faction_player_map[p];
+ }
+}
+
function next(checkpoint = false) {
if (checkpoint) {
clear_undo();
@@ -492,7 +508,9 @@ function next(checkpoint = false) {
// 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];
+
+ const next_active = get_next_active(node.p);
+
if (next_active !== current_active && game.undo.length > 0) {
insert_before_active_node(
create_leaf_node('confirm_turn', get_active_faction())
@@ -532,6 +550,7 @@ function game_view(state: Game, current: Player | 'Observer') {
current === OBSERVER ? null : player_faction_map[current];
view = {
+ active: game.active,
engine: game.engine, // TODO: remove
log: game.log,
prompt: null,
@@ -694,25 +713,28 @@ function draw_hand_cards(faction_id: FactionId, count: number) {
if (game.medallions[faction_id].includes(INTELLIGENCE_MEDALLION_ID)) {
count++;
}
- const drawn_cards = count;
+ let drawn_cards = 0;
// Draw all remaining cards
if (deck.length < count) {
count = count - deck.length;
+ drawn_cards += deck.length;
game.hands[faction_id] = game.hands[faction_id].concat(deck);
game.discard[faction_id] = [];
}
+ for (let i = 0; i < count; i++) {
+ const deck = list_deck(faction_id);
+ if (deck.length > 0) {
+ game.hands[faction_id].push(draw_card(deck));
+ drawn_cards++;
+ }
+ }
const log =
drawn_cards === 1
? `${get_player(faction_id)} draws 1 card`
: `${get_player(faction_id)} draws ${drawn_cards} cards`;
logi(log);
-
- for (let i = 0; i < count; i++) {
- const deck = list_deck(faction_id);
- game.hands[faction_id].push(draw_card(deck));
- }
}
// #endregion
@@ -956,10 +978,18 @@ states.add_to_front = {
gen_spend_hero_points();
const args = get_active_node_args();
const possible_fronts = get_fronts_to_add_to(args.t);
- view.prompt =
- possible_fronts.length === 1
- ? `Add strength to ${front_names[possible_fronts[0]]}`
- : 'Add strength to a Front';
+
+ const number_of_fronts = possible_fronts.length;
+
+ if (number_of_fronts === 0) {
+ view.prompt = 'No valid front to target. You must skip';
+ gen_action('skip');
+ } else if (number_of_fronts === 1) {
+ view.prompt = `Add strength to ${front_names[possible_fronts[0]]}`;
+ } else {
+ view.prompt = 'Add strength to a Front';
+ }
+
for (let f of possible_fronts) {
gen_action_front(f);
}
@@ -975,6 +1005,9 @@ states.add_to_front = {
update_front(f, value, get_active_faction());
resolve_active_and_proceed();
},
+ skip() {
+ resolve_active_and_proceed();
+ },
};
states.attack_front = {
@@ -983,47 +1016,35 @@ states.attack_front = {
gen_spend_hero_points();
const { t: target, n, src } = get_active_node_args();
- let fronts: Array<FrontId> = [];
+ const possible_fronts = get_fronts_to_add_to(target, n);
+ const number_of_fronts = possible_fronts.length;
- if (target === ANY) {
- fronts = get_fronts_to_add_to(ANY, n);
- } else if (target === 'd' || target === 'v') {
- fronts = get_fronts_closest_to(target);
- } else if (game.fronts[target].status === DEFEAT) {
- fronts = get_fronts_closest_to('d');
- } else if (game.fronts[target].status === VICTORY) {
- fronts = get_fronts_to_add_to(ANY);
+ if (number_of_fronts === 0) {
+ view.prompt = 'No valid front to target. You must skip';
+ gen_action('skip');
+ } else if (number_of_fronts === 1) {
+ view.prompt = `Attack ${front_names[possible_fronts[0]]}`;
} else {
- fronts.push(target);
+ view.prompt = 'Attack a Front';
}
- view.prompt =
- fronts.length === 1
- ? `Attack ${front_names[fronts[0]]}`
- : 'Attack a front';
- let prefix = '';
if (src) {
- prefix = `${get_source_name(src)}: `;
- }
- if (fronts.length === 1) {
- view.prompt = prefix
- ? `${prefix}attack ${front_names[fronts[0]]}`
- : `Attack ${front_names[fronts[0]]}`;
- } else {
- view.prompt = prefix ? `${prefix}attack a front` : `Attack a front`;
+ view.prompt = add_prompt_prefix(view.prompt, get_source_name(src));
}
- fronts.forEach((id) => gen_action('front', id));
+ possible_fronts.forEach((id) => gen_action('front', id));
},
spend_hp() {
resolve_spend_hp();
},
front(f: FrontId) {
- const node = get_active_node();
- const value = node.a.v;
+ const value = get_active_node_args().v;
update_front(f, value);
resolve_active_and_proceed();
},
+ skip() {
+ resolve_active_and_proceed();
+ },
};
states.break_tie_final_bid = {
@@ -1076,7 +1097,7 @@ states.break_tie_winner = {
};
/**
- * Change does not do anything, but it will change
+ * Change does not do anything, but it will change
* active player and trigger a confirm for the previous
* state
*/
@@ -1087,8 +1108,8 @@ states.change_active_player = {
},
prompt() {
view.prompt = '';
- }
-}
+ },
+};
states.choose_area_ap = {
inactive: 'choose area to use Action Points',
@@ -1186,7 +1207,8 @@ states.change_bonus = {
states.choose_card = {
inactive: 'choose a card',
- prompt() {
+ prompt(player: Player) {
+ console.log('player', player);
gen_spend_hero_points();
const { src } = get_active_node_args<ChooseCardArgs>();
view.prompt = 'Choose a card to play this turn';
@@ -1194,7 +1216,7 @@ states.choose_card = {
view.prompt = 'Choose a card to play';
}
- const faction = get_active_faction();
+ const faction = player_faction_map[player];
const hand = game.hands[faction];
for (let c of hand) {
if (!game.selected_cards[faction].includes(c)) {
@@ -1205,14 +1227,14 @@ states.choose_card = {
spend_hp() {
resolve_spend_hp();
},
- card(c: CardId) {
- const faction = get_active_faction();
+ 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];
}
- resolve_active_and_proceed();
+ // resolve_active_and_proceed();
},
};
@@ -1362,12 +1384,12 @@ states.draw_card = {
draw_card() {
const { v } = get_active_node_args();
draw_hand_cards(get_active_faction(), v);
- resolve_active_and_proceed();
+ resolve_active_and_proceed(true);
},
draw_cards() {
const { v } = get_active_node_args();
draw_hand_cards(get_active_faction(), v);
- resolve_active_and_proceed();
+ resolve_active_and_proceed(true);
},
};
@@ -1384,7 +1406,7 @@ states.draw_glory = {
game.glory.push(faction);
- // TODO: remove if statement, just here atm to not break
+ // TODO: remove if statement, just here atm to not break
// running games
if (!game.glory_current_year) {
game.glory_current_year = game.glory_current_year = {
@@ -1397,7 +1419,11 @@ states.draw_glory = {
array_remove(game.bag_of_glory, index);
- log(`${get_player(get_active_faction())} draws <ft${faction}> from the Bag of Glory`)
+ log(
+ `${get_player(
+ get_active_faction()
+ )} draws <ft${faction}> from the Bag of Glory`
+ );
resolve_active_and_proceed(true);
},
};
@@ -3299,6 +3325,13 @@ function get_fronts_closest_to(target: 'd' | 'v'): FrontId[] {
},
[]
);
+
+ // Possible if all fronts have either been
+ // defeated or are victorious
+ if (values.length === 0) {
+ return [];
+ }
+
const targetValue =
target === 'd' ? Math.min(...values) : Math.max(...values);
return Object.keys(game.fronts).filter(
@@ -3382,7 +3415,7 @@ function add_prompt_prefix(prompt: string, prefix: string) {
}
function get_active_faction(): FactionId {
- return player_faction_map[game.active];
+ return player_faction_map[game.active as Player];
}
function get_blank_marker_id(track_id: number, space_id: number) {
diff --git a/types.d.ts b/types.d.ts
index b4ef5f0..6d0a31a 100644
--- a/types.d.ts
+++ b/types.d.ts
@@ -20,7 +20,7 @@ export interface Game {
// TODO: why is this needed?
[index: number]: any;
// RTT
- active: Player | 'None' | null;
+ active: Player | Player[] | 'None' | null;
log: string[];
seed: number;
state: string | null;
@@ -78,7 +78,7 @@ export interface Game {
export interface View {
engine: Game['engine'];
log: number | string[];
- active?: string | null;
+ active: string | string[] | null;
prompt: string | null;
state: Game['state'];
actions?: any;
@@ -131,7 +131,7 @@ export interface SeqNode {
export interface LeafNode<T = any> {
t: 'l';
s: string; // State
- p: FactionId | 'None'; // Player
+ p: FactionId | 'None' | 'all' | FactionId[]; // Player
a?: T; // args
r?: 0 | 1; // 1 if resolved
}