summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2024-10-20 22:42:35 +0200
committerTor Andersson <tor@ccxvii.net>2024-10-21 00:49:17 +0200
commitc80d41f09ca11bcc41f35e55b55142e22820cfd5 (patch)
tree3c60a2a72e8213fcaf12bca010c18d4f828f02e8
parent73afac930d3f6671ee6d3d1d57cc889e0ed4dc23 (diff)
downloadmaria-c80d41f09ca11bcc41f35e55b55142e22820cfd5.tar.gz
Re-enter supply trains during movement.
-rw-r--r--play.js4
-rw-r--r--rules.js264
2 files changed, 247 insertions, 21 deletions
diff --git a/play.js b/play.js
index 75591c1..7b72851 100644
--- a/play.js
+++ b/play.js
@@ -1022,6 +1022,10 @@ function on_update() {
for (let p = 0; p < 20; ++p)
action_button_with_argument("supreme", p, power_name[piece_power[p]])
+ for (let pow of all_powers)
+ action_button_with_argument("power", pow, power_name[pow])
+
+ action_button("re_enter", "Re-enter")
action_button("force_march", "Force march")
action_button("take", "Take")
diff --git a/rules.js b/rules.js
index c318f40..d6cb220 100644
--- a/rules.js
+++ b/rules.js
@@ -4,10 +4,7 @@
/* TODO
-re-entering supply trains during movement (10.2)
- supply payment
-
-winter
+winter recruitment
tc draw
no TCs for minor power if major fortress enemy controlled
@@ -69,6 +66,15 @@ function is_map_space(s) {
return s >= 0 && s <= 618
}
+function is_piece_on_map(p) {
+ return is_map_space(game.pos[p])
+}
+
+function is_piece_on_map_or_eliminated(p) {
+ let s = game.pos[p]
+ return is_map_space(s) || s === ELIMINATED
+}
+
function find_city(city) {
let n = data.cities.name.length
let x = -1
@@ -104,6 +110,9 @@ const P_AUSTRIA = 5
const POWER_NAME = [ "France", "Bavaria", "Prussia", "Saxony", "Pragmatic Army", "Austria" ]
+const is_major_power = [ 1, 0, 1, 0, 1, 1 ]
+const is_minor_power = [ 0, 1, 0, 1, 0, 0 ]
+
const SPADES = 0
const CLUBS = 1
const HEARTS = 2
@@ -136,15 +145,9 @@ const max_power_troops = [ 5*8, 1*8, 4*8, 1*8, 3*8, 6*8 ]
const all_powers = [ 0, 1, 2, 3, 4, 5 ]
-const all_home_or_depot_cities = [
-]
-
const all_power_depots = [
]
-const all_power_re_entry_cities = [
-]
-
const all_power_generals = [
[ 0, 1, 2, 3, 4 ],
[ 5 ],
@@ -288,6 +291,8 @@ const all_powers_pragmatic_austria = [ P_PRAGMATIC, P_AUSTRIA ]
const all_powers_none = []
const all_powers_france_bavaria = [ P_FRANCE, P_BAVARIA ]
const all_powers_bavaria = [ P_BAVARIA ]
+const all_powers_pragmatic = [ P_PRAGMATIC ]
+const all_powers_austria = [ P_AUSTRIA ]
const all_powers_prussia_saxony = [ P_PRUSSIA, P_SAXONY ]
const all_powers_saxony = [ P_SAXONY ]
const all_powers_bavaria_saxony = [ P_BAVARIA, P_SAXONY ]
@@ -300,6 +305,14 @@ function coop_major_power(pow) {
return pow
}
+function coop_minor_power(pow) {
+ if (pow === P_FRANCE)
+ return P_BAVARIA
+ if (pow === P_PRUSSIA)
+ return P_SAXONY
+ return pow
+}
+
function all_friendly_minor_powers(pow) {
switch (pow) {
case P_FRANCE:
@@ -327,6 +340,21 @@ function all_coop_powers(pow) {
}
}
+function all_controlled_powers(pow) {
+ switch (pow) {
+ case P_FRANCE:
+ case P_BAVARIA:
+ return all_powers_france_bavaria
+ case P_PRUSSIA:
+ case P_SAXONY:
+ return all_powers_prussia_saxony
+ case P_PRAGMATIC:
+ return all_powers_pragmatic
+ case P_AUSTRIA:
+ return all_powers_austria
+ }
+}
+
function is_hostile_to_austria() {
switch (game.power) {
case P_FRANCE:
@@ -532,6 +560,14 @@ all_home_country_fortresses[P_SAXONY] = set_intersect(all_fortresses, data.count
all_home_country_fortresses[P_AUSTRIA] = set_intersect(all_fortresses, data.country.Austria)
all_home_country_fortresses[P_PRAGMATIC] = set_intersect(all_fortresses, data.country.Netherlands)
+const all_home_country_major_fortresses = []
+all_home_country_major_fortresses[P_FRANCE] = set_intersect(data.type.major_fortress, data.country.France)
+all_home_country_major_fortresses[P_BAVARIA] = set_intersect(data.type.major_fortress, data.country.Bavaria)
+all_home_country_major_fortresses[P_PRUSSIA] = set_intersect(data.type.major_fortress, data.country.Prussia)
+all_home_country_major_fortresses[P_SAXONY] = set_intersect(data.type.major_fortress, data.country.Saxony)
+all_home_country_major_fortresses[P_AUSTRIA] = set_intersect(data.type.major_fortress, data.country.Austria)
+all_home_country_major_fortresses[P_PRAGMATIC] = set_intersect(data.type.major_fortress, data.country.Netherlands)
+
const all_silesian_fortresses = set_intersect(all_fortresses, data.country.Silesia)
const protect_range = []
@@ -591,6 +627,10 @@ function is_enemy_controlled_fortress(s) {
return false
}
+function is_friendly_controlled_fortress(s) {
+ return !is_enemy_controlled_fortress(s)
+}
+
function is_power_controlled_fortress(pow, s) {
let owner = map_get(game.elector, s, -1)
if (owner < 0)
@@ -741,7 +781,7 @@ function count_used_troops() {
function count_unused_troops_on_map() {
let n = 0
for (let p of all_power_generals[game.power])
- if (is_map_space(game.pos[p]))
+ if (is_piece_on_map(p))
n += 8 - game.troops[p]
return n
}
@@ -1262,7 +1302,7 @@ function goto_supply() {
}
for (let p of all_controlled_generals(game.power)) {
- if (!is_map_space(game.pos[p]))
+ if (!is_piece_on_map(p))
continue
if (is_hostile_to_austria()) {
@@ -1532,6 +1572,7 @@ function goto_movement() {
log_br()
+ game.move_re_entered = 0
game.move_conq = []
}
@@ -1558,7 +1599,7 @@ states.movement = {
let done_trains = true
for (let p of all_controlled_generals(game.power)) {
- if (!set_has(game.moved, p) && is_map_space(game.pos[p])) {
+ if (!set_has(game.moved, p) && is_piece_on_map(p)) {
if (can_general_move_anywhere(p)) {
gen_action_piece(p)
done_generals = false
@@ -1567,10 +1608,15 @@ states.movement = {
}
for (let p of all_controlled_trains(game.power)) {
- if (!set_has(game.moved, p) && is_map_space(game.pos[p])) {
- if (can_train_move_anywhere(p)) {
- gen_action_piece(p)
- done_trains = false
+ if ((game.move_re_entered & (1 << piece_power[p])) === 0)
+ if (can_train_re_enter(p))
+ view.actions.re_enter = 1
+ if (!set_has(game.moved, p)) {
+ if (is_piece_on_map(p)) {
+ if (can_train_move_anywhere(p)) {
+ gen_action_piece(p)
+ done_trains = false
+ }
}
}
}
@@ -1589,6 +1635,10 @@ states.movement = {
else
view.actions.end_movement = 1
},
+ re_enter() {
+ push_undo()
+ goto_re_enter_train()
+ },
piece(p) {
push_undo()
@@ -1626,6 +1676,7 @@ states.movement = {
log_conquest(game.move_conq)
delete game.move_conq
+ delete game.move_re_entered
goto_combat()
},
@@ -2020,6 +2071,178 @@ states.move_supreme = {
},
}
+/* RE-ENTER SUPPLY TRAIN */
+
+/*
+ TODO: move to end of movement (like friedrich recruitment)?
+*/
+
+function goto_re_enter_train() {
+ if (all_controlled_powers(game.power).length > 1)
+ game.state = "re_enter_train_power"
+ else
+ game.state = "re_enter_train"
+ game.re_enter = {
+ pool: [],
+ used: [],
+ pieces: [],
+ }
+ game.count = 0
+}
+
+function can_train_re_enter(p) {
+ return (
+ (is_piece_on_map(p) || game.pos[p] === ELIMINATED) &&
+ !set_has(game.moved, p) &&
+ train_has_re_entry_city(p)
+ )
+}
+
+function train_has_re_entry_city(p) {
+ let pow = piece_power[p]
+ if (coop_minor_power(pow) !== pow)
+ return can_re_enter_train_at_power_fortress(pow) || can_re_enter_train_at_power_fortress(coop_minor_power(pow))
+ else
+ return can_re_enter_train_at_power_fortress(pow)
+}
+
+function gen_re_enter_train_at_power_fortress(pow) {
+ for (let s of all_home_country_major_fortresses[pow])
+ if (is_friendly_controlled_fortress(s) && !has_any_piece(s))
+ gen_action_space(s)
+}
+
+function can_re_enter_train_at_power_fortress(pow) {
+ for (let s of all_home_country_major_fortresses[pow])
+ if (is_friendly_controlled_fortress(s) && !has_any_piece(s))
+ return true
+ return false
+}
+
+states.re_enter_train_power = {
+ inactive: "move",
+ prompt() {
+ prompt("Re-enter supply train from which power?")
+ view.actions.power = []
+ for (let pow of all_controlled_powers(game.power)) {
+ if (game.move_re_entered & (1 << pow))
+ continue
+ for (let p of all_power_trains[pow]) {
+ if (can_train_re_enter(p)) {
+ view.actions.power.push(pow)
+ break
+ }
+ }
+ }
+ },
+ power(pow) {
+ game.move_re_entered |= (1 << pow)
+ set_active_to_power(pow)
+ game.state = "re_enter_train"
+ },
+}
+
+states.re_enter_train = {
+ inactive: "move",
+ prompt() {
+ let str
+
+ let paid = game.count + sum_card_values(game.re_enter.pool)
+
+ let av_trains = 0
+ for (let p of all_power_trains[game.power]) {
+ if (can_train_re_enter(p)) {
+ if (paid >= 4)
+ gen_action_piece(p)
+ av_trains += 1
+ }
+ }
+
+ if (paid / 4 < av_trains) {
+ for (let c of game.hand[game.power])
+ gen_action_card(c)
+ }
+
+ if (game.re_enter.used.length > 0)
+ view.actions.next = 1
+
+ if (av_trains > 0) {
+ str = "Re-enter supply trains for 4 each"
+ if (paid > 1)
+ str += " \u2014 " + paid + " points."
+ else if (paid === 1)
+ str += " \u2014 1 point."
+ else
+ str += "."
+ } else {
+ str = "Re-enter supply trains done."
+ }
+
+ prompt(str)
+
+ view.draw = game.re_enter.pool
+ },
+ piece(p) {
+ push_undo()
+ spend_card_value(game.re_enter.pool, game.re_enter.used, 4)
+ set_add(game.moved, p)
+ map_set(game.re_enter.pieces, p, game.pos[p])
+ game.state = "re_enter_train_where"
+ game.selected = p
+ },
+ card(c) {
+ push_undo()
+ set_delete(game.hand[game.power], c)
+ set_add(game.re_enter.pool, c)
+ },
+ next() {
+ push_undo()
+ end_re_enter_train()
+ },
+}
+
+states.re_enter_train_where = {
+ inactive: "move",
+ prompt() {
+ prompt("Re-enter supply train at a major fortress.")
+
+ view.selected = game.selected
+ view.draw = game.re_enter.pool
+
+ gen_re_enter_train_at_power_fortress(game.power)
+ if (coop_minor_power(game.power) !== game.power)
+ gen_re_enter_train_at_power_fortress(coop_minor_power(game.power))
+ },
+ space(s) {
+ game.pos[game.selected] = s
+ game.selected = -1
+ game.state = "re_enter_train"
+ },
+}
+
+function end_re_enter_train() {
+ if (game.re_enter.used.length > 0) {
+ log_br()
+ log(POWER_NAME[game.power] + " spent " + game.re_enter.used.map(format_card).join(", ") + ".")
+ map_for_each(game.re_enter.pieces, (p, s) => {
+ if (s !== ELIMINATED)
+ log("Re-entered P" + p + " from S" + s + " at S" + game.pos[p] + ".")
+ else
+ log("Re-entered P" + p + " at S" + game.pos[p] + ".")
+ })
+ log_br()
+ }
+
+ // put back into hand unused cards
+ for (let c of game.re_enter.pool)
+ set_add(game.hand[game.power], c)
+
+ delete game.re_enter
+
+ set_active_to_current_action_stage()
+ game.state = "movement"
+}
+
/* RECRUITMENT */
function troop_cost() {
@@ -2144,7 +2367,6 @@ states.recruit = {
str = "Nothing to recruit"
let paid = game.count + sum_card_values(game.recruit.pool)
-
if (paid > 1)
str += " \u2014 " + paid + " points."
else if (paid === 1)
@@ -2216,7 +2438,7 @@ function end_recruit() {
else
log("Recruited with " + game.recruit.used.map(format_card).join(", ") + ".")
map_for_each(game.recruit.pieces, (p,s) => {
- log("Re-entered P" + p + " at S" + s + ".")
+ log("Re-entered P" + p + " to S" + s + ".")
})
if (game.recruit.troops)
log(">" + game.recruit.troops + " troops")
@@ -2271,11 +2493,11 @@ function goto_combat() {
let to = []
for (let p of all_controlled_generals(game.power))
- if (is_map_space(game.pos[p]))
+ if (is_piece_on_map(p))
set_add(from, game.pos[p])
for (let p of all_enemy_generals(game.power))
- if (is_map_space(game.pos[p]))
+ if (is_piece_on_map(p))
set_add(to, game.pos[p])
game.combat = []