summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--rules.ts319
1 files changed, 171 insertions, 148 deletions
diff --git a/rules.ts b/rules.ts
index f9552c7..b2854f8 100644
--- a/rules.ts
+++ b/rules.ts
@@ -5,13 +5,17 @@
// Check all push/clear_undo
// TODO: check flank attack
-// TODO: clean up use of who/what/which/where -- explicit selected_lord, selected_vassal, etc?
+// TODO: check all who = NOBODY etc resets
+
// TODO: remove unneccessary game.who = NOBODY etc
+
// TODO: call end_influence_check earlier when possible?
// TODO: use game.command instead of game.who for levy -- can we avoid saving who and count in push_state?
+// TODO: can we simplify push_state to not push count and who?
// TODO: verify all push_state uses
+
/*
REDO
parley - needs push_state? probably.
@@ -58,10 +62,12 @@ type Season = number & { TAG: "Season" }
type Way = "path" | "road" | "highway"
+type MyMap<K,V> = (K|V)[]
+
interface Game {
seed: number,
scenario: string,
- hidden: boolean,
+ hidden: 0 | 1,
log: string[],
undo: any[],
@@ -105,51 +111,58 @@ interface Game {
},
flags: {
- first_action: number,
- first_march_highway: number,
- free_levy: number,
- free_parley_henry: number,
- free_parley_gloucester: number,
- burgundians: number,
- bloody: number,
- london_for_york: number,
- surprise_landing: number,
- sail_to_port: number,
- march_to_port: number,
- parliament_votes: number,
- succession: number,
- jack_cade: number,
- commons_militia: number,
- swap_battle_attacker: number,
- supply_depot: number,
- loyalty_and_trust: number,
- warden_of_the_marches: number,
- naval_blockade: number,
+ bloody: 0 | 1,
+ burgundians: 0 | 1,
+ commons_militia: 0 | 1 | 2,
+ first_action: 0 | 1,
+ first_march_highway: 0 | 1,
+ free_levy: 0 | 1,
+ free_parley_gloucester: 0 | 1 | 2 | 3,
+ free_parley_henry: 0 | 1 | 2,
+ jack_cade: 0 | 1 | 2,
+ london_for_york: 0 | 1,
+ loyalty_and_trust: 0 | 1,
+ march_to_port: 0 | 1,
+ naval_blockade: -1 | 0 | 1,
+ parliament_votes: 0 | 1,
+ sail_to_port: 0 | 1,
+ succession: 0 | 1,
+ supply_depot: 0 | 1,
+ surprise_landing: 0 | 1,
+ swap_battle_attacker: 0 | 1,
+ warden_of_the_marches: 0 | 1,
},
command: Lord,
actions: number,
group: Lord[],
intercept_group: Lord[],
-
- arts_of_war: Card[],
who: Lord,
other: Lord,
where: Locale,
vassal: Vassal,
-
what: string,
count: number,
- event_data: any,
-
- check: any,
- supply: any,
- march: { from: Locale, to: Locale },
- parley: any,
-
- battle: Battle,
- spoils: number[],
+ /* temporary properties for various phases and actions */
+ arts_of_war?: Card[],
+ march?: { from: Locale, to: Locale },
+ battle?: Battle,
+ supply?: MyMap<Locale,number>,
+ parley?: MyMap<Locale,number>,
+ spoils?: number[],
+
+ check?: { cost: number, modifier: number, source: string }[],
+
+ /* temporary properties for various events */
+ event_scots?: Lord[],
+ event_propaganda?: Locale[],
+ event_aragne?: Vassal[],
+ event_she_wolf?: Vassal[],
+ event_earl_rivers?: MyMap<Lord,number>,
+ event_the_kings_name?: any,
+
+ /* game over properties */
victory?: string,
result?: string,
}
@@ -270,15 +283,39 @@ interface State {
soldiers_of_fortune?(): void,
}
-interface StateList {
- [index: string]: State
+interface View {
+ prompt: string,
+ actions: any,
+ log: string[],
+
+ end: number,
+ turn: number,
+ victory_check: number,
+ influence: number,
+
+ events: Card[],
+ pieces: any,
+ reveal: number,
+
+ held_y: number,
+ held_l: number,
+
+ command: Lord,
+ hand: Card[],
+ plan: Lord[],
+
+ battle?: Battle,
+ arts_of_war?: Card[],
+ group?: Lord[],
+ what?: Card,
+ who?: Lord,
}
// === CONSTANTS ===
var game: Game
-var view: any
-var states: StateList = {}
+var view: View
+var states: { [index: string]: State } = {}
const data = require("./data.js")
@@ -1830,7 +1867,7 @@ function init_influence_check(lord: Lord) {
}
function end_influence_check() {
- game.check = 0
+ delete game.check
}
function count_influence_score() {
@@ -2004,7 +2041,7 @@ states.levy_arts_of_war_first = {
}
function end_levy_arts_of_war_first() {
- game.arts_of_war = null
+ delete game.arts_of_war
set_active_enemy()
if (game.active === P2)
goto_levy_arts_of_war_first()
@@ -2074,7 +2111,7 @@ states.levy_arts_of_war = {
}
function end_levy_arts_of_war() {
- game.arts_of_war = null
+ delete game.arts_of_war
set_active_enemy()
if (game.active === P2)
goto_levy_arts_of_war()
@@ -3594,6 +3631,7 @@ function search_supply_by_way(result, start: Locale, carts: number, ships: numbe
if (result)
return result
+
return false
}
@@ -3662,10 +3700,10 @@ function init_supply() {
game.supply = search_supply([])
}
-function can_action_supply() {
+function can_action_supply(): boolean {
if (game.actions < 1)
return false
- return !!search_supply(false)
+ return search_supply(false)
}
function goto_supply() {
@@ -3734,7 +3772,7 @@ states.supply_source = {
view.prompt += ` ${ships} Ship.`
for (let i = 0; i < game.supply.length; i += 2)
- gen_action_locale(game.supply[i])
+ gen_action_locale(game.supply[i] as Locale)
},
locale(loc) {
push_undo()
@@ -3790,7 +3828,7 @@ function use_port_supply(source: Locale, amount: number) {
function end_supply() {
spend_action(1)
resume_command()
- game.supply = 0
+ delete game.supply
game.where = NOWHERE
}
@@ -4094,13 +4132,13 @@ function search_tax(result, start: Locale, lord: Lord) {
return false
}
-function can_action_tax() {
+function can_action_tax(): boolean {
if (game.actions < 1)
return false
let here = get_lord_locale(game.command)
if (can_tax_at(here, game.command))
return true
- return !!search_tax(false, here, game.command)
+ return search_tax(false, here, game.command)
}
function goto_tax() {
@@ -4297,7 +4335,7 @@ function list_parley_command() {
return result
}
-function can_action_parley_levy() {
+function can_action_parley_levy(): boolean {
if (game.count <= 0
&& (game.who !== LORD_HENRY_VI || game.flags.free_parley_henry === 0)
&& ((game.who !== LORD_GLOUCESTER_1 && game.who !== LORD_GLOUCESTER_2) || game.flags.free_parley_gloucester === 0)
@@ -4306,12 +4344,11 @@ function can_action_parley_levy() {
let here = get_lord_locale(game.who)
if (can_parley_at(here))
return true
- return !!search_parley(false, here, game.who)
+ return search_parley(false, here, game.who)
}
function list_parley_levy() {
- let here = get_lord_locale(game.who)
- return search_parley([], here, game.who)
+ return search_parley([], get_lord_locale(game.who), game.who)
}
function goto_parley() {
@@ -4327,7 +4364,7 @@ function goto_parley() {
// Campaign phase, and current location is no cost (except some events), and always successful.
if (game.parley.length === 2 && get_lord_locale(game.command) === game.parley[0]) {
log(`Parley at ${data.locales[get_lord_locale(game.command)]}`)
- shift_favour_toward(game.parley[0])
+ shift_favour_toward(game.parley[0] as Locale)
if (is_lancaster_lord(game.command) && is_event_in_play(EVENT_YORK_AN_HONEST_TALE_SPEEDS_BEST)) {
reduce_lancaster_influence(1)
}
@@ -4337,7 +4374,7 @@ function goto_parley() {
}
if (game.parley.length === 2) {
- game.where = game.parley[0]
+ game.where = game.parley[0] as Locale
add_influence_check_distance(game.parley[1])
} else {
game.where = NOWHERE
@@ -4348,7 +4385,7 @@ function end_parley(success: boolean) {
pop_state()
game.flags.naval_blockade = 0
game.where = NOWHERE
- game.parley = null
+ delete game.parley
if (game.flags.free_parley_henry > 0 && game.who === LORD_HENRY_VI) {
--game.flags.free_parley_henry
++game.count
@@ -4383,7 +4420,7 @@ states.parley = {
view.prompt = "Parley: Choose a Locale to Parley."
if (game.where === NOWHERE) {
for (let i = 0; i < game.parley.length; i += 2)
- gen_action_locale(game.parley[i])
+ gen_action_locale(game.parley[i] as Locale)
} else {
view.prompt = "Parley: "
prompt_influence_check()
@@ -4477,13 +4514,15 @@ function can_march_to(to: Locale) {
function prompt_march() {
let from = get_lord_locale(game.command)
+ // TODO: this is a bit complicated -- can we simplify?
+
if (is_first_action()) {
for (let to of data.locales[from].paths) {
if (can_march_to(to))
gen_action_locale(to)
}
}
- if (game.actions > 0 || game.flags.surprise_landing === 2) {
+ if (game.actions > 0 || game.flags.surprise_landing === 1) {
for (let to of data.locales[from].roads) {
if (can_march_to(to))
gen_action_locale(to)
@@ -4493,7 +4532,7 @@ function prompt_march() {
if (can_march_to(to))
gen_action_locale(to)
}
- } else if ((game.actions === 0 && is_first_march_highway()) || game.flags.surprise_landing === 2) {
+ } else if (game.actions === 0 && is_first_march_highway()) {
for (let to of data.locales[from].highways) {
if (can_march_to(to))
gen_action_locale(to)
@@ -4568,7 +4607,7 @@ function march_with_group_2() {
switch (type) {
case "highway":
- if (is_first_march_highway() || game.flags.surprise_landing === 2) {
+ if (is_first_march_highway() || game.flags.surprise_landing === 1) {
spend_march_action(0)
} else {
spend_march_action(1)
@@ -4577,7 +4616,7 @@ function march_with_group_2() {
break
case "road":
- if ((alone && is_first_march_highway()) || game.flags.surprise_landing === 2) {
+ if ((alone && is_first_march_highway()) || game.flags.surprise_landing === 1) {
spend_march_action(0)
} else {
spend_march_action(1)
@@ -4606,7 +4645,7 @@ function end_march() {
// Disbanded in battle!
if (!is_lord_on_map(game.command)) {
game.where = NOWHERE
- game.march = null
+ delete game.march
spend_all_actions()
resume_command()
return
@@ -4619,7 +4658,7 @@ function end_march() {
game.flags.march_to_port = 0
game.flags.sail_to_port = 0
- game.march = null
+ delete game.march
resume_command()
}
@@ -5038,7 +5077,7 @@ function get_spoils(type: Asset) {
}
function add_spoils(type: Asset, n) {
- if (!game.spoils)
+ if (game.spoils === undefined)
game.spoils = [ 0, 0, 0, 0, 0, 0, 0 ]
game.spoils[type] += n
}
@@ -7032,8 +7071,7 @@ function goto_battle_spoils() {
function end_battle_spoils() {
game.who = NOBODY
- game.spoils = null
-
+ delete game.spoils
goto_battle_losses_victor()
}
@@ -7368,7 +7406,7 @@ function goto_battle_aftermath() {
// Recovery
spend_all_actions()
game.where = NOWHERE
- game.battle = null
+ delete game.battle
game.flags.bloody = 0
end_march()
}
@@ -8209,24 +8247,25 @@ exports.setup = function (seed, scenario, options) {
game = {
seed,
scenario,
- hidden: !!options.hidden,
+ hidden: options.hidden ? 1 : 0,
log: [],
undo: [],
active: null,
- rebel: null,
state: "setup_lords",
stack: [],
+
victory_check: 0,
+ turn: 0,
influence: 0,
+ rebel: null,
hand_y: [],
hand_l: [],
plan_y: [],
plan_l: [],
- turn: 0,
events: [], // this levy/this campaign cards
pieces: {
@@ -8250,26 +8289,26 @@ exports.setup = function (seed, scenario, options) {
},
flags: {
+ bloody: 0,
+ burgundians: 0,
+ commons_militia: 0,
first_action: 0,
first_march_highway: 0,
free_levy: 0,
- free_parley_henry:0,
- free_parley_gloucester:0,
- burgundians:0,
- bloody:0,
- london_for_york:0,
- surprise_landing:0,
- sail_to_port: 0,
+ free_parley_gloucester: 0,
+ free_parley_henry: 0,
+ jack_cade: 0,
+ london_for_york: 0,
+ loyalty_and_trust: 0,
march_to_port: 0,
- parliament_votes:0,
- succession:0,
- jack_cade:0,
- commons_militia:0,
- swap_battle_attacker:0,
- supply_depot:0,
- loyalty_and_trust:0,
- warden_of_the_marches:0,
- naval_blockade:0
+ naval_blockade: 0,
+ parliament_votes: 0,
+ sail_to_port: 0,
+ succession: 0,
+ supply_depot: 0,
+ surprise_landing: 0,
+ swap_battle_attacker: 0,
+ warden_of_the_marches: 0,
},
command: NOBODY,
@@ -8282,15 +8321,6 @@ exports.setup = function (seed, scenario, options) {
vassal: NOVASSAL,
what: null,
count: 0,
- event_data: 0,
- check: null,
- arts_of_war: null,
-
- supply: 0,
- march: null,
- battle: null,
- spoils: null,
- parley: 0,
}
log_h1(scenario)
@@ -10011,12 +10041,12 @@ function end_immediate_event() {
function goto_lancaster_event_scots() {
game.state = "scots"
- game.event_data = []
game.who = NOBODY
+ game.event_scots = []
}
function end_lancaster_event_scots() {
- game.event_data = 0
+ delete game.event_scots
game.who = NOBODY
end_immediate_event()
}
@@ -10026,13 +10056,13 @@ states.scots = {
prompt() {
view.prompt = "Scots: You may add 1 Men-at-Arms and 1 Militia to each Lord."
for (let lord of all_lancaster_lords) {
- if (is_lord_on_map(lord) && map_get(game.event_data, lord, 0) < 3) {
+ if (is_lord_on_map(lord) && map_get(game.event_scots, lord, 0) < 3) {
gen_action_lord(lord)
}
}
if (game.who !== NOBODY) {
- let troops = map_get(game.event_data, game.who, 0)
+ let troops = map_get(game.event_scots, game.who, 0)
if ((troops & 1) === 0)
view.actions.add_militia = 1
if ((troops & 2) === 0)
@@ -10045,15 +10075,15 @@ states.scots = {
},
add_militia() {
add_lord_forces(game.who, MILITIA, 1)
- let troops = map_get(game.event_data, game.who, 0)
- map_set(game.event_data, game.who, troops + 1)
+ let troops = map_get(game.event_scots, game.who, 0)
+ map_set(game.event_scots, game.who, troops + 1)
if (troops !== 0)
game.who = NOBODY
},
add_men_at_arms() {
add_lord_forces(game.who, MEN_AT_ARMS, 1)
- let troops = map_get(game.event_data, game.who, 0)
- map_set(game.event_data, game.who, troops + 2)
+ let troops = map_get(game.event_scots, game.who, 0)
+ map_set(game.event_scots, game.who, troops + 2)
if (troops !== 0)
game.who = NOBODY
},
@@ -10163,15 +10193,15 @@ states.french_troops = {
// === EVENT: WARWICKS PROPAGANDA ===
function add_propaganda_target(loc: Locale) {
- set_add(game.event_data, loc)
+ set_add(game.event_propaganda, loc)
}
function remove_propaganda_target(loc: Locale) {
- set_delete(game.event_data, loc)
+ set_delete(game.event_propaganda, loc)
}
function is_propaganda_target(loc: Locale) {
- return set_has(game.event_data, loc)
+ return set_has(game.event_propaganda, loc)
}
function goto_warwicks_propaganda() {
@@ -10184,7 +10214,7 @@ function goto_warwicks_propaganda() {
if (can_play) {
game.state = "warwicks_propaganda"
- game.event_data = []
+ game.event_propaganda = []
game.where = NOWHERE
game.count = 0
} else {
@@ -10262,7 +10292,7 @@ states.warwicks_propaganda_yorkist_choice = {
}
function end_warwicks_propaganda() {
- game.event_data = 0
+ delete game.event_propaganda
game.where = NOWHERE
game.count = 0
set_active_enemy()
@@ -10419,7 +10449,7 @@ function goto_lancaster_event_luniverselle_aragne() {
}
if (can_play) {
game.state = "aragne_1"
- game.event_data = []
+ game.event_aragne = []
} else {
logi("No Effect")
end_immediate_event()
@@ -10430,9 +10460,9 @@ states.aragne_1 = {
inactive: "L'universelle Aragne",
prompt() {
view.prompt = "Select up to 2 Vassals"
- if (game.event_data.length < 2) {
+ if (game.event_aragne.length < 2) {
for (let v of all_vassals) {
- if (!set_has(game.event_data, v) && is_vassal_mustered_with_york_lord(v)) {
+ if (!set_has(game.event_aragne, v) && is_vassal_mustered_with_york_lord(v)) {
gen_action_vassal(v)
}
}
@@ -10441,7 +10471,7 @@ states.aragne_1 = {
},
vassal(v) {
push_undo()
- set_add(game.event_data, v)
+ set_add(game.event_aragne, v)
logi(`Vassal ${data.vassals[v].name} selected`)
},
done() {
@@ -10460,7 +10490,7 @@ states.aragne_2 = {
prompt() {
view.prompt = `For Each vassal, influence check : failure disbands it`
let done = true
- for (let v of game.event_data) {
+ for (let v of game.event_aragne) {
gen_action_vassal(v)
done = false
}
@@ -10501,15 +10531,14 @@ states.aragne_3 = {
end_influence_check()
- set_delete(game.event_data, game.vassal)
+ set_delete(game.event_aragne, game.vassal)
game.vassal = NOVASSAL
game.state = "aragne_2"
},
}
function end_universelle_aragne() {
- game.who = NOBODY
- game.event_data = 0
+ delete game.event_aragne
end_immediate_event()
}
@@ -10793,7 +10822,7 @@ function goto_york_event_shewolf_of_france() {
}
if (can_play) {
game.state = "she_wolf"
- game.event_data = []
+ game.event_she_wolf = []
game.who = NOBODY
} else {
logi(`No Effect`)
@@ -10807,7 +10836,7 @@ states.she_wolf = {
view.prompt = "Shift each Yorkist Vassal's Calendar marker 1 box right."
let done = true
for (let v of all_vassals) {
- if (!set_has(game.event_data, v) && is_vassal_mustered_with_friendly_lord(v)) {
+ if (!set_has(game.event_she_wolf, v) && is_vassal_mustered_with_friendly_lord(v)) {
gen_action_vassal(v)
done = false
}
@@ -10819,11 +10848,11 @@ states.she_wolf = {
push_undo()
if (current_turn() < 16)
set_vassal_lord_and_service(v, get_vassal_lord(v), get_vassal_service(v) + 1)
- set_add(game.event_data, v)
+ set_add(game.event_she_wolf, v)
logi(`Vassal ${data.vassals[v].name} shifted one calendar box`)
},
done() {
- game.event_data = 0
+ delete game.event_she_wolf
end_immediate_event()
},
}
@@ -10926,12 +10955,12 @@ function goto_york_event_yorkist_north() {
function goto_york_event_earl_rivers() {
game.state = "earl_rivers"
- game.event_data = []
+ game.event_earl_rivers = []
game.who = NOBODY
}
function end_york_event_earl_rivers() {
- game.event_data = 0
+ delete game.event_earl_rivers
game.who = NOBODY
end_immediate_event()
}
@@ -10942,18 +10971,18 @@ states.earl_rivers = {
view.prompt = "Earl Rivers: Add up to 2 Militia to each lord"
view.actions.done = 1
for (let lord of all_york_lords) {
- if (is_lord_on_map(lord) && map_get(game.event_data, lord, 0) < 3) {
+ if (is_lord_on_map(lord) && map_get(game.event_earl_rivers, lord, 0) < 3) {
gen_action_lord(lord)
}
}
if (game.who !== NOBODY) {
- let troops = map_get(game.event_data, game.who, 0)
+ let troops = map_get(game.event_earl_rivers, game.who, 0)
if ((troops & 1) === 0)
view.actions.add_militia = 1
}
if (game.who !== NOBODY) {
- let troops = map_get(game.event_data, game.who, 0)
+ let troops = map_get(game.event_earl_rivers, game.who, 0)
if ((troops & 1) === 0)
view.actions.add_militia2 = 1
}
@@ -10964,16 +10993,16 @@ states.earl_rivers = {
add_militia() {
push_undo()
add_lord_forces(game.who, MILITIA, 1)
- let troops = map_get(game.event_data, game.who, 0)
- map_set(game.event_data, game.who, troops + 1)
+ let troops = map_get(game.event_earl_rivers, game.who, 0)
+ map_set(game.event_earl_rivers, game.who, troops + 1)
if (troops > 1)
game.who = NOBODY
},
add_militia2() {
push_undo()
add_lord_forces(game.who, MILITIA, 2)
- let troops = map_get(game.event_data, game.who, 0)
- map_set(game.event_data, game.who, troops + 1)
+ let troops = map_get(game.event_earl_rivers, game.who, 0)
+ map_set(game.event_earl_rivers, game.who, troops + 1)
if (troops > 1)
game.who = NOBODY
},
@@ -11434,7 +11463,7 @@ states.rebel_supply_depot = {
function end_rebel_supply_depot() {
pop_state()
- game.spoils = null
+ delete game.spoils
}
// === HELD EVENT: SURPRISE LANDING ===
@@ -11450,7 +11479,7 @@ function can_play_surprise_landing() {
function goto_play_surprise_landing() {
game.state = "surprise_landing"
- game.flags.surprise_landing = 2
+ game.flags.surprise_landing = 1
game.who = NOBODY
}
@@ -11792,32 +11821,21 @@ function pop_state() {
}
function save_state_for_the_kings_name() {
- let copy = {}
- for (let k in game) {
- let v = game[k]
- if (k === "undo")
- v = undefined
- else if (k === "log")
- v = undefined
- else if (typeof v === "object" && v !== null)
- v = object_copy(v)
- copy[k] = v
- }
- game.event_data = copy
+ game.event_the_kings_name = game.undo[game.undo.length - 1]
}
function restore_state_for_the_kings_name() {
- let save_log = game.log
- let save_influence = game.influence
- game = game.event_data
+ let current_log = game.log
+ let current_influence = game.influence
+ game = game.event_the_kings_name
game.undo = []
- game.log = save_log
- game.influence = save_influence
- game.event_data = 0
+ game.log = current_log
+ game.influence = current_influence
+ delete game.event_the_kings_name
}
function delete_state_for_the_kings_name() {
- game.event_data = 0
+ delete game.event_the_kings_name
}
function clear_undo() {
@@ -12135,6 +12153,11 @@ function map_delete(map, item) {
}
}
+function map_for_each<K,V>(map: MyMap<K,V>, f: (K,V)=>void) {
+ for (let i = 0; i < map.length; i += 2)
+ f(map[i] as K, map[i+1] as V)
+}
+
// === FUZZING ASSERTS ===
const mutually_exclusive_lords = [