diff options
Diffstat (limited to 'rules.js')
-rw-r--r-- | rules.js | 463 |
1 files changed, 298 insertions, 165 deletions
@@ -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 } |