summaryrefslogtreecommitdiff
path: root/rules.js
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2024-10-21 11:45:33 +0200
committerTor Andersson <tor@ccxvii.net>2024-10-21 11:51:54 +0200
commitd47d48c2d24f65ee8c753f175dcbb3e0b4ed18ec (patch)
tree1731d9496ef2bf20deb4cd43480f22551004df30 /rules.js
parent5b20a8ae9194fe76c6b589005687a6e33682e1ef (diff)
downloadmaria-d47d48c2d24f65ee8c753f175dcbb3e0b4ed18ec.tar.gz
winter recruitment
Diffstat (limited to 'rules.js')
-rw-r--r--rules.js399
1 files changed, 191 insertions, 208 deletions
diff --git a/rules.js b/rules.js
index 7165442..bfafa26 100644
--- a/rules.js
+++ b/rules.js
@@ -4,7 +4,11 @@
/* TODO
-winter recruitment
+renumber
+ major powers 0-4
+ minor powers 5-6
+
+winter scoring
tc draw
no TCs for minor power if major fortress enemy controlled
@@ -141,13 +145,8 @@ const DRESDEN = find_city("Dresden")
const ENGLAND = find_city("England")
-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_power_depots = [
-]
-
const all_power_generals = [
[ 0, 1, 2, 3, 4 ],
[ 5 ],
@@ -228,6 +227,7 @@ const piece_name = [
const all_pieces = [ ...all_power_generals.flat(), ...all_power_trains.flat() ]
const all_trains = [ ...all_power_trains.flat() ]
const all_generals = [ ...all_power_generals.flat() ]
+const all_generals_by_rank = all_generals.slice().sort((a,b)=>piece_rank[a]-piece_rank[b])
const all_france_bavaria_generals = [
...all_power_generals[P_FRANCE],
@@ -724,8 +724,8 @@ function get_supreme_commander(s) {
for (let p of all_generals)
if ((game.supreme & (1<<p)) && game.pos[p] === s)
return p
- for (let p of all_generals)
- if (!(game.supreme & (1<<p)) && game.pos[p] === s)
+ for (let p of all_generals_by_rank)
+ if (game.pos[p] === s)
return p
return -1
}
@@ -755,22 +755,6 @@ function get_space_suit(s) {
throw "IMPOSSIBLE"
}
-function count_eliminated_trains() {
- let n = 0
- for (let p of all_power_trains[game.power])
- if (game.pos[p] === ELIMINATED)
- ++n
- return n
-}
-
-function count_eliminated_generals() {
- let n = 0
- for (let p of all_power_generals[game.power])
- if (game.pos[p] === ELIMINATED)
- ++n
- return n
-}
-
function count_used_troops() {
let current = 0
for (let p of all_power_generals[game.power])
@@ -778,14 +762,6 @@ function count_used_troops() {
return current
}
-function count_unused_troops_on_map() {
- let n = 0
- for (let p of all_power_generals[game.power])
- if (is_piece_on_map(p))
- n += 8 - game.troops[p]
- return n
-}
-
function has_any_piece(to) {
for (let p = 0; p <= last_piece; ++p)
if (game.pos[p] === to)
@@ -865,15 +841,6 @@ function select_stack(s) {
return list
}
-function add_one_troop(p) {
- for (let x of all_power_generals[game.power]) {
- if (game.pos[x] === game.pos[p] && game.troops[x] < 8) {
- game.troops[x] ++
- break
- }
- }
-}
-
function eliminate_general(p, indent) {
if (indent)
log(">P" + p + " eliminated")
@@ -887,6 +854,26 @@ function eliminate_general(p, indent) {
/* SEQUENCE OF PLAY */
+const TURN_NAME = [
+ "Setup",
+ "Turn 1",
+ "Turn 2",
+ "Turn 3",
+ "Winter 1741",
+ "Turn 4",
+ "Turn 5",
+ "Turn 6",
+ "Winter 1742",
+ "Turn 7",
+ "Turn 8",
+ "Turn 9",
+ "Winter 1743",
+ "Turn 10",
+ "Turn 11",
+ "Turn 12",
+ "Winter 1744",
+]
+
const POWER_FROM_ACTION_STAGE = [
P_FRANCE, // and bavaria
P_PRUSSIA, // and saxony
@@ -905,8 +892,29 @@ function set_active_to_current_action_stage() {
set_active_to_power(POWER_FROM_ACTION_STAGE[game.stage])
}
+const POWER_FROM_WINTER_STAGE = [
+ P_FRANCE,
+ P_BAVARIA,
+ P_PRUSSIA,
+ P_SAXONY,
+ P_PRAGMATIC,
+ P_AUSTRIA,
+]
+
+const title_from_winter_stage = [
+ "=0",
+ "=1",
+ "=2",
+ "=3",
+ "=4",
+ "=5",
+]
+
+function set_active_to_current_winter_stage() {
+ set_active_to_power(POWER_FROM_WINTER_STAGE[game.stage])
+}
+
function goto_end_turn() {
- // TODO: winter
goto_start_turn()
}
@@ -914,15 +922,18 @@ function goto_start_turn() {
game.turn += 1
game.stage = 0
- log("# Turn " + game.turn)
+ log("# " + TURN_NAME[game.turn])
game.selected = -1
- delete game.ia_lost
- // MARIA: politics
- // MARIA: hussars
+ if (game.turn % 4 === 0) {
+ goto_winter_turn()
+ } else {
+
+ // MARIA: politics
- goto_place_hussars()
+ goto_place_hussars()
+ }
}
function goto_action_stage() {
@@ -962,6 +973,9 @@ function goto_place_hussars() {
function end_place_hussars() {
set_clear(game.moved)
+ log("=" + P_AUSTRIA + " Hussars")
+ for (let p of all_hussars)
+ log("Hussar at S" + game.pos[p] + ".")
goto_action_stage()
}
@@ -2073,16 +2087,12 @@ 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 = {
+ game.recruit = {
pool: [],
used: [],
pieces: [],
@@ -2094,12 +2104,11 @@ 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)
+ has_re_entry_space_for_supply_train(piece_power[p])
)
}
-function train_has_re_entry_city(p) {
- let pow = piece_power[p]
+function has_re_entry_space_for_supply_train(pow) {
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
@@ -2147,7 +2156,7 @@ states.re_enter_train = {
prompt() {
let str
- let paid = game.count + sum_card_values(game.re_enter.pool)
+ let paid = game.count + sum_card_values(game.recruit.pool)
let av_trains = 0
for (let p of all_power_trains[game.power]) {
@@ -2163,7 +2172,7 @@ states.re_enter_train = {
gen_action_card(c)
}
- if (game.re_enter.used.length > 0)
+ if (game.recruit.used.length > 0)
view.actions.next = 1
if (av_trains > 0) {
@@ -2180,20 +2189,20 @@ states.re_enter_train = {
prompt(str)
- view.draw = game.re_enter.pool
+ view.draw = game.recruit.pool
},
piece(p) {
push_undo()
- spend_card_value(game.re_enter.pool, game.re_enter.used, 4)
+ spend_card_value(game.recruit.pool, game.recruit.used, 4)
set_add(game.moved, p)
- map_set(game.re_enter.pieces, p, game.pos[p])
+ map_set(game.recruit.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)
+ set_add(game.recruit.pool, c)
},
next() {
push_undo()
@@ -2207,24 +2216,30 @@ states.re_enter_train_where = {
prompt("Re-enter supply train at a major fortress.")
view.selected = game.selected
- view.draw = game.re_enter.pool
+ view.draw = game.recruit.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
+ space(to) {
+ game.pos[game.selected] = to
game.selected = -1
game.state = "re_enter_train"
+
+ // remove hussars
+ for (let p of all_hussars) {
+ log("P" + p + " removed.")
+ game.pos[p] = ELIMINATED
+ }
},
}
function end_re_enter_train() {
- if (game.re_enter.used.length > 0) {
+ if (game.recruit.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) => {
+ log(POWER_NAME[game.power] + " spent " + game.recruit.used.map(format_card).join(", ") + ".")
+ map_for_each(game.recruit.pieces, (p, s) => {
if (s !== ELIMINATED)
log("Re-entered P" + p + " from S" + s + " at S" + game.pos[p] + ".")
else
@@ -2234,53 +2249,47 @@ function end_re_enter_train() {
}
// put back into hand unused cards
- for (let c of game.re_enter.pool)
+ for (let c of game.recruit.pool)
set_add(game.hand[game.power], c)
- delete game.re_enter
+ delete game.recruit
set_active_to_current_action_stage()
game.state = "movement"
}
-/* RECRUITMENT */
+/* WINTER RECRUITMENT */
-function troop_cost() {
- if (is_map_space(game.recruit.re_enter))
- return 8
- return 6
-}
+function goto_winter_turn() {
-function spend_recruit_cost() {
- spend_card_value(game.recruit.pool, game.recruit.used, troop_cost())
-}
+ // TODO: winter scoring
-function has_available_depot() {
- for (let s of all_power_depots[game.power])
- // TODO: also allied other player's pieces?
- if (!has_enemy_piece(s))
- return true
- return false
+ goto_winter_stage()
}
-function can_re_enter_general(to) {
- if (has_friendly_supply_train(to))
- return false
- if (has_non_cooperative_general(to))
- return false
- if (1 + count_generals(to) > 3)
- return false
- return true
+function goto_winter_stage() {
+ set_active_to_current_winter_stage()
+
+ clear_undo()
+
+ log(title_from_winter_stage[game.stage])
+
+ goto_recruit()
}
-function can_re_enter_supply_train(s) {
- return !has_any_piece(s)
+function end_winter_stage() {
+ clear_undo()
+
+ if (++game.stage === 6)
+ goto_end_turn()
+ else
+ goto_winter_stage()
}
function goto_recruit() {
game.count = 0
- if (!can_recruit_anything_in_theory()) {
+ if (!can_recruit_anything()) {
end_recruit()
return
}
@@ -2289,80 +2298,62 @@ function goto_recruit() {
pool: [],
used: [],
pieces: [],
- re_enter: ELIMINATED,
troops: 0,
}
- // if all depots have enemy pieces, choose ONE city in given sector and COST is 8
- if (has_available_depot())
- game.state = "recruit"
- else
- game.state = "re_enter_choose_city"
+ game.state = "recruit"
}
-states.re_enter_choose_city = {
- inactive: "recruit",
- prompt() {
- prompt("Choose city to re-enter troops.")
- for (let s of all_power_re_entry_cities[game.power])
- if (!has_enemy_piece(s))
- gen_action_space(s)
- },
- space(s) {
- push_undo()
- game.recruit.re_enter = s
- game.state = "recruit"
- },
-}
-
-function has_re_entry_space(p) {
- let can_re_enter_at = is_general(p) ? can_re_enter_general : can_re_enter_supply_train
- if (is_map_space(game.recruit.re_enter))
- return can_re_enter_at(game.recruit.re_enter)
- for (let s of all_power_depots[game.power])
- if (can_re_enter_at(s))
+function has_re_entry_space_for_general() {
+ for (let s of all_home_country_major_fortresses[game.power])
+ if (can_re_enter_general_at_city(s))
return true
return false
}
-function can_recruit_anything_in_theory() {
- let unused_everywhere = max_power_troops(game.power) - count_used_troops()
- return unused_everywhere > 0 || count_eliminated_trains() > 0
+function can_re_enter_general_at_city(from, to) {
+ if (is_enemy_controlled_fortress(to))
+ return false
+ if (has_friendly_supply_train(to))
+ return false
+ if (has_non_cooperative_general(to))
+ return false
+ if (count_generals(to) >= 2)
+ return false
+ return true
}
function can_recruit_anything() {
- let unused_everywhere = max_power_troops(game.power) - count_used_troops()
- let elim_trains = count_eliminated_trains()
- let elim_generals = count_eliminated_generals()
- let unused_on_map = count_unused_troops_on_map()
- // can reinforce on-map generals
- if (unused_everywhere > 0 && unused_on_map > 0)
- return true
- // can re-enter eliminated generals
- if (unused_everywhere > 0 && elim_generals > 0 && has_re_entry_space())
- return true
- // can re-enter eliminated supply trains
- if (elim_trains > 0 && has_re_entry_space())
- return true
+ for (let p of all_power_generals[game.power]) {
+ // can re-enter generals
+ if (game.pos[p] === ELIMINATED && has_re_entry_space_for_general())
+ return true
+ // can recruit troops?
+ if (is_piece_on_map(p) && game.troops[p] < 8)
+ return true
+ }
return false
}
states.recruit = {
inactive: "recruit",
prompt() {
- let cost = troop_cost()
- let n_troops = count_used_troops()
- let av_troops = max_power_troops(game.power) - n_troops
- let av_trains = count_eliminated_trains()
- let possible = can_recruit_anything()
+ let av_general = 0
+ let av_troops = 0
+ for (let p of all_power_generals[game.power]) {
+ if (is_piece_on_map(p))
+ av_troops += 8 - game.troops[p]
+ else if (game.pos[p] === ELIMINATED && has_re_entry_space_for_general()) {
+ av_general += 1
+ av_troops += 8
+ }
+ }
let str
- if (av_trains > 0 && av_troops > 0)
- str = `Recruit supply trains and up to ${av_troops} troops for ${cost} each`
+ if (av_general > 0 && av_troops > 0)
+ str = `Re-enter generals and recruit up to ${av_troops} troops for 4 each`
else if (av_troops > 0)
- str = `Recruit up to ${av_troops} troops for ${cost} each`
- else if (av_trains > 0)
- str = `Recruit supply trains for ${cost} each`
+ str = `Recruit up to ${av_troops} troops for 4 each`
else
str = "Nothing to recruit"
@@ -2378,31 +2369,23 @@ states.recruit = {
view.draw = game.recruit.pool
- if (possible && paid / cost < av_troops + av_trains) {
- for (let c of game.hand[game.power])
- gen_action_card(c)
- }
+ if (av_troops > 0) {
+ if (paid / 4 < av_troops) {
+ for (let c of game.hand[game.power])
+ gen_action_card(c)
+ }
- if (paid >= cost) {
- if (av_troops > 0) {
+ if (paid >= 4) {
for (let p of all_power_generals[game.power]) {
- if (game.troops[p] > 0 && game.troops[p] < 8) {
- let s = game.pos[p]
- gen_action_piece(s)
- }
- else if (game.pos[p] === ELIMINATED && has_re_entry_space(p))
+ if (game.troops[p] > 0 && game.troops[p] < 8)
gen_action_piece(p)
- }
- }
- if (av_trains > 0) {
- for (let p of all_power_trains[game.power]) {
- if (game.pos[p] === ELIMINATED && has_re_entry_space(p))
+ else if (game.pos[p] === ELIMINATED && has_re_entry_space_for_general())
gen_action_piece(p)
}
}
}
- if (paid < cost || !possible)
+ if (paid < 4 || av_troops === 0)
view.actions.end_recruit = 1
},
card(c) {
@@ -2413,14 +2396,14 @@ states.recruit = {
piece(p) {
push_undo()
- spend_recruit_cost()
+ spend_card_value(game.recruit.pool, game.recruit.used, 4)
if (game.pos[p] === ELIMINATED) {
game.selected = p
- game.state = "re_enter"
+ game.state = "re_enter_general_where"
} else {
game.recruit.troops += 1
- add_one_troop(p)
+ game.troops[p] += 1
}
},
end_recruit() {
@@ -2429,63 +2412,63 @@ states.recruit = {
},
}
-function end_recruit() {
- if (game.recruit) {
- if (game.recruit.used.length > 0) {
- log_br()
- if (game.recruit.troops > 0)
- log("Recruited " + game.recruit.troops + " troops with " + game.recruit.used.map(format_card).join(", ") + ".")
- else
- log("Recruited with " + game.recruit.used.map(format_card).join(", ") + ".")
- map_for_each(game.recruit.pieces, (p,s) => {
- log("Re-entered P" + p + " to S" + s + ".")
- })
- if (game.recruit.troops)
- log(">" + game.recruit.troops + " troops")
- }
-
- // put back into hand unused cards
- for (let c of game.recruit.pool)
- set_add(game.hand[game.power], c)
-
- delete game.recruit
- }
-
- // MARIA: NOT goto_combat()
- // goto_combat()
-}
-
-states.re_enter = {
+states.re_enter_general_where = {
inactive: "recruit",
prompt() {
prompt("Re-enter " + format_selected() + ".")
view.selected = game.selected
-
- let p = game.selected
- let can_re_enter_at = is_general(p) ? can_re_enter_general : can_re_enter_supply_train
-
- if (is_map_space(game.recruit.re_enter)) {
- if (can_re_enter_at(game.recruit.re_enter))
- gen_action_space(game.recruit.re_enter)
- } else {
- for (let s of all_power_depots[game.power])
- if (can_re_enter_at(s))
- gen_action_space(s)
- }
+ for (let s of all_home_country_major_fortresses[game.power])
+ if (can_re_enter_general_at_city(s))
+ gen_action_space(s)
},
space(s) {
let p = game.selected
game.pos[p] = s
- map_set(game.recruit.pieces, p, s)
+ set_add(game.recruit.pieces, p)
if (is_general(p)) {
game.recruit.troops += 1
game.troops[p] = 1
}
game.selected = -1
game.state = "recruit"
+
+ // remove hussars
+ for (let p of all_hussars) {
+ log("P" + p + " removed.")
+ game.pos[p] = ELIMINATED
+ }
+
+ // remove enemy supply trains
+ for (let p of all_enemy_trains(game.power)) {
+ log("P" + p + " eliminated.")
+ game.pos[p] = ELIMINATED
+ }
},
}
+function end_recruit() {
+ if (game.recruit) {
+ if (game.recruit.used.length > 0) {
+ log_br()
+ log("Recruited " + game.recruit.troops + " troops with " + game.recruit.used.map(format_card).join(", ") + ".")
+ for (let p of game.recruit.pieces)
+ log("Re-entered P" + p + " at S" + game.pos[p] + ".")
+ } else {
+ log("Recruited nothing.")
+ }
+
+ // put back into hand unused cards
+ for (let c of game.recruit.pool)
+ set_add(game.hand[game.power], c)
+
+ delete game.recruit
+ } else {
+ log("Recruited nothing.")
+ }
+
+ end_winter_stage()
+}
+
/* COMBAT (CHOOSE TARGETS) */
function goto_combat() {