diff options
Diffstat (limited to 'rules.js')
-rw-r--r-- | rules.js | 448 |
1 files changed, 253 insertions, 195 deletions
@@ -13,24 +13,75 @@ exports.scenarios = [ exports.roles = [ "York", "Lancaster" ] -const { CARDS, BLOCKS, AREAS, BORDERS } = require('./data') +const { CARDS, BLOCKS, AREAS, BORDERS, block_index, area_index } = require('./data') -delete AREAS.LPool -delete AREAS.YPool -delete AREAS.LMinor -delete AREAS.YMinor +const first_area = 6 // first real area (skip pools and seas) +const last_area = AREAS.length - 4 // skip layout areas at end +const block_count = BLOCKS.length + +// roles const LANCASTER = "Lancaster" const YORK = "York" -const REBEL = "Rebel" const ENEMY = { Lancaster: "York", York: "Lancaster" } const OBSERVER = "Observer" const BOTH = "Both" -const POOL = "Pool" -const MINOR = "Minor" -const NOBODY = -1 +// areas const NOWHERE = 0 +const POOL = 1 +const MINOR = 2 + +const IRISH_SEA = area_index["Irish Sea"] +const NORTH_SEA = area_index["North Sea"] +const ENGLISH_CHANNEL = area_index["English Channel"] + +const IRELAND = area_index["Ireland"] +const SCOTLAND = area_index["Scotland"] +const FRANCE = area_index["France"] +const CALAIS = area_index["Calais"] + +const CAERNARVON = area_index["Caernarvon"] +const PEMBROKE = area_index["Pembroke"] +const POWYS = area_index["Powys"] +const GLAMORGAN = area_index["Glamorgan"] + +const CORNWALL = area_index["Cornwall"] +const EAST_YORKS = area_index["East Yorks"] +const HEREFORD = area_index["Hereford"] +const ISLE_OF_MAN = area_index["Isle of Man"] +const MIDDLESEX = area_index["Middlesex"] +const NORTH_YORKS = area_index["North Yorks"] +const RUTLAND = area_index["Rutland"] +const SOUTH_YORKS = area_index["South Yorks"] + +// blocks +const NOBODY = -1 +const B_YORK = block_index["York"] +const B_MARCH = block_index["March"] +const B_RUTLAND = block_index["Rutland"] +const B_CLARENCE_Y = block_index["Clarence/Y"] +const B_GLOUCESTER = block_index["Gloucester"] +const B_EXETER_Y = block_index["Exeter/Y"] +const B_WARWICK_Y = block_index["Warwick/Y"] +const B_KENT_Y = block_index["Kent/Y"] +const B_SALISBURY_Y = block_index["Salisbury/Y"] +const B_IRISH_MERCENARY = block_index["Irish Mercenary"] +const B_BURGUNDIAN_MERCENARY = block_index["Burgundian Mercenary"] +const B_CALAIS_MERCENARY = block_index["Calais Mercenary"] +const B_HENRY_VI = block_index["Henry VI"] +const B_PRINCE_EDWARD = block_index["Prince Edward"] +const B_EXETER_L = block_index["Exeter/L"] +const B_SOMERSET = block_index["Somerset"] +const B_RICHMOND = block_index["Richmond"] +const B_WARWICK_L = block_index["Warwick/L"] +const B_KENT_L = block_index["Kent/L"] +const B_SALISBURY_L = block_index["Salisbury/L"] +const B_CLARENCE_L = block_index["Clarence/L"] +const B_SCOTS_MERCENARY = block_index["Scots Mercenary"] +const B_WELSH_MERCENARY = block_index["Welsh Mercenary"] +const B_FRENCH_MERCENARY = block_index["French Mercenary"] +const B_REBEL = block_index["Rebel"] // serif cirled numbers const DIE_HIT = [ 0, '\u2776', '\u2777', '\u2778', '\u2779', '\u277A', '\u277B' ] @@ -39,10 +90,6 @@ const DIE_MISS = [ 0, '\u2460', '\u2461', '\u2462', '\u2463', '\u2464', '\u2465' const ATTACK_MARK = "*" const RESERVE_MARK = "" -// List for fast iteration -const BLOCKLIST = Object.keys(BLOCKS) -const AREALIST = Object.keys(AREAS) - let states = {} let game = null @@ -75,14 +122,14 @@ function log(s) { } function log_move_start(from) { - game.turn_buf = [ from ] + game.turn_buf = [ "#" + from ] } function log_move_continue(to, mark) { if (mark) - game.turn_buf.push(to + mark) + game.turn_buf.push("#" + to + mark) else - game.turn_buf.push(to) + game.turn_buf.push("#" + to) } function log_move_end() { @@ -198,9 +245,7 @@ function is_royal_heir(who) { } function is_dead(who) { - if (who in BLOCKS) - return game.location[who] === NOWHERE - return game.location[who+"/L"] === NOWHERE && game.location[who+"/Y"] === NOWHERE + return game.location[who] === NOWHERE } function is_shield_area_for(where, who, combat) { @@ -208,22 +253,22 @@ function is_shield_area_for(where, who, combat) { let needle = BLOCKS[who].shield // Nevilles going to exile in Calais - if (where === "Calais") { - if (who === "Warwick/L" || who === "Kent/L" || who === "Salisbury/L") + if (where === CALAIS) { + if (who === B_WARWICK_L || who === B_KENT_L || who === B_SALISBURY_L) return false - if (count_blocks_exclude_mercenaries("Calais") < 4) { - if (who === "Kent/Y") - return is_area_friendly_to("East Yorks", LANCASTER) - if (who === "Salisbury/Y") - return is_area_friendly_to("North Yorks", LANCASTER) + if (count_blocks_exclude_mercenaries(CALAIS) < 4) { + if (who === B_KENT_Y) + return is_area_friendly_to(EAST_YORKS, LANCASTER) + if (who === B_SALISBURY_Y) + return is_area_friendly_to(NORTH_YORKS, LANCASTER) } } // Exeter and Clarence as enemy nobles - if (who === "Exeter/Y") - return where === "Cornwall" - if (who === "Clarence/L") - return (where === "South Yorks" || where === "Rutland" || where === "Hereford") + if (who === B_EXETER_Y) + return where === CORNWALL + if (who === B_CLARENCE_L) + return (where === SOUTH_YORKS || where === RUTLAND || where === HEREFORD) // Everyone can always use their own shield if (haystack && haystack.includes(needle)) @@ -231,11 +276,11 @@ function is_shield_area_for(where, who, combat) { // Nevilles can use each other's shields if their owner is dead if (is_neville(who)) { - if (is_dead("Warwick") && haystack.includes("Warwick")) + if (is_dead(B_WARWICK_L) && is_dead(B_WARWICK_Y) && haystack.includes("Warwick")) return true - if (is_dead("Kent") && haystack.includes("Kent")) + if (is_dead(B_KENT_L) && is_dead(B_KENT_Y) && haystack.includes("Kent")) return true - if (is_dead("Salisbury") && haystack.includes("Salisbury")) + if (is_dead(B_SALISBURY_L) && is_dead(B_SALISBURY_Y) && haystack.includes("Salisbury")) return true } @@ -250,11 +295,11 @@ function is_shield_area_for(where, who, combat) { let available = false if (haystack.includes("Lancaster")) available = true - if (is_dead("Exeter") && haystack.includes("Exeter")) + if (is_dead(B_EXETER_L) && is_dead(B_EXETER_Y) && haystack.includes("Exeter")) available = true - if (is_dead("Somerset") && haystack.includes("Somerset")) + if (is_dead(B_SOMERSET) && haystack.includes("Somerset")) available = true - if (is_dead("Richmond") && haystack.includes("Richmond")) + if (is_dead(B_RICHMOND) && haystack.includes("Richmond")) available = true if (available) return !combat || find_senior_heir_in_area(LANCASTER, where) === who @@ -295,21 +340,21 @@ function is_home_for(where, who) { } function is_available_home_for(where, who) { - if (who === "Clarence/L") + if (who === B_CLARENCE_L) return is_home_for(where, who) && is_vacant_area(where) return is_home_for(where, who) && is_friendly_or_vacant_area(where) } function count_available_homes(who) { let count = 0 - for (let where of AREALIST) + for (let where = first_area; where <= last_area; ++where) if (is_available_home_for(where, who)) ++count return count } function available_home(who) { - for (let where of AREALIST) + for (let where = first_area; where <= last_area; ++where) if (is_available_home_for(where, who)) return where } @@ -324,7 +369,7 @@ function go_home_if_possible(who) { let home = available_home(who) if (game.location[who] !== home) { game.location[who] = home - game.turn_log.push([block_name(who), game.location[who]]) // TODO: "Home"? + game.turn_log.push([block_name(who), "#" + game.location[who]]) // TODO: "Home"? } } else { return true @@ -336,8 +381,7 @@ function go_home_if_possible(who) { function is_on_map_not_in_exile_or_man(who) { let where = game.location[who] return where !== NOWHERE && where !== MINOR && where !== POOL && - where !== "Isle of Man" && - !is_exile_area(where) + where !== ISLE_OF_MAN && !is_exile_area(where) } function is_land_area(where) { @@ -353,12 +397,12 @@ function is_area_friendly_to(where, owner) { } function is_london_friendly_to(owner) { - return is_area_friendly_to("Middlesex", owner) + return is_area_friendly_to(MIDDLESEX, owner) } function count_lancaster_nobles_and_heirs() { let count = 0 - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (block_owner(b) === LANCASTER && (block_type(b) === 'nobles' || block_type(b) === 'church' || block_type(b) === 'heir')) if (is_on_map_not_in_exile_or_man(b)) @@ -370,7 +414,7 @@ function count_lancaster_nobles_and_heirs() { function count_york_nobles_and_heirs() { let count = 0 - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (block_owner(b) === YORK && (block_type(b) === 'nobles' || block_type(b) === 'church' || block_type(b) === 'heir')) if (is_on_map_not_in_exile_or_man(b)) @@ -393,7 +437,7 @@ function block_home(who) { } function block_owner(who) { - if (who === REBEL) { + if (who === B_REBEL) { if (game.pretender !== NOBODY) return block_owner(game.pretender) else if (game.king !== NOBODY) @@ -407,11 +451,11 @@ function block_owner(who) { function block_initiative(who) { if (block_type(who) === 'bombard') return game.battle_round <= 1 ? 'A' : 'D' - return BLOCKS[who].combat[0] + return BLOCKS[who].initiative } function block_printed_fire_power(who) { - return BLOCKS[who].combat[1] | 0 + return BLOCKS[who].fire_power } function block_fire_power(who, where) { @@ -427,7 +471,7 @@ function block_fire_power(who, where) { ++combat if (is_levy(who) && block_home(who) === has_city(where)) ++combat - if (who === "Welsh Mercenary" && is_wales(where)) + if (who === B_WELSH_MERCENARY && is_wales(where)) ++combat } return combat @@ -471,7 +515,7 @@ function block_loyalty(source, target) { if (target_name === "Northumberland" || target_name === "Westmoreland") return 0 } - return BLOCKS[target].loyalty | 0 + return BLOCKS[target].loyalty } function can_defect(source, target) { @@ -483,11 +527,11 @@ function can_defect(source, target) { function can_attempt_treason_event() { if (game.treason === game.attacker[game.where]) { - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (is_defender(b) && can_defect(NOBODY, b)) return true } else { - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (is_attacker(b) && can_defect(NOBODY, b)) return true } @@ -497,14 +541,14 @@ function can_attempt_treason_event() { function treachery_tag(who) { if (who === game.king) return 'King' if (who === game.pretender) return 'Pretender' - if (who === "Warwick/L" || who === "Warwick/Y") return 'Warwick' + if (who === B_WARWICK_L || who === B_WARWICK_Y) return 'Warwick' return game.active } function can_attempt_treachery(who) { let once = treachery_tag(who) if (set_has(game.battle_list, who) && !set_has(game.treachery, once)) { - for (let b of BLOCKLIST) { + for (let b = 0; b < block_count; ++b) { if (game.active === game.attacker[game.where]) { if (is_defender(b) && can_defect(who, b)) return true @@ -538,7 +582,7 @@ function is_block_alive(b) { } function border_id(a, b) { - return (a < b) ? a + "/" + b : b + "/" + a + return (a < b) ? a * 100 + b : b * 100 + a } function border_was_last_used_by_enemy(from, to) { @@ -564,7 +608,7 @@ function reset_border_limits() { function count_friendly(where) { let p = game.active let count = 0 - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (game.location[b] === where && block_owner(b) === p && !set_has(game.dead, b)) ++count return count @@ -573,7 +617,7 @@ function count_friendly(where) { function count_enemy(where) { let p = ENEMY[game.active] let count = 0 - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (game.location[b] === where && block_owner(b) === p && !set_has(game.dead, b)) ++count return count @@ -582,7 +626,7 @@ function count_enemy(where) { function count_enemy_excluding_reserves(where) { let p = ENEMY[game.active] let count = 0 - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (game.location[b] === where && block_owner(b) === p) if (!set_has(game.reserves, b)) ++count @@ -612,19 +656,19 @@ function is_major_port(where) { } function is_sea_area(where) { - return where === 'Irish Sea' || where === 'North Sea' || where === 'English Channel' + return where === IRISH_SEA || where === NORTH_SEA || where === ENGLISH_CHANNEL } function is_wales(where) { - return where === "Caernarvon" || where === "Pembroke" || where === "Powys" || where === "Glamorgan" + return where === CAERNARVON || where === PEMBROKE || where === POWYS || where === GLAMORGAN } function is_lancaster_exile_area(where) { - return where === "France" || where === "Scotland" + return where === FRANCE || where === SCOTLAND } function is_york_exile_area(where) { - return where === "Calais" || where === "Ireland" + return where === CALAIS || where === IRELAND } function is_exile_area(where) { @@ -644,7 +688,7 @@ function is_pretender_exile_area(where) { } function can_recruit_to(who, to) { - if (who === "Welsh Mercenary") + if (who === B_WELSH_MERCENARY) return is_wales(to) && is_friendly_or_vacant_area(to) switch (block_type(who)) { case 'heir': @@ -678,14 +722,14 @@ function can_recruit(who) { if (game.active === game.piracy) return false if (can_activate(who) && game.location[who] === POOL) - for (let to of AREALIST) + for (let to = first_area; to <= last_area; ++to) if (can_recruit_to(who, to)) return true return false } function have_contested_areas() { - for (let where of AREALIST) + for (let where = first_area; where <= last_area; ++where) if (is_area_on_map(where) && is_contested_area(where)) return true return false @@ -697,7 +741,7 @@ function count_pinning(where) { function count_pinned(where) { let count = 0 - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (game.location[b] === where && block_owner(b) === game.active) if (!set_has(game.reserves, b)) ++count @@ -717,7 +761,7 @@ function can_block_sea_move_to(who, from, to) { return false if (game.active === game.force_march) return false - if (who === REBEL || who === "Scots Mercenary" || who === "Welsh Mercenary") + if (who === B_REBEL || who === B_SCOTS_MERCENARY || who === B_WELSH_MERCENARY) return false if (block_type(who) === 'bombard' || block_type(who) === 'levies') return false @@ -863,7 +907,7 @@ function can_block_muster_via(who, from, next, muster) { if (next === muster) return true if (border_type(from, next) !== 'minor') { - if (AREAS[next].exits.includes(muster)) + if (set_has(AREAS[next].exits, muster)) if (can_block_land_move_to(who, next, muster)) return true } @@ -886,7 +930,7 @@ function can_block_muster(who, muster) { } function can_muster_to(muster) { - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (can_block_muster(b, muster)) return true return false @@ -924,12 +968,12 @@ function disband(who) { function check_instant_victory() { // Check Clarence/Y and Exeter/L specifically (they're not heirs if converted) - if (is_dead("York") && is_dead("March") && is_dead("Rutland") && is_dead("Clarence/Y") && is_dead("Gloucester")) { + if (is_dead(B_YORK) && is_dead(B_MARCH) && is_dead(B_RUTLAND) && is_dead(B_CLARENCE_Y) && is_dead(B_GLOUCESTER)) { log("All York heirs were eliminated!") game.victory = "Lancaster won by eliminating all enemy heirs!" game.result = LANCASTER } - if (is_dead("Henry VI") && is_dead("Prince Edward") && is_dead("Exeter/L") && is_dead("Somerset") && is_dead("Richmond")) { + if (is_dead(B_HENRY_VI) && is_dead(B_PRINCE_EDWARD) && is_dead(B_EXETER_L) && is_dead(B_SOMERSET) && is_dead(B_RICHMOND)) { log("All Lancaster heirs were eliminated!") game.victory = "York won by eliminating all enemy heirs!" game.result = YORK @@ -939,12 +983,12 @@ function check_instant_victory() { function eliminate_block(who) { log(block_name(who) + " was eliminated.") game.flash += " " + block_name(who) + " was eliminated." - if (who === "Exeter/Y") { + if (who === B_EXETER_Y) { game.location[who] = NOWHERE ++game.killed_heirs[LANCASTER] return check_instant_victory() } - if (who === "Clarence/L") { + if (who === B_CLARENCE_L) { game.location[who] = NOWHERE ++game.killed_heirs[YORK] return check_instant_victory() @@ -963,12 +1007,12 @@ function eliminate_block(who) { } if (is_mercenary(who)) { switch (who) { - case "Welsh Mercenary": game.location[who] = POOL; break - case "Irish Mercenary": game.location[who] = "Ireland"; break - case "Burgundian Mercenary": game.location[who] = "Calais"; break - case "Calais Mercenary": game.location[who] = "Calais"; break - case "Scots Mercenary": game.location[who] = "Scotland"; break - case "French Mercenary": game.location[who] = "France"; break + case B_WELSH_MERCENARY: game.location[who] = POOL; break + case B_IRISH_MERCENARY: game.location[who] = IRELAND; break + case B_BURGUNDIAN_MERCENARY: game.location[who] = CALAIS; break + case B_CALAIS_MERCENARY: game.location[who] = CALAIS; break + case B_SCOTS_MERCENARY: game.location[who] = SCOTLAND; break + case B_FRENCH_MERCENARY: game.location[who] = FRANCE; break } game.steps[who] = block_max_steps(who) set_add(game.dead, who) @@ -989,7 +1033,7 @@ function reduce_block(who) { function count_attackers() { let count = 0 - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (is_attacker(b)) ++count return count @@ -997,7 +1041,7 @@ function count_attackers() { function count_defenders() { let count = 0 - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (is_defender(b)) ++count return count @@ -1005,7 +1049,7 @@ function count_defenders() { function count_blocks_exclude_mercenaries(where) { let count = 0 - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (game.location[b] === where && !is_mercenary(b) && !(game.reduced && set_has(game.reduced, b))) ++count return count @@ -1013,32 +1057,32 @@ function count_blocks_exclude_mercenaries(where) { function count_blocks(where) { let count = 0 - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (game.location[b] === where && !(game.reduced && set_has(game.reduced, b))) ++count return count } function add_blocks_exclude_mercenaries(list, where) { - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (game.location[b] === where && !is_mercenary(b) && !(game.reduced && set_has(game.reduced, b))) set_add(list, b) } function add_blocks(list, where) { - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (game.location[b] === where && !(game.reduced && set_has(game.reduced, b))) set_add(list, b) } function check_supply_penalty() { game.supply = [] - for (let where of AREALIST) { + for (let where = first_area; where <= last_area; ++where) { if (is_friendly_area(where)) { - if (where === "Calais" || where === "France") { + if (where === CALAIS || where === FRANCE) { if (count_blocks_exclude_mercenaries(where) > 4) add_blocks_exclude_mercenaries(game.supply, where) - } else if (where === "Ireland" || where === "Scotland") { + } else if (where === IRELAND || where === SCOTLAND) { if (count_blocks_exclude_mercenaries(where) > 2) add_blocks_exclude_mercenaries(game.supply, where) } else if (has_city(where)) { @@ -1055,12 +1099,12 @@ function check_supply_penalty() { function check_exile_limits() { game.exiles = [] - for (let where of AREALIST) { + for (let where = first_area; where <= last_area; ++where) { if (is_friendly_area(where)) { - if (where === "Calais" || where === "France") { + if (where === CALAIS || where === FRANCE) { if (count_blocks_exclude_mercenaries(where) > 4) add_blocks_exclude_mercenaries(game.exiles, where) - } else if (where === "Ireland" || where === "Scotland") { + } else if (where === IRELAND || where === SCOTLAND) { if (count_blocks_exclude_mercenaries(where) > 2) add_blocks_exclude_mercenaries(game.exiles, where) } @@ -1075,20 +1119,20 @@ function check_exile_limits() { // SETUP function find_block(owner, name) { - if (name in BLOCKS) - return name + if (name in block_index) + return block_index[name] name = name + "/" + owner[0] - if (name in BLOCKS) - return name + if (name in block_index) + return block_index[name] throw new Error("Block not found: " + name) } function deploy(who, where) { if (where === "Enemy") return - if (!(where in AREAS)) + if (!(where in area_index)) throw new Error("Area not found: " + where) - game.location[who] = where + game.location[who] = area_index[where] game.steps[who] = BLOCKS[who].steps } @@ -1101,7 +1145,7 @@ function deploy_york(name, where) { } function reset_blocks() { - for (let b of BLOCKLIST) { + for (let b = 0; b < block_count; ++b) { game.location[b] = NOWHERE game.steps[b] = block_max_steps(b) } @@ -1112,8 +1156,8 @@ function setup_game() { game.campaign = 1 game.end_campaign = 3 - game.pretender = "York" - game.king = "Henry VI" + game.pretender = B_YORK + game.king = B_HENRY_VI deploy_lancaster("Henry VI", "Middlesex") deploy_lancaster("Somerset", "Dorset") @@ -1186,8 +1230,8 @@ function setup_kingmaker() { game.campaign = 2 game.end_campaign = 2 - game.pretender = "Henry VI" - game.king = "March" + game.pretender = B_HENRY_VI + game.king = B_MARCH deploy_york("March", "Middlesex") deploy_york("Gloucester", "South Yorks") @@ -1242,7 +1286,7 @@ function setup_kingmaker() { deploy_lancaster("Canterbury (church)", "Enemy") // Prisoner! - set_add(game.dead, "Henry VI") + set_add(game.dead, B_HENRY_VI) } function setup_richard_iii() { @@ -1250,8 +1294,8 @@ function setup_richard_iii() { game.campaign = 3 game.end_campaign = 3 - game.pretender = "Richmond" - game.king = "Gloucester" + game.pretender = B_RICHMOND + game.king = B_GLOUCESTER deploy_york("Gloucester", "Middlesex") deploy_york("Norfolk", "East Anglia") @@ -1298,11 +1342,11 @@ function setup_richard_iii() { // Kingmaker scenario special rule function free_henry_vi() { - if (set_has(game.dead, "Henry VI")) { - if ((game.active === LANCASTER && is_friendly_area("Middlesex")) || - (game.active === YORK && is_enemy_area("Middlesex"))) { + if (set_has(game.dead, B_HENRY_VI)) { + if ((game.active === LANCASTER && is_friendly_area(MIDDLESEX)) || + (game.active === YORK && is_enemy_area(MIDDLESEX))) { log("Henry VI rescued!") - set_delete(game.dead, "Henry VI") + set_delete(game.dead, B_HENRY_VI) } } } @@ -1312,6 +1356,7 @@ function free_henry_vi() { function start_campaign() { logbr() log(".h1 Campaign " + game.campaign) + logbr() // TODO: Use board game mulligan rules instead of automatically redealing? do { @@ -1328,6 +1373,7 @@ function start_game_turn() { logbr() log(".h1 Turn " + game.turn + " of campaign " + game.campaign + ".") + logbr() // Reset movement and attack tracking state reset_border_limits() @@ -1451,6 +1497,7 @@ function reveal_cards() { function start_player_turn() { logbr() log(".h2 " + game.active) + reset_border_limits() let lc = CARDS[game.l_card] let yc = CARDS[game.y_card] @@ -1517,7 +1564,7 @@ states.plague_event = { return view.prompt = "Plague: Waiting for " + game.active + " to choose a city." view.prompt = "Plague: Choose an enemy city area." gen_action(view, 'pass') - for (let where of AREALIST) + for (let where = first_area; where <= last_area; ++where) if (is_enemy_area(where) && has_city(where)) gen_action(view, 'area', where) }, @@ -1525,7 +1572,7 @@ states.plague_event = { log("Plague ravaged " + has_city(where) + "!") game.where = where game.plague = [] - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (game.location[b] === where) set_add(game.plague, b) game.active = ENEMY[game.active] @@ -1550,6 +1597,7 @@ states.apply_plague = { if (game.plague.length === 0) { delete game.plague game.active = ENEMY[game.active] + game.where = NOWHERE end_player_turn() } }, @@ -1568,7 +1616,7 @@ states.muster_event = { view.prompt = "Muster: Choose one friendly or vacant muster area." gen_action_undo(view) gen_action(view, 'end_action_phase') - for (let where of AREALIST) { + for (let where = first_area; where <= last_area; ++where) { if (is_friendly_or_vacant_area(where)) if (can_muster_to(where)) gen_action(view, 'area', where) @@ -1594,7 +1642,7 @@ states.muster_who = { view.prompt = "Muster: Move blocks to the designated muster area." gen_action_undo(view) gen_action(view, 'end_action_phase') - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (can_block_muster(b, game.where)) gen_action(view, 'block', b) }, @@ -1728,7 +1776,7 @@ states.action_phase = { gen_action_undo(view) gen_action(view, 'end_action_phase') - for (let b of BLOCKLIST) { + for (let b = 0; b < block_count; ++b) { let from = game.location[b] if (can_recruit(b)) { if (game.moves > 0) @@ -1768,11 +1816,13 @@ states.action_phase = { if (game.moves > 0 && game.turn_log.length === 0 && game.recruit_log.length === 0) logp("did nothing.") - if (game.turn_log.length > 0) + if (game.turn_log.length > 0) { print_turn_log(game.active + " moved:") + } game.turn_log = game.recruit_log - if (game.turn_log.length > 0) + if (game.turn_log.length > 0) { print_turn_log(game.active + " recruited:") + } game.turn_log = null game.recruit_log = null @@ -1790,12 +1840,12 @@ states.recruit_where = { view.prompt = "Recruit " + block_name(game.who) + " where?" gen_action_undo(view) gen_action(view, 'block', game.who) - for (let to of AREALIST) + for (let to = first_area; to <= last_area; ++to) if (can_recruit_to(game.who, to)) gen_action(view, 'area', to) }, area: function (to) { - game.recruit_log.push([to]) + game.recruit_log.push(["#" + to]) --game.moves game.location[game.who] = to set_add(game.moved, game.who) @@ -1929,7 +1979,7 @@ function end_move() { if (game.distance > 0) { log_move_end() if (!set_has(game.activated, game.origin)) { - logp("activated " + game.origin + ".") + logp("activated #" + game.origin + ".") set_add(game.activated, game.origin) game.moves -- } @@ -1963,7 +2013,7 @@ states.battle_phase = { if (is_inactive_player(current)) return view.prompt = "Waiting for " + game.active + " to choose a battle." view.prompt = "Choose the next battle to fight!" - for (let where of AREALIST) + for (let where = first_area; where <= last_area; ++where) if (is_area_on_map(where) && is_contested_area(where)) gen_action(view, 'area', where) }, @@ -2000,7 +2050,7 @@ function resume_battle() { function end_battle() { if (game.turn_log && game.turn_log.length > 0) - print_turn_log("Retreated from " + game.where + ":") + print_turn_log("Retreated from #" + game.where + ":") free_henry_vi() game.flash = "" game.battle_round = 0 @@ -2018,7 +2068,7 @@ states.treason_event = { return view.prompt = "Treason: Waiting for " + game.active + " to choose a target." view.prompt = "Treason: Choose a target or pass." gen_action(view, 'pass') - for (let b of BLOCKLIST) { + for (let b = 0; b < block_count; ++b) { if (game.active === game.attacker[game.where]) { if (is_defender(b) && can_defect(NOBODY, b)) { gen_action(view, 'battle_treachery', b) @@ -2051,7 +2101,7 @@ states.treason_event = { } function bring_on_reserves(owner, moved) { - for (let b of BLOCKLIST) { + for (let b = 0; b < block_count; ++b) { if (block_owner(b) === owner && game.location[b] === game.where) { set_delete(game.reserves, b) if (moved) @@ -2065,10 +2115,10 @@ function bring_on_reserves(owner, moved) { function start_battle_round() { if (++game.battle_round <= 4) { if (game.turn_log && game.turn_log.length > 0) - print_turn_log("Retreated from " + game.where + ":") + print_turn_log("Retreated from #" + game.where + ":") game.turn_log = [] - log("~ Battle Round " + game.battle_round + " ~") + log(".h4 Battle Round " + game.battle_round) reset_border_limits() set_clear(game.moved) @@ -2107,7 +2157,7 @@ function pump_battle_round() { function filter_battle_blocks(ci, is_candidate) { let output = null - for (let b of BLOCKLIST) { + for (let b = 0; b < block_count; ++b) { if (is_candidate(b) && !set_has(game.moved, b) && !set_has(game.dead, b)) { if (block_initiative(b) === ci) { if (!output) @@ -2286,7 +2336,7 @@ function can_block_fire(who) { function find_minor_heir(owner) { let candidate = NOBODY - for (let b of BLOCKLIST) { + for (let b = 0; b < block_count; ++b) { if (block_owner(b) === owner && block_type(b) === 'heir' && game.location[b] === MINOR) if (candidate === NOBODY || BLOCKS[b].heir < BLOCKS[candidate].heir) candidate = b @@ -2296,7 +2346,7 @@ function find_minor_heir(owner) { function find_senior_heir(owner) { let candidate = NOBODY - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (block_owner(b) === owner && block_type(b) === 'heir' && !is_dead(b)) if (candidate === NOBODY || BLOCKS[b].heir < BLOCKS[candidate].heir) candidate = b @@ -2305,7 +2355,7 @@ function find_senior_heir(owner) { function find_next_king(owner) { let candidate = NOBODY - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (block_owner(b) === owner && block_type(b) === 'heir' && game.location[b] !== NOWHERE) if (candidate === NOBODY || BLOCKS[b].heir < BLOCKS[candidate].heir) candidate = b @@ -2314,7 +2364,7 @@ function find_next_king(owner) { function find_senior_heir_in_area(owner, where) { let candidate = NOBODY - for (let b of BLOCKLIST) { + for (let b = 0; b < block_count; ++b) { if (block_owner(b) === owner && block_type(b) === 'heir' && game.location[b] === where) { if (is_battle_reserve(b)) continue @@ -2376,10 +2426,10 @@ states.battle_round = { gen_action(view, 'battle_treachery', game.king) if (can_attempt_treachery(game.pretender)) gen_action(view, 'battle_treachery', game.pretender) - if (can_attempt_treachery("Warwick/L")) - gen_action(view, 'battle_treachery', "Warwick/L") - if (can_attempt_treachery("Warwick/Y")) - gen_action(view, 'battle_treachery', "Warwick/Y") + if (can_attempt_treachery(B_WARWICK_L)) + gen_action(view, 'battle_treachery', B_WARWICK_L) + if (can_attempt_treachery(B_WARWICK_Y)) + gen_action(view, 'battle_treachery', B_WARWICK_Y) }, battle_fire: function (who) { fire_with_block(who) @@ -2417,7 +2467,7 @@ states.battle_charge = { return view.prompt = "Heir Charge: Waiting for " + game.active + " to choose a target." view.prompt = "Heir Charge: Choose a target." gen_action(view, 'undo') - for (let b of BLOCKLIST) { + for (let b = 0; b < block_count; ++b) { if (game.active === game.attacker[game.where]) { if (is_defender(b)) { gen_action(view, 'battle_charge', b) @@ -2449,7 +2499,7 @@ states.battle_treachery = { return view.prompt = "Treachery: Waiting for " + game.active + " to choose a target." view.prompt = "Treachery: Choose a target." gen_action(view, 'undo') - for (let b of BLOCKLIST) { + for (let b = 0; b < block_count; ++b) { if (game.active === game.attacker[game.where]) { if (is_defender(b) && can_defect(game.who, b)) { gen_action(view, 'battle_treachery', b) @@ -2511,11 +2561,11 @@ function apply_hit(who) { function list_victims(p) { let is_candidate = (p === game.attacker[game.where]) ? is_attacker : is_defender let max = 0 - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (is_candidate(b) && game.steps[b] > max) max = game.steps[b] let list = [] - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (is_candidate(b) && game.steps[b] === max) set_add(list, b) return list @@ -2565,7 +2615,7 @@ states.retreat_in_battle = { } else { game.flash = block_name(game.who) + " retreated." log_battle(game.flash) - game.turn_log.push([game.active, to]) + game.turn_log.push([game.active, "#" + to]) use_border(game.where, to) game.location[game.who] = to resume_battle() @@ -2598,7 +2648,7 @@ states.sea_retreat_to = { }, area: function (to) { let sea = game.location[game.who] - game.turn_log.push([game.active, sea, to]) + game.turn_log.push([game.active, "#" + sea, "#" + to]) game.flash = block_name(game.who) + " retreated." log_battle(game.flash) game.location[game.who] = to @@ -2619,7 +2669,7 @@ function goto_regroup() { game.active = game.attacker[game.where] if (is_enemy_area(game.where)) game.active = ENEMY[game.active] - log(game.active + " won the battle in " + game.where + "!") + log(game.active + " won the battle in #" + game.where + "!") game.state = 'regroup' game.turn_log = [] clear_undo() @@ -2632,7 +2682,7 @@ states.regroup = { view.prompt = "Regroup: Choose an army to move." gen_action_undo(view) gen_action(view, 'end_regroup') - for (let b of BLOCKLIST) { + for (let b = 0; b < block_count; ++b) { if (game.location[b] === game.where) { if (game.active === game.piracy) { if (set_has(game.is_pirate, b)) @@ -2687,7 +2737,7 @@ states.regroup_to = { game.location[game.who] = to game.state = 'sea_regroup_to' } else { - game.turn_log.push([game.where, to]) + game.turn_log.push(["#"+game.where, "#"+to]) move_block(game.who, game.where, to) game.who = NOBODY game.state = 'regroup' @@ -2736,10 +2786,10 @@ function goto_supply_phase() { } function goto_execute_clarence() { - if (is_block_alive("Clarence/L")) { + if (is_block_alive(B_CLARENCE_L)) { game.active = LANCASTER game.state = 'execute_clarence' - game.who = "Clarence/L" + game.who = B_CLARENCE_L } else { goto_execute_exeter() } @@ -2755,7 +2805,7 @@ states.execute_clarence = { }, execute_clarence: function () { logp("executed Clarence.") - eliminate_block("Clarence/L") + eliminate_block(B_CLARENCE_L) game.who = NOBODY if (game.result) return goto_game_over() @@ -2768,10 +2818,10 @@ states.execute_clarence = { } function goto_execute_exeter() { - if (is_block_alive("Exeter/Y")) { + if (is_block_alive(B_EXETER_Y)) { game.active = YORK game.state = 'execute_exeter' - game.who = "Exeter/Y" + game.who = B_EXETER_Y } else { goto_enter_pretender_heir() } @@ -2787,7 +2837,7 @@ states.execute_exeter = { }, execute_exeter: function () { logp("executed Exeter.") - eliminate_block("Exeter/Y") + eliminate_block(B_EXETER_Y) game.who = NOBODY if (game.result) return goto_game_over() @@ -2815,7 +2865,7 @@ states.enter_pretender_heir = { if (is_inactive_player(current)) return view.prompt = "Waiting for " + game.active + " to enter pretender heirs." view.prompt = "Death of an Heir: Enter " + block_name(game.who) + " in an exile area." - for (let where of AREALIST) + for (let where = first_area; where <= last_area; ++where) if (is_pretender_exile_area(where)) gen_action(view, 'area', where) }, @@ -2856,7 +2906,7 @@ states.supply_limits_pretender = { }, block: function (who) { push_undo() - game.turn_log.push([game.location[who]]) + game.turn_log.push(["#" + game.location[who]]) set_add(game.reduced, who) reduce_block(who) check_supply_penalty() @@ -2890,7 +2940,7 @@ states.enter_royal_heir = { return view.prompt = "Waiting for " + game.active + " to enter royal heirs." view.prompt = "Death of an Heir: Enter " + block_name(game.who) + " in a Crown area." let can_enter = false - for (let where of AREALIST) { + for (let where = first_area; where <= last_area; ++where) { if (is_crown_area(where) && is_friendly_or_vacant_area(where)) { gen_action(view, 'area', where) can_enter = true @@ -2940,7 +2990,7 @@ states.supply_limits_king = { }, block: function (who) { push_undo() - game.turn_log.push([game.location[who]]) + game.turn_log.push(["#" + game.location[who]]) set_add(game.reduced, who) reduce_block(who) check_supply_penalty() @@ -2967,45 +3017,45 @@ function goto_political_turn() { game.turn_log = [] // Levies disband - for (let b of BLOCKLIST) { + for (let b = 0; b < block_count; ++b) { if (!is_land_area(game.location[b])) continue switch (block_type(b)) { case 'bombard': case 'levies': case 'rebel': - game.turn_log.push([game.location[b]]) + game.turn_log.push(["#" + game.location[b]]) disband(b) break case 'mercenaries': switch (b) { - case "Welsh Mercenary": - game.turn_log.push([game.location[b]]) + case B_WELSH_MERCENARY: + game.turn_log.push(["#" + game.location[b]]) disband(b) break - case "Irish Mercenary": - if (game.location[b] !== "Ireland") { - game.turn_log.push([game.location[b], "Ireland"]) - game.location[b] = "Ireland" + case B_IRISH_MERCENARY: + if (game.location[b] !== IRELAND) { + game.turn_log.push(["#" + game.location[b], "#" + IRELAND]) + game.location[b] = IRELAND } break - case "Burgundian Mercenary": - case "Calais Mercenary": - if (game.location[b] !== "Calais") { - game.turn_log.push([game.location[b], "Calais"]) - game.location[b] = "Calais" + case B_BURGUNDIAN_MERCENARY: + case B_CALAIS_MERCENARY: + if (game.location[b] !== CALAIS) { + game.turn_log.push(["#" + game.location[b], "#" + CALAIS]) + game.location[b] = CALAIS } break - case "Scots Mercenary": - if (game.location[b] !== "Scotland") { - game.turn_log.push([game.location[b], "Scotland"]) - game.location[b] = "Scotland" + case B_SCOTS_MERCENARY: + if (game.location[b] !== SCOTLAND) { + game.turn_log.push(["#" + game.location[b], "#" + SCOTLAND]) + game.location[b] = SCOTLAND } break - case "French Mercenary": - if (game.location[b] !== "France") { - game.turn_log.push([game.location[b], "France"]) - game.location[b] = "France" + case B_FRENCH_MERCENARY: + if (game.location[b] !== FRANCE) { + game.turn_log.push(["#" + game.location[b], "#" + FRANCE]) + game.location[b] = FRANCE } break } @@ -3024,13 +3074,13 @@ function goto_political_turn() { if (l_count > y_count && block_owner(game.king) === YORK) { game.king = find_senior_heir(LANCASTER) game.pretender = find_senior_heir(YORK) - log(game.king + " usurped the throne!") + log(block_name(game.king) + " usurped the throne!") } else if (y_count > l_count && block_owner(game.king) === LANCASTER) { game.king = find_senior_heir(YORK) game.pretender = find_senior_heir(LANCASTER) - log(game.king + " usurped the throne!") + log(block_name(game.king) + " usurped the throne!") } else { - log(game.king + " remained king.") + log(block_name(game.king) + " remained king.") } // Game ends after last Usurpation check @@ -3048,7 +3098,7 @@ function goto_pretender_goes_home() { game.state = 'pretender_goes_home' game.turn_log = [] let choices = false - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (block_owner(b) === game.active && is_block_on_map(b)) if (go_home_if_possible(b)) choices = true @@ -3066,7 +3116,7 @@ states.pretender_goes_home = { return view.prompt = "Waiting for the Pretender to go to exile." gen_action_undo(view) let done = true - for (let b of BLOCKLIST) { + for (let b = 0; b < block_count; ++b) { if (block_owner(b) === game.active && is_block_on_map(b) && !set_has(game.moved, b)) { if (!is_in_exile(b)) { if (is_heir(b)) { @@ -3083,7 +3133,7 @@ states.pretender_goes_home = { } if (done) { view.prompt = "Pretender Goes Home: You may move nobles to another home." - for (let b of BLOCKLIST) { + for (let b = 0; b < block_count; ++b) { if (block_owner(b) === game.active && is_block_on_map(b) && !set_has(game.moved, b)) { if (!is_in_exile(b)) { if (is_at_home(b)) { @@ -3121,7 +3171,7 @@ states.pretender_goes_home_to = { else view.prompt = "Pretender Goes Home: Move " + block_name(game.who) + " to home." gen_action(view, 'block', game.who) - for (let where of AREALIST) { + for (let where = first_area; where <= last_area; ++where) { if (where !== game.location[game.who]) { if (is_heir(game.who)) { if (is_friendly_exile_area(where)) @@ -3134,9 +3184,9 @@ states.pretender_goes_home_to = { }, area: function (to) { if (is_exile_area(to)) - game.turn_log.push([block_name(game.who), to]) // TODO: "Exile"? + game.turn_log.push([block_name(game.who), "#" + to]) // TODO: "Exile"? else - game.turn_log.push([block_name(game.who), to]) // TODO: "Home"? + game.turn_log.push([block_name(game.who), "#" + to]) // TODO: "Home"? set_add(game.moved, game.who) game.location[game.who] = to game.who = NOBODY @@ -3171,7 +3221,7 @@ states.exile_limits_pretender = { block: function (who) { push_undo() let where = game.location[who] - logp("disbanded in " + where + ".") + logp("disbanded in #" + where + ".") game.exiles = game.exiles.filter(b => game.location[b] !== where) disband(who) }, @@ -3188,7 +3238,7 @@ function goto_king_goes_home() { game.state = 'king_goes_home' game.turn_log = [] let choices = false - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (block_owner(b) === game.active && is_block_on_map(b)) if (go_home_if_possible(b)) choices = true @@ -3206,7 +3256,7 @@ states.king_goes_home = { return view.prompt = "Waiting for the King to go home." gen_action_undo(view) let done = true - for (let b of BLOCKLIST) { + for (let b = 0; b < block_count; ++b) { if (block_owner(b) === game.active && is_block_on_map(b) && !set_has(game.moved, b)) { if (!is_in_exile(b)) { if (!is_at_home(b)) { @@ -3220,7 +3270,7 @@ states.king_goes_home = { } if (done) { view.prompt = "King Goes Home: You may move nobles and heirs to another home." - for (let b of BLOCKLIST) { + for (let b = 0; b < block_count; ++b) { if (block_owner(b) === game.active && is_block_on_map(b) && !set_has(game.moved, b)) { if (!is_in_exile(b)) { if (is_at_home(b)) { @@ -3255,13 +3305,13 @@ states.king_goes_home_to = { return view.prompt = "Waiting for the King to go home." view.prompt = "King Goes Home: Move " + block_name(game.who) + " to home." gen_action(view, 'block', game.who) - for (let where of AREALIST) + for (let where = first_area; where <= last_area; ++where) if (where !== game.location[game.who]) if (is_available_home_for(where, game.who)) gen_action(view, 'area', where) }, area: function (to) { - game.turn_log.push([block_name(game.who), to]) // TODO: "Home"? + game.turn_log.push([block_name(game.who), "#" + to]) // TODO: "Home"? set_add(game.moved, game.who) game.location[game.who] = to game.who = NOBODY @@ -3296,7 +3346,7 @@ states.exile_limits_king = { block: function (who) { push_undo() let where = game.location[who] - logp("disbanded in " + where + ".") + logp("disbanded in #" + where + ".") game.exiles = game.exiles.filter(b => game.location[b] !== where) disband(who) }, @@ -3309,7 +3359,7 @@ states.exile_limits_king = { function end_political_turn() { // Campaign reset set_clear(game.dead) - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) game.steps[b] = block_max_steps(b) ++game.campaign @@ -3343,11 +3393,11 @@ function make_battle_view() { flash: game.flash } - battle.title = game.attacker[game.where] + " attacks " + game.where + battle.title = game.attacker[game.where] + " attacks " + AREAS[game.where].name battle.title += " \u2014 round " + game.battle_round + " of 4" function fill_cell(cell, owner, fn) { - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (game.location[b] === game.where & block_owner(b) === owner && !set_has(game.dead, b) && fn(b)) cell.push(b) } @@ -3366,16 +3416,20 @@ exports.setup = function (seed, scenario, options) { log: [], undo: [], + state: null, active: null, + turn: 0, moves: 0, who: NOBODY, where: NOWHERE, show_cards: false, killed_heirs: { Lancaster: 0, York: 0 }, + king: NOBODY, + pretender: NOBODY, - location: {}, - steps: {}, + location: [], + steps: [], moved: [], dead: [], reserves: [], @@ -3399,7 +3453,10 @@ exports.setup = function (seed, scenario, options) { else throw new Error("Unknown scenario:", scenario) + logbr() log(".h1 " + scenario) + logbr() + start_campaign() return game } @@ -3453,6 +3510,7 @@ exports.view = function(state, current) { location: game.location, steps: game.steps, moved: game.moved, + dead: game.dead, battle: null, prompt: null, } |