summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2024-10-24 21:39:07 +0200
committerTor Andersson <tor@ccxvii.net>2024-10-24 23:31:55 +0200
commit9f2f99a05be1bf496dd398f1337e3e9f1767c77e (patch)
treedd8f96af3ed1c50feb53b335f68203b42881e2bf
parent0d300b3070f51910af34877de9de97d4fa163703 (diff)
downloadmaria-9f2f99a05be1bf496dd398f1337e3e9f1767c77e.tar.gz
Use sequence-of-play table for alternating moves etc.
-rw-r--r--play.js25
-rw-r--r--rules.js463
2 files changed, 322 insertions, 166 deletions
diff --git a/play.js b/play.js
index f3f5630..725f1ad 100644
--- a/play.js
+++ b/play.js
@@ -64,7 +64,12 @@ const F_EMPEROR_AUSTRIA = 2
const F_ITALY_FRANCE = 4
const F_ITALY_AUSTRIA = 8
const F_SILESIA_ANNEXED = 16
-const F_IMPERIAL_ELECTION = 32
+const F_IMPERIAL_ELECTION = 32 // imperial election card revealed!
+const F_WAR_OF_JENKINS_EAR = 64 // -1 France TC for one turn
+const F_SAXONY_TC_ONCE = 128 // only draw TCs once per turn
+const F_FRANCE_REDUCED = 256
+const F_PRUSSIA_NEUTRAL = 512
+const F_MOVE_FLANDERS = 1024
const SPADES = 0
const CLUBS = 1
@@ -76,6 +81,11 @@ const IMPERIAL_ELECTION = 24
const ELIMINATED = data.cities.name.length
+const ARENBERG = 17
+const SAXONY_GENERAL = 19
+const SAXONY_TRAIN = 29
+const PRUSSIAN_TRAIN_2 = 23
+
const all_powers = [ 0, 1, 2, 3, 4, 5 ]
const all_major_powers = [ 0, 1, 2, 3 ]
@@ -97,6 +107,15 @@ const all_power_trains = [
[ 29 ],
]
+const all_power_pieces = [
+ [ ...all_power_generals[0], ...all_power_trains[0] ],
+ [ ...all_power_generals[1], ...all_power_trains[1] ],
+ [ ...all_power_generals[2], ...all_power_trains[2] ],
+ [ ...all_power_generals[3], ...all_power_trains[3] ],
+ [ ...all_power_generals[4], ...all_power_trains[4] ],
+ [ ...all_power_generals[5], ...all_power_trains[5] ],
+]
+
const last_piece = 29
const all_hussars = [ 30, 31 ]
@@ -1167,6 +1186,10 @@ function on_update() {
banner += `<span class="subsidy ${power_class[other]}">S</span>`
})
}
+ if (pow === P_PRUSSIA && (view.flags & F_PRUSSIA_NEUTRAL))
+ banner += " \u2014 Neutral"
+ if (pow === P_FRANCE && (view.flags & F_WAR_OF_JENKINS_EAR))
+ banner += " \u2014 receives 1 TC less"
ui.power_header[pow].innerHTML = banner
ui.hand[pow].replaceChildren()
diff --git a/rules.js b/rules.js
index 18ed12c..8c2dc7c 100644
--- a/rules.js
+++ b/rules.js
@@ -1,9 +1,6 @@
"use strict"
-// TODO: subsidy contracts
// TODO: remove hussars when retreating across them
-// TODO: austria + pragmatic action stage intermixing on flanders
-// TODO: austria + pragmatic stack if both agree
/*
@@ -11,21 +8,10 @@ OPTIMIZE: fewer/smarter/smaller lists, smarter power control checks
OPTIMIZE: range checks instead of set checks
POLISH: check inactive prompts
-POLISH: check push_undo/clear_undo for political phase and political changes
-POLISH: add undo steps and pauses for saxony neutral return pieces
+POLISH: check push_undo/clear_undo for all
UI: show who controls which power in player list
-UI: show TC modifiers
-UI: show subsidy contracts
-
-when are subsidies given?
- when drawn
-when are subsidies created?
- example france subsidy to prussia
- at france's stage?
- at prussia's stage?
-
- what happens to subsidy when minor's fortress is enemy occupied
+UI: show TC modifiers in banner
*/
@@ -78,6 +64,7 @@ const F_WAR_OF_JENKINS_EAR = 64 // -1 France TC for one turn
const F_SAXONY_TC_ONCE = 128 // only draw TCs once per turn
const F_FRANCE_REDUCED = 256
const F_PRUSSIA_NEUTRAL = 512
+const F_MOVE_FLANDERS = 1024
const SPADES = 0
const CLUBS = 1
@@ -1090,55 +1077,6 @@ const TURN_NAME = [
"Winter 1744",
]
-const POWER_FROM_ACTION_STAGE = [
- P_FRANCE, // and bavaria
- P_PRUSSIA, // and saxony
- P_PRAGMATIC, // interleave with austria moves on flanders map
- P_AUSTRIA, // and pragmatic army -- interleave with pragmatic moves on flanders map
-]
-
-const title_from_action_stage = [
- "=" + P_FRANCE + "France and Bavaria",
- "=" + P_PRUSSIA + "Prussia and Saxony",
- "=" + P_PRAGMATIC + "Pragmatic Army",
- "=" + P_AUSTRIA + "Austria",
-]
-
-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 = [
- "=" + P_FRANCE,
- "=" + P_BAVARIA,
- "=" + P_PRUSSIA,
- "=" + P_SAXONY,
- "=" + P_PRAGMATIC,
- "=" + P_AUSTRIA,
-]
-
-function set_active_to_current_winter_stage() {
- set_active_to_power(POWER_FROM_WINTER_STAGE[game.stage])
-}
-
-function goto_end_turn() {
- if (game.flags & F_IMPERIAL_ELECTION) {
- goto_imperial_election()
- return
- }
-
- goto_start_turn()
-}
-
function goto_start_turn() {
game.turn += 1
@@ -1157,50 +1095,123 @@ function goto_start_turn() {
}
}
-function goto_action_stage() {
- set_active_to_current_action_stage()
-
- clear_undo()
-
- log(title_from_action_stage[game.stage])
-
- goto_tactical_cards()
+function goto_end_turn() {
+ if (game.flags & F_IMPERIAL_ELECTION) {
+ goto_imperial_election()
+ return
+ }
+ goto_start_turn()
}
-function end_action_stage() {
- clear_undo()
+const sequence_of_play = [
+ { power: P_AUSTRIA, action: goto_place_hussars },
+
+ { power: P_FRANCE, action: start_action_stage },
+ { power: P_FRANCE, action: goto_tactical_cards },
+ { power: P_BAVARIA, action: goto_tactical_cards },
+ { power: P_FRANCE, action: init_movement },
+ { power: P_FRANCE, action: goto_movement_global },
+ { power: P_FRANCE, action: end_movement },
+ { power: P_FRANCE, action: goto_combat },
+ { power: P_FRANCE, action: end_action_stage },
+ { power: P_FRANCE, action: goto_france_reduces_military_objectives },
+
+ { power: P_PRUSSIA, action: start_action_stage },
+ { power: P_PRUSSIA, action: goto_tactical_cards },
+ {
+ power: P_SAXONY,
+ action() {
+ if (is_saxony_prussian())
+ goto_tactical_cards()
+ else
+ next_sequence_of_play()
+ },
+ },
+ { power: P_PRUSSIA, action: init_movement },
+ { power: P_PRUSSIA, action: goto_movement_global },
+ { power: P_PRUSSIA, action: end_movement },
+ { power: P_PRUSSIA, action: goto_combat },
+ { power: P_PRUSSIA, action: end_action_stage },
+ { power: P_PRUSSIA, action: goto_prussia_ends_neutrality },
+ { power: P_PRUSSIA, action: goto_prussia_annexes_silesia },
+
+ { power: P_AUSTRIA, action: start_action_stage },
+ { power: P_AUSTRIA, action: goto_tactical_cards },
+ {
+ power: P_SAXONY,
+ action() {
+ if (is_saxony_prussian())
+ next_sequence_of_play()
+ else
+ goto_tactical_cards()
+ },
+ },
+ { power: P_PRAGMATIC, action: goto_tactical_cards },
+
+ // alternate moves on flanders starting with pragmatic army
+ { power: P_PRAGMATIC, action: init_movement },
+ { power: P_PRAGMATIC, action: goto_movement_flanders },
+ { power: P_AUSTRIA, action: goto_movement_flanders },
+ { power: P_PRAGMATIC, action: goto_movement_flanders },
+ { power: P_AUSTRIA, action: goto_movement_flanders },
+ { power: P_PRAGMATIC, action: goto_movement_flanders },
+ { power: P_AUSTRIA, action: goto_movement_flanders },
+ { power: P_PRAGMATIC, action: goto_movement_flanders },
+ { power: P_AUSTRIA, action: goto_movement_bohemia },
+ { power: P_AUSTRIA, action: end_movement },
+
+ { power: P_AUSTRIA, action: goto_combat },
+ { power: P_PRAGMATIC, action: goto_combat },
+ { power: P_AUSTRIA, action: end_action_stage },
+
+ { power: P_FRANCE, action: goto_end_turn },
+]
- set_active_to_current_action_stage()
+function start_sequence_of_play() {
+ game.stage = -1
+ next_sequence_of_play()
+}
- if (check_instant_victory())
- return
+function current_sequence_of_play() {
+ return sequence_of_play[game.stage]
+}
- if (game.power === P_PRUSSIA)
- game.flags &= ~F_PRUSSIA_NEUTRAL
+function set_active_to_current_sequence_of_play() {
+ set_active_to_power(current_sequence_of_play().power)
+}
- if (game.power === P_PRUSSIA && has_prussia_conquered_silesia()) {
- game.state = "offer_peace"
- return
- }
+function next_sequence_of_play() {
+ clear_undo()
+ ++game.stage
+ let row = current_sequence_of_play()
+ set_active_to_power(row.power)
+ row.action()
+}
- if (
- game.power === P_FRANCE &&
- !(game.flags & F_FRANCE_REDUCED) &&
- count_french_vp_markers_in_core_austria() > 0 &&
- france_has_no_generals_in_core_austria()
- ) {
- game.state = "france_reduces_military_objectives"
- return
+function start_action_stage() {
+ switch (game.power) {
+ case P_FRANCE:
+ log("=0 France and Bavaria")
+ break
+ case P_PRUSSIA:
+ if (is_saxony_prussian())
+ log("=1 Prussia and Saxony")
+ else
+ log("=1 Prussia")
+ break
+ case P_AUSTRIA:
+ if (is_saxony_prussian())
+ log("=2 Austria and Pragmatic Army")
+ else
+ log("=2 Austria, Pragmatic Army, and Saxony")
+ break
}
-
- end_action_stage_2()
+ next_sequence_of_play()
}
-function end_action_stage_2() {
- if (++game.stage === 4)
- goto_end_turn()
- else
- goto_action_stage()
+function end_action_stage() {
+ if (!check_instant_victory())
+ next_sequence_of_play()
}
/* AUSTRIA PLACES ITS HUSSARS */
@@ -1216,8 +1227,7 @@ function end_place_hussars() {
for (let p of all_hussars)
log("Hussar at S" + game.pos[p] + ".")
- game.stage = 0
- goto_action_stage()
+ next_sequence_of_play()
}
states.place_hussars = {
@@ -1428,10 +1438,13 @@ function give_subsidy(other) {
}
function goto_tactical_cards() {
- // TODO: pause to decide subsidy (france/bavaria)
-
- if (game.power === P_SAXONY)
+ if (game.power === P_SAXONY) {
+ if (game.flags & F_SAXONY_TC_ONCE) {
+ next_sequence_of_play()
+ return
+ }
game.flags |= F_SAXONY_TC_ONCE
+ }
game.draw = []
@@ -1488,24 +1501,7 @@ function end_tactical_cards() {
set_add(game.hand1[game.power], c)
delete game.draw
- // draw minor power's cards after major power
- let minor = coop_minor_power(game.power)
- if (minor === P_SAXONY && (game.flags & F_SAXONY_TC_ONCE))
- minor = game.power
- if (minor !== game.power) {
- set_active_to_power(minor)
- goto_tactical_cards()
- return
- }
-
- // back to major power after minor power
- let major = coop_major_power(game.power)
- if (major !== game.power)
- set_active_to_power(major)
-
- // TODO: draw austria and pragmatic cards at the same time
-
- goto_supply()
+ next_sequence_of_play()
}
/* PAYMENT */
@@ -1679,7 +1675,7 @@ function goto_supply() {
}
function resume_supply() {
- set_active_to_current_action_stage()
+ set_active_to_current_sequence_of_play()
if (game.supply.hussars.length > 0)
goto_supply_hussars()
else if (game.supply.restore.length > 0)
@@ -1857,7 +1853,16 @@ states.supply_done = {
function end_supply() {
delete game.supply
- goto_movement()
+ // NOTE: Austria tactical card and supply steps after pragmatic
+ if (game.power === P_AUSTRIA) {
+ set_active_to_power(P_PRAGMATIC)
+ goto_tactical_cards()
+ return
+ }
+ if (game.power === P_PRAGMATIC)
+ set_active_to_power(P_AUSTRIA)
+
+ next_sequence_of_play()
}
/* TRANSFER TROOPS */
@@ -1904,16 +1909,70 @@ function give_troops(total) {
/* MOVEMENT */
-function goto_movement() {
- set_active_to_current_action_stage()
-
- game.state = "movement"
+function init_movement() {
set_clear(game.moved)
log_br()
game.move_re_entered = 0
game.move_conq = []
+
+ next_sequence_of_play()
+}
+
+function has_unmoved_piece_on_flanders_map(pow) {
+ for (let p of all_power_pieces[pow])
+ if (is_flanders_space(game.pos[p]) && !set_has(game.moved, p))
+ return true
+ return false
+}
+
+function has_unmoved_piece_on_bohemia_map(pow) {
+ for (let p of all_power_pieces[pow])
+ if (is_bohemia_space(game.pos[p]) && !set_has(game.moved, p))
+ return true
+ return false
+}
+
+function goto_movement_global() {
+ game.state = "movement"
+}
+
+function goto_movement_flanders() {
+ game.flags |= F_MOVE_FLANDERS
+ game.state = "movement"
+ if (!has_unmoved_piece_on_flanders_map(game.power))
+ next_sequence_of_play()
+}
+
+function goto_movement_bohemia() {
+ game.flags &= ~F_MOVE_FLANDERS
+ game.state = "movement"
+ if (!has_unmoved_piece_on_bohemia_map(game.power))
+ next_sequence_of_play()
+}
+
+function goto_movement() {
+ game.state = "movement"
+}
+
+function resume_movement() {
+ set_active_to_power(coop_major_power(game.power))
+ game.selected = -1
+ if (game.flags & F_MOVE_FLANDERS) {
+ let row = sequence_of_play[game.stage+1]
+ if (row.action === goto_movement_flanders) {
+ let next = game.power === P_PRAGMATIC ? P_AUSTRIA : P_PRAGMATIC
+ if (!has_unmoved_piece_on_flanders_map(next))
+ next_sequence_of_play()
+ else
+ game.state = "movement_flanders_next"
+ } else {
+ game.state = "movement"
+ }
+ } else {
+ game.state = "movement"
+ }
}
function is_forbidden_neutral_space(pow, to) {
@@ -1960,6 +2019,20 @@ function can_general_move_anywhere(p) {
return false
}
+states.movement_flanders_next = {
+ inactive: "move",
+ prompt() {
+ prompt("Alternate moves on Flanders map.")
+ if (game.power === P_PRAGMATIC)
+ gen_action_power(P_AUSTRIA)
+ else
+ gen_action_power(P_PRAGMATIC)
+ },
+ power(_) {
+ next_sequence_of_play()
+ },
+}
+
states.movement = {
inactive: "move",
prompt() {
@@ -1968,6 +2041,8 @@ states.movement = {
for (let p of all_controlled_generals(game.power)) {
if (!set_has(game.moved, p) && is_piece_on_map(p)) {
+ if ((game.flags & F_MOVE_FLANDERS) && !is_flanders_space(game.pos[p]))
+ continue
if (can_general_move_anywhere(p)) {
gen_action_piece(p)
done_generals = false
@@ -1976,11 +2051,14 @@ states.movement = {
}
for (let p of all_controlled_trains(game.power)) {
- if ((game.move_re_entered & (1 << piece_power[p])) === 0)
+ 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 ((game.flags & F_MOVE_FLANDERS) && !is_flanders_space(game.pos[p]))
+ continue
if (can_train_move_anywhere(p)) {
gen_action_piece(p)
done_trains = false
@@ -2035,19 +2113,26 @@ states.movement = {
this.end_movement()
},
end_movement() {
- push_undo()
+ if (game.flags & F_MOVE_FLANDERS) {
+ for (let p of all_power_pieces[game.power])
+ if (is_flanders_space(game.pos[p]))
+ set_add(game.moved, p)
+ }
+ next_sequence_of_play()
+ },
+}
- if (game.moved.length === 0)
- log("Nothing moved.")
+function end_movement() {
+ if (game.moved.length === 0)
+ log("Nothing moved.")
- set_clear(game.moved)
+ set_clear(game.moved)
- log_conquest(game.move_conq)
- delete game.move_conq
- delete game.move_re_entered
+ log_conquest(game.move_conq)
+ delete game.move_conq
+ delete game.move_re_entered
- goto_combat()
- },
+ next_sequence_of_play()
}
function format_move(max) {
@@ -2411,13 +2496,10 @@ function end_move_piece() {
}
}
- if (supreme) {
+ if (supreme)
game.state = "move_supreme"
- } else {
- game.selected = -1
- game.state = "movement"
- set_active_to_current_action_stage()
- }
+ else
+ resume_movement()
}
states.move_supreme = {
@@ -2438,8 +2520,7 @@ states.move_supreme = {
if (game.pos[p] === here)
game.supreme &= ~(1<<p)
game.supreme |= (1<<p)
- game.selected = -1
- game.state = "movement"
+ resume_movement()
},
piece(p) {
this.supreme(p)
@@ -2606,12 +2687,34 @@ function end_re_enter_train() {
delete game.recruit
- set_active_to_current_action_stage()
+ set_active_to_power(coop_major_power(game.power))
game.state = "movement"
}
/* WINTER RECRUITMENT */
+const POWER_FROM_WINTER_STAGE = [
+ P_FRANCE,
+ P_BAVARIA,
+ P_PRUSSIA,
+ P_SAXONY,
+ P_PRAGMATIC,
+ P_AUSTRIA,
+]
+
+const title_from_winter_stage = [
+ "=" + P_FRANCE,
+ "=" + P_BAVARIA,
+ "=" + P_PRUSSIA,
+ "=" + P_SAXONY,
+ "=" + P_PRAGMATIC,
+ "=" + P_AUSTRIA,
+]
+
+function set_active_to_current_winter_stage() {
+ set_active_to_power(POWER_FROM_WINTER_STAGE[game.stage])
+}
+
function goto_winter_turn() {
// record winter scores
@@ -2946,7 +3049,7 @@ function goto_combat() {
function next_combat() {
clear_undo()
- set_active_to_current_action_stage()
+ set_active_to_current_sequence_of_play()
game.count = 0
delete game.attacker
delete game.defender
@@ -3421,7 +3524,7 @@ function search_retreat_possible_dfs(result, seen, here, range) {
if (is_illegal_cross_map_retreat(here, next))
continue
if (range === 1) {
- set_add(result, next)
+ map_set(result, next, seen.slice())
} else {
seen.push(next)
search_retreat_possible_dfs(result, seen, next, range - 1)
@@ -3441,33 +3544,40 @@ function search_retreat(loser, winner, range) {
let possible = search_retreat_possible(loser, range)
let max = 0
- for (let s of possible) {
+ map_for_each(possible, (s, _) => {
let d = map_get(distance, s, -1)
if (d > max)
max = d
- }
+ })
let result = []
- for (let s of possible)
+ map_for_each(possible, (s, path) => {
if (map_get(distance, s, -1) === max)
- result.push(s)
+ map_set(result, s, path)
+ })
return result
}
-// TODO: remove hussars when retreating across them
states.retreat = {
inactive: "retreat defeated general",
prompt() {
prompt("Retreat " + format_selected() + " " + Math.abs(game.count) + " cities.")
view.selected = game.selected
- for (let s of game.retreat)
+ map_for_each(game.retreat, (s, _) => {
gen_action_space(s)
+ })
},
space(to) {
push_undo()
+
log("Retreated to S" + to + ".")
+
+ console.log("RET", map_get(game.retreat, to, 0))
+ // TODO: use path to stomp hussars
+
for (let p of game.selected)
game.pos[p] = to
+
delete game.retreat
game.state = "retreat_done"
},
@@ -3550,7 +3660,7 @@ function goto_retroactive_conquest() {
map_clear(game.retro)
- end_action_stage()
+ next_sequence_of_play()
}
/* VICTORY */
@@ -3886,7 +3996,7 @@ function end_adjust_political_tracks() {
if (check_instant_victory())
return
- goto_place_hussars()
+ start_sequence_of_play()
}
/* POLITICAL CARDS */
@@ -4497,6 +4607,18 @@ function end_saxony_neutral() {
/* PRUSSIA ANNEXES SILESIA */
+function goto_prussia_ends_neutrality() {
+ game.flags &= ~F_PRUSSIA_NEUTRAL
+ next_sequence_of_play()
+}
+
+function goto_prussia_annexes_silesia() {
+ if (has_prussia_conquered_silesia())
+ game.state = "offer_peace"
+ else
+ next_sequence_of_play()
+}
+
function is_prussia_neutral() {
return !!(game.flags & F_PRUSSIA_NEUTRAL)
}
@@ -4528,7 +4650,7 @@ states.offer_peace = {
game.state = "accept_peace"
},
pass() {
- end_action_stage_2()
+ next_sequence_of_play()
},
}
@@ -4543,7 +4665,7 @@ states.accept_peace = {
goto_annex_silesia()
},
refuse() {
- end_action_stage_2()
+ next_sequence_of_play()
},
}
@@ -4714,7 +4836,7 @@ states.silesia_enter_prussian_train = {
let s = ELIMINATED
log(">P" + p + " to S" + s)
game.pos[p] = s
- end_action_stage_2()
+ next_sequence_of_play()
},
}
@@ -4726,12 +4848,23 @@ states.silesia_done = {
},
next() {
clear_undo()
- end_action_stage_2()
+ next_sequence_of_play()
}
}
/* FRANCE REDUCES MILITARY OBJECTIVES */
+function goto_france_reduces_military_objectives() {
+ if (
+ !(game.flags & F_FRANCE_REDUCED) &&
+ count_french_vp_markers_in_core_austria() > 0 &&
+ france_has_no_generals_in_core_austria()
+ )
+ game.state = "france_reduces_military_objectives"
+ else
+ next_sequence_of_play()
+}
+
function count_french_vp_markers_in_core_austria() {
let n = 0
map_for_each(game.victory, (s, pow) => {
@@ -4782,10 +4915,10 @@ states.france_reduces_military_objectives = {
n = (n + 1) >> 1
log("France set aside " + n + " victory markers.")
game.vp[SET_ASIDE_FRANCE] = n
- end_action_stage_2()
+ next_sequence_of_play()
},
pass() {
- end_action_stage_2()
+ next_sequence_of_play()
},
}
@@ -5430,7 +5563,7 @@ function mask_hand1(player) {
if (player_from_power(pow) === player)
view_hand[pow] = game.hand1[pow]
else
- view_hand[pow] = game.hand1[pow].map(c => c & ~127)
+ view_hand[pow] = game.hand1[pow].concat(game.hand2[pow]).map(c => c & ~127)
}
return view_hand
}
@@ -5441,7 +5574,7 @@ function mask_hand2(player) {
if (player_from_power(pow) === player)
view_hand[pow] = game.hand2[pow]
else
- view_hand[pow] = game.hand2[pow].map(c => c & ~127)
+ view_hand[pow] = []
}
return view_hand
}