summaryrefslogtreecommitdiff
path: root/rules.js
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2022-12-27 14:54:09 +0100
committerTor Andersson <tor@ccxvii.net>2023-02-18 13:02:38 +0100
commit68d98bb82ba085ec7648d12676704333e530d30d (patch)
treed218bc64fcaf0641dd90f332c37966402141b44b /rules.js
parent1a1c5531d9729d744584ca150c929befb6c0908f (diff)
downloadnevsky-68d98bb82ba085ec7648d12676704333e530d30d.tar.gz
Losses and routs.
Diffstat (limited to 'rules.js')
-rw-r--r--rules.js253
1 files changed, 222 insertions, 31 deletions
diff --git a/rules.js b/rules.js
index db17a41..227f80e 100644
--- a/rules.js
+++ b/rules.js
@@ -538,6 +538,14 @@ function get_lord_routed_forces(lord, n) {
return pack4_get(game.pieces.routed[lord], n)
}
+function has_unrouted_units(lord) {
+ return game.pieces.forces[lord] !== 0
+}
+
+function has_routed_units(lord) {
+ return game.pieces.routed[lord] !== 0
+}
+
function set_lord_locale(lord, locale) {
game.pieces.locale[lord] = locale
}
@@ -3300,7 +3308,7 @@ states.actions = {
pass() {
push_undo()
log("Passed.")
- spend_all_actions() // TODO: maybe only one action?
+ spend_all_actions()
},
end_actions() {
@@ -4313,7 +4321,6 @@ function this_lord_has_russian_raiders() {
}
function can_ravage_locale(loc) {
- // TODO: cost 2 if enemy lord is adjacent (2nd ed)
if (!is_enemy_territory(loc))
return false
if (has_conquered_marker(loc))
@@ -4500,8 +4507,12 @@ function can_action_sail() {
if (!has_enough_available_ships_for_horses())
return false
- // TODO: and a valid destination
- return true
+ // and a valid destination
+ for (let to of data.seaports)
+ if (to !== here && !has_enemy_lord(to))
+ return true
+
+ return false
}
function goto_sail() {
@@ -4776,6 +4787,8 @@ function start_battle() {
log_h3(`Battle at %${here}`)
+ // TODO: array <= 3 attacking lords automatically
+
game.battle = {
where: here,
attacker: game.active,
@@ -5208,6 +5221,17 @@ function debug_group_list(list) {
console.log(debug_group(sg), "strike", debug_group(hg))
}
+function has_no_unrouted_lords() {
+ // All unrouted lords are either in battle array or in reserves
+ for (let p = 0; p < 12; ++p)
+ if (is_friendly_lord(game.battle.array[p]))
+ return false
+ for (let lord of game.battle.reserves)
+ if (is_friendly_locale(lord))
+ return false
+ return true
+}
+
function has_front_strike_choice() {
let s = game.battle.step & 1
let f = pack_battle_array_front()
@@ -5329,6 +5353,22 @@ function goto_next_strike() {
}
function goto_strike() {
+ function is_battle_over() {
+ set_active_attacker()
+ if (has_no_unrouted_lords())
+ return true
+ set_active_defender()
+ if (has_no_unrouted_lords())
+ return true
+ return false
+ }
+
+ // Exit early if one side is completely routed
+ if (is_battle_over()) {
+ goto_next_strike()
+ return
+ }
+
let s = game.battle.step & 1
if (s)
set_active_attacker()
@@ -5366,8 +5406,9 @@ function goto_strike() {
function goto_select_strike_group() {
if (game.battle.groups.length === 0)
goto_next_strike()
+ else if (game.battle.groups.length === 1)
+ select_strike_group(0)
else
- // TODO: select if 1
game.state = "select_strike_group"
}
@@ -5424,11 +5465,12 @@ function select_strike_group(i) {
}
function goto_select_hit_lord() {
- if (game.battle.hg.length > 1) {
- game.state = "select_hit_lord"
- } else {
+ if (game.battle.hg.length === 0)
+ end_hit_lord()
+ else if (game.battle.hg.length === 1)
select_hit_lord(game.battle.array[game.battle.hg[0]])
- }
+ else
+ game.state = "select_hit_lord"
}
states.select_hit_lord = {
@@ -5448,20 +5490,40 @@ function select_hit_lord(lord) {
game.state = "hit_lord"
}
-function has_lord_routed(lord) {
- return game.pieces.forces[lord] === 0
-}
-
function rout_lord(lord) {
log(`L${lord} routed!`)
- for (let p = 0; p < 12; ++p)
- if (game.battle.array[p] === lord)
+
+ for (let p = 0; p < 12; ++p) {
+ if (game.battle.array[p] === lord) {
+
+ // remove from battle array
game.battle.array[p] = NOBODY
+
+ // remove from current hit group
+ array_remove_item(game.battle.hg, p)
+
+ for (let i = 0; i < game.battle.groups;) {
+ let hg = game.battle.groups[i][1]
+
+ // remove from other hit groups
+ array_remove_item(hg, p)
+
+ // remove strike groups with no remaining targets
+ if (hg.length === 0)
+ array_remove(game.battle.groups, i)
+ else
+ ++i
+ }
+
+ break
+ }
+ }
+
set_add(game.battle.routed, lord)
}
function resume_hit_lord() {
- if ((game.battle.h1 === 0 && game.battle.h2 === 0) || has_lord_routed(game.who))
+ if ((game.battle.h1 === 0 && game.battle.h2 === 0) || !has_unrouted_units(game.who))
end_hit_lord()
}
@@ -5474,7 +5536,7 @@ function action_hit_lord(lord, type) {
let protection = FORCE_PROTECTION[type]
let evade = FORCE_EVADE[type]
- // TODO: manual choice
+ // TODO: manual choice of hit type
let ap = (game.battle.h2 > 0) ? 2 : 0
if (evade > 0 && !game.battle.storm) {
@@ -5498,15 +5560,16 @@ function action_hit_lord(lord, type) {
rout_unit(lord, type)
}
- // TODO: manual choice
+ // TODO: manual choice of hit type
if (game.battle.h2)
game.battle.h2--
else
game.battle.h1--
- if (has_lord_routed(lord)) {
+ if (!has_unrouted_units(lord)) {
rout_lord(lord)
- // TODO: remaining hits!
+ if (game.battle.h1 > 0 || game.battle.h2 > 0)
+ log("TODO: remaining hits!")
}
resume_hit_lord()
@@ -5567,13 +5630,27 @@ function end_hit_lord() {
// === BATTLE: NEW ROUND ===
function goto_new_round() {
- // TODO: no unrouted lords
if (game.battle.conceded) {
game.battle.loser = game.battle.conceded
end_battle()
- } else {
- goto_concede()
+ return
}
+
+ set_active_attacker()
+ if (has_no_unrouted_lords()) {
+ game.battle.loser = game.active
+ end_battle()
+ return
+ }
+
+ set_active_defender()
+ if (has_no_unrouted_lords()) {
+ game.battle.loser = game.active
+ end_battle()
+ return
+ }
+
+ goto_concede()
}
// === ENDING THE BATTLE ===
@@ -5599,7 +5676,9 @@ function end_battle() {
// spoils
// service
- set_active(game.battle.loser)
+ log_br()
+ log(`${game.battle.loser} lost battle.`)
+
set_active_loser()
goto_battle_withdraw()
}
@@ -5917,8 +5996,115 @@ states.battle_remove = {
// === ENDING THE BATTLE: LOSSES ===
function goto_battle_losses() {
- log("TODO: losses")
- goto_battle_spoils()
+ set_active_loser() // loser first, to save time
+ resume_battle_losses()
+}
+
+function resume_battle_losses() {
+ game.state = "battle_losses"
+ for (let lord = first_friendly_lord; lord <= last_friendly_lord; ++lord)
+ if (has_routed_units(lord))
+ return
+ resume_battle_losses_remove()
+}
+
+function resume_battle_losses_remove() {
+ game.state = "battle_losses_remove"
+ for (let lord = first_friendly_lord; lord <= last_friendly_lord; ++lord)
+ if (is_lord_on_map(lord) && !has_unrouted_units(lord))
+ return
+ end_battle_losses()
+}
+
+function end_battle_losses() {
+ if (game.active === game.battle.loser) {
+ set_active_victor()
+ resume_battle_losses()
+ } else {
+ goto_battle_spoils()
+ }
+}
+
+function action_losses(lord, type) {
+ let protection = FORCE_PROTECTION[type]
+ let evade = FORCE_EVADE[type]
+
+ let target = Math.max(protection, evade)
+ if (game.active !== game.battle.conceded)
+ target = 1
+
+ let die = roll_die()
+ if (die <= target) {
+ log(`L${lord} ${FORCE_TYPE_NAME[type]} ${die} <= ${target}`)
+ add_lord_routed_forces(lord, type, -1)
+ add_lord_forces(lord, type, 1)
+ } else {
+ log(`L${lord} ${FORCE_TYPE_NAME[type]} ${die} > ${target}`)
+ add_lord_routed_forces(lord, type, -1)
+ if (type === SERFS)
+ game.pieces.smerdi++
+ }
+
+ resume_battle_losses()
+}
+
+states.battle_losses = {
+ prompt() {
+ view.prompt = "Losses: Determine the fate of your routed units."
+ for (let lord = first_friendly_lord; lord <= last_friendly_lord; ++lord) {
+ if (is_lord_on_map(lord) && has_routed_units(lord)) {
+ if (get_lord_routed_forces(lord, KNIGHTS) > 0)
+ gen_action_routed_knights(lord)
+ if (get_lord_routed_forces(lord, SERGEANTS) > 0)
+ gen_action_routed_sergeants(lord)
+ if (get_lord_routed_forces(lord, LIGHT_HORSE) > 0)
+ gen_action_routed_light_horse(lord)
+ if (get_lord_routed_forces(lord, ASIATIC_HORSE) > 0)
+ gen_action_routed_asiatic_horse(lord)
+ if (get_lord_routed_forces(lord, MEN_AT_ARMS) > 0)
+ gen_action_routed_men_at_arms(lord)
+ if (get_lord_routed_forces(lord, MILITIA) > 0)
+ gen_action_routed_militia(lord)
+ if (get_lord_routed_forces(lord, SERFS) > 0)
+ gen_action_routed_serfs(lord)
+ }
+ }
+ },
+ routed_knights(lord) {
+ action_losses(lord, KNIGHTS)
+ },
+ routed_sergeants(lord) {
+ action_losses(lord, SERGEANTS)
+ },
+ routed_light_horse(lord) {
+ action_losses(lord, LIGHT_HORSE)
+ },
+ routed_asiatic_horse(lord) {
+ action_losses(lord, ASIATIC_HORSE)
+ },
+ routed_men_at_arms(lord) {
+ action_losses(lord, MEN_AT_ARMS)
+ },
+ routed_militia(lord) {
+ action_losses(lord, MILITIA)
+ },
+ routed_serfs(lord) {
+ action_losses(lord, SERFS)
+ },
+}
+
+states.battle_losses_remove = {
+ prompt() {
+ view.prompt = "Losses: Remove lords who lost all their forces."
+ for (let lord = first_friendly_lord; lord <= last_friendly_lord; ++lord)
+ if (is_lord_on_map(lord) && !has_unrouted_units(lord))
+ gen_action_lord(lord)
+ },
+ lord(lord) {
+ set_delete(game.battle.retreated, lord)
+ disband_lord(lord, true)
+ resume_battle_losses_remove()
+ },
}
// === ENDING THE BATTLE: SPOILS ===
@@ -5989,13 +6175,15 @@ states.battle_spoils = {
function goto_battle_service() {
set_active_loser()
if (game.battle.retreated)
- game.state = "battle_service"
+ resume_battle_service()
else
goto_battle_aftermath()
}
function resume_battle_service() {
- if (game.battle.retreated.length === 0)
+ if (game.battle.retreated.length > 0)
+ game.state = "battle_service"
+ else
goto_battle_aftermath()
}
@@ -6365,6 +6553,7 @@ function goto_disband() {
function disband_lord(lord, permanently = false) {
let here = get_lord_locale(lord)
+ let turn = current_turn()
if (permanently) {
log(`Disbanded L${lord} permanently.`)
@@ -6376,9 +6565,11 @@ function disband_lord(lord, permanently = false) {
set_lord_service(lord, NEVER)
} else {
log(`Disbanded L${lord}.`)
- let turn = current_turn() + data.lords[lord].service
- set_lord_locale(lord, CALENDAR + turn)
- set_lord_service(lord, turn)
+ if (is_levy_phase()
+ set_lord_locale(lord, CALENDAR + turn + data.lords[lord].service)
+ else
+ set_lord_locale(lord, CALENDAR + turn + data.lords[lord].service + 1)
+ set_lord_service(lord, NEVER)
}
remove_lieutenant(lord)