diff options
author | Tor Andersson <tor@ccxvii.net> | 2024-10-22 22:19:15 +0200 |
---|---|---|
committer | Tor Andersson <tor@ccxvii.net> | 2024-10-22 23:37:57 +0200 |
commit | 23bd83c80d3e0a2d7dd18f76ae4039effd7ce448 (patch) | |
tree | fa5e2beeacbf025c8250cfe77059c7add6048363 | |
parent | 78754928c9b1c55bdffe40ff33342c7329f3ce77 (diff) | |
download | maria-23bd83c80d3e0a2d7dd18f76ae4039effd7ce448.tar.gz |
political shifts and troops+tcs
-rw-r--r-- | cards.js | 264 | ||||
-rw-r--r-- | play.css | 27 | ||||
-rw-r--r-- | play.html | 22 | ||||
-rw-r--r-- | play.js | 33 | ||||
-rw-r--r-- | rules.js | 327 |
5 files changed, 610 insertions, 63 deletions
diff --git a/cards.js b/cards.js new file mode 100644 index 0000000..1d029cb --- /dev/null +++ b/cards.js @@ -0,0 +1,264 @@ +"use strict" + +var political_cards = [ { title: null, powers: [], flavor: null, effects: [] } ] + +;(function () { + +const P_FRANCE = 0 +const P_PRUSSIA = 1 +const P_PRAGMATIC = 2 +const P_AUSTRIA = 3 + +function def_pol_card(year, title, powers, flavor, effects) { + powers.sort((a,b)=>a-b) + political_cards.push({ year, title, powers, flavor, effects }) +} + +/* 1741 */ + +def_pol_card(1741, + "Swedish-Russian War", + [ P_PRUSSIA, P_FRANCE, P_AUSTRIA ], + "FLAVOR", + [ + "Russia -1 or +1", + "Italy -1", + ] +) + +def_pol_card(1741, + "Military Aid?", + [ P_PRUSSIA, P_FRANCE, P_AUSTRIA ], + "FLAVOR", + [ + "Russia +1", + ] +) + +def_pol_card(1741, + "Naples", + [ P_FRANCE, P_AUSTRIA ], + "FLAVOR", + [ + "Italy -1", + ] +) + +def_pol_card(1741, + "Charles Emmanuel", + [ P_PRUSSIA, P_FRANCE, P_AUSTRIA ], + "FLAVOR", + [ + "Saxony -1 if allied with Prussia", + "Italy +1", + ] +) + +def_pol_card(1741, + "War of Jenkins' Ear", + [ P_PRAGMATIC, P_FRANCE, P_AUSTRIA ], + "FLAVOR", + [ + "France -1 TC this turn", + "Italy -1 or +1", + ] +) + +def_pol_card(1741, + "Denmark", + [ P_PRAGMATIC, P_FRANCE, P_AUSTRIA ], + "FLAVOR", + [ + "Pragmatic +1 TC and +3 troops", + ] +) + +/* 1742 */ + +def_pol_card(1742, + "Coup d'État", + [ P_PRUSSIA, P_FRANCE, P_AUSTRIA ], + "FLAVOR", + [ + "Russia -1", + ] +) + +def_pol_card(1742, + "Sweden Surrenders", + [ P_PRUSSIA, P_FRANCE, P_AUSTRIA ], + "FLAVOR", + [ + "Saxony +1", + "Russia -1", + ] +) + +def_pol_card(1742, + "Spain", + [ P_PRUSSIA, P_FRANCE, P_AUSTRIA ], + "FLAVOR", + [ + "Russia -1 or +1", + "Italy -2", + ] +) + +def_pol_card(1742, + "Sardinia-Piedmont", + [ P_PRAGMATIC, P_FRANCE, P_AUSTRIA ], + "FLAVOR", + [ + "Italy +2", + ] +) + +def_pol_card(1742, + "Hessian Mercenaries", + [ P_PRAGMATIC, P_FRANCE, P_AUSTRIA ], + "FLAVOR", + [ + "Your major power +3 troops", + ] +) + +def_pol_card(1742, + "Electoral Palatinate", + [ P_PRAGMATIC, P_FRANCE, P_AUSTRIA ], + "FLAVOR", + [ + "Mannheim to French control", + "France +1 TC and +3 troops", + ] +) + +/* 1743 */ + +def_pol_card(1743, + "Lopukhna Conspiracy", + [ P_PRUSSIA, P_FRANCE, P_AUSTRIA ], + "FLAVOR", + [ + "Russia -2", + ] +) + +def_pol_card(1743, + "Alexei Bestushev", + [ P_PRUSSIA, P_FRANCE, P_AUSTRIA ], + "FLAVOR", + [ + "Saxony +1", + "Russia -1 or +1", + ] +) + +def_pol_card(1743, + "Commodore Martin", + [ P_PRAGMATIC, P_FRANCE, P_AUSTRIA ], + "FLAVOR", + [ + "Italy -1 or +2", + ] +) + +def_pol_card(1743, + "France", + [ P_PRAGMATIC, P_FRANCE, P_AUSTRIA ], + "FLAVOR", + [ + "Italy -1 or +1", + ] +) + +def_pol_card(1743, + "Frankfurt Union", + [ P_PRAGMATIC, P_FRANCE, P_AUSTRIA ], + "FLAVOR", + [ + "France or Bavaria +2 troops", + ] +) + +def_pol_card(1743, + "Quadruple Alliance", + [ P_PRUSSIA, P_AUSTRIA ], + "FLAVOR", + [ + "Saxony +4", + "Saxony +2 troops", + ] +) + +/* 1744 */ + +def_pol_card(1744, + "Catherine", + [ P_PRUSSIA, P_FRANCE, P_AUSTRIA ], + "FLAVOR", + [ + "Russia -1 or +1", + ] +) + +def_pol_card(1744, + "Russia", + [ P_PRUSSIA, P_AUSTRIA ], + "FLAVOR", + [ + "Russia -1 or +2", + ] +) + +def_pol_card(1744, + "Genoa", + [ P_FRANCE, P_AUSTRIA ], + "FLAVOR", + [ + "Saxony +1", + "Italy -2 or +1", + ] +) + +def_pol_card(1744, + "Separate Peace", + [ P_PRAGMATIC, P_FRANCE, P_AUSTRIA ], + "FLAVOR", + [ + "Italy -1 or +2", + ] +) + +def_pol_card(1744, + "Jacobite Rising", + [ P_PRAGMATIC, P_FRANCE ], + "FLAVOR", + [ + "Pragmatic general to England", + // "Pragmatic -1 TC until game end", + ] +) + +def_pol_card(1744, + "Carnatic War", + [ P_PRAGMATIC, P_FRANCE, P_AUSTRIA ], + "FLAVOR", + [ + "Pragmatic or France +1 TC and +2 troops", + ] +) + +/* IMPERIAL ELECTION */ + +def_pol_card(1742, + "Imperial Election", + [], + null, + null +) + +})() + +/* EXPORT */ + +if (typeof module === 'object') module.exports = political_cards @@ -14,6 +14,15 @@ white-space: wrap; } +button span.suit { + font-family: "Suit Symbols", var(--font-widget); + line-height: 1; +} + +button { + min-width: 48px; +} + body { background-color: slategray; } @@ -56,10 +65,10 @@ body { top: 325px; } -#display_prussia { left: 42px; } -#display_france { left: 246px; } -#display_pragmatic { left: 449px; } -#display_austria { left: 653px; } +#placed_prussia { left: 42px; } +#placed_france { left: 246px; } +#placed_pragmatic { left: 449px; } +#placed_austria { left: 653px; } header { background-color: hsl(44, 35%, 80%); color: #000c; } header.your_turn { color: black; } @@ -460,6 +469,16 @@ body.shift span.value.deck_4 { background-color: #f002 } border-color: white; } +.card.polcard.action { + box-shadow: 0 0 0 3px white; + border-color: black; +} + +.card.selected { + box-shadow: 0 0 0 3px yellow; + border-color: black; +} + @media (hover: hover) { .card.tc.action:hover { margin-top: -10px; @@ -100,25 +100,13 @@ <div id="political_body"> <div id="political_display"> <div id="pol_tracks"></div> - <div class="pc_pile card_pile" id="display_prussia"></div> - <div class="pc_pile card_pile" id="display_france"></div> - <div class="pc_pile card_pile" id="display_pragmatic"></div> - <div class="pc_pile card_pile" id="display_austria"></div> + <div class="pc_pile card_pile" id="placed_prussia"></div> + <div class="pc_pile card_pile" id="placed_france"></div> + <div class="pc_pile card_pile" id="placed_pragmatic"></div> + <div class="pc_pile card_pile" id="placed_austria"></div> </div> <div style="display:flex;flex-wrap:wrap;gap:18px;margin:18px;"> - <div id="pc_deck" class="card_pile"> - <div class="pile card polcard reverse deck_4"></div> - <div class="pile card polcard reverse deck_3"></div> - <div class="pile card polcard reverse deck_2"></div> - <div class="pile card polcard reverse deck_1"></div> - <!-- - <div id="pcp4" class="card polcard pile reverse deck_4"></div> - <div id="pcp3" class="card polcard pile reverse deck_3"></div> - <div id="pcp2" class="card polcard pile reverse deck_2"></div> - <div id="pcp1" class="card polcard pile reverse deck_1"></div> - <div id="pcp_imp" class="card polcard c25"></div> - --> - </div> + <div id="pc_deck" class="card_pile"></div> <div id="pc_show" style="display:flex;flex-wrap:wrap;gap:18px;"> </div> </div> @@ -363,11 +363,11 @@ const ui = { pol_tracks: document.getElementById("pol_tracks"), pc_deck: document.getElementById("pc_deck"), pc_show: document.getElementById("pc_show"), - pc_display: [ - document.getElementById("display_france"), - document.getElementById("display_prussia"), - document.getElementById("display_pragmatic"), - document.getElementById("display_austria"), + pc_placed: [ + document.getElementById("placed_france"), + document.getElementById("placed_prussia"), + document.getElementById("placed_pragmatic"), + document.getElementById("placed_austria"), ], discard: [ document.getElementById("discard_1"), @@ -1094,12 +1094,15 @@ function on_update() { if (view.flags & F_IMPERIAL_ELECTION) ui.pc_deck.appendChild(ui.political[25]) + for (let pc = 1; pc <= 25; ++pc) + ui.political[pc].classList.toggle("selected", pc === view.pc) + for (let pow of all_major_powers) { - ui.pc_display[pow].replaceChildren() + ui.pc_placed[pow].replaceChildren() for (let tc of view.saved[pow]) - ui.pc_display[pow].appendChild(show_tc(tc)) - if (view.display && view.display[pow] >= 0) - ui.pc_display[pow].appendChild(show_tc(view.display[pow])) + ui.pc_placed[pow].appendChild(show_tc(tc)) + if (view.placed && view.placed[pow] >= 0) + ui.pc_placed[pow].appendChild(show_tc(view.placed[pow])) } ui.pc_show.replaceChildren() @@ -1171,6 +1174,17 @@ function on_update() { layout_combat_marker() } + action_button_with_argument("suit", SPADES, colorize_S) + action_button_with_argument("suit", CLUBS, colorize_C) + action_button_with_argument("suit", HEARTS, colorize_H) + action_button_with_argument("suit", DIAMONDS, colorize_D) + + action_button_with_argument("shift", -2, "\u2b05\u2b05") + action_button_with_argument("shift", -1, "\u2b05") + action_button_with_argument("shift", +1, "\u27a1") + action_button_with_argument("shift", +2, "\u27a1\u27a1") + action_button_with_argument("shift", +4, "\u27a1\u27a1\u27a1\u27a1") + for (let v = 16; v >= 0; --v) action_button_with_argument("value", v, v) @@ -1193,6 +1207,7 @@ function on_update() { action_button("next", "Next") action_button("done", "Done") + action_button("end_political_card", "End political card") action_button("end_cards", "End card draw") action_button("end_setup", "End setup") action_button("end_recruit", "End recruit") @@ -91,6 +91,7 @@ const F_ITALY_FRANCE = 4 const F_ITALY_AUSTRIA = 8 const F_SILESIA_ANNEXED = 16 const F_IMPERIAL_ELECTION = 32 // imperial election card revealed! +const F_WAR_OF_JENKINS_EAR = 64 const SPADES = 0 const CLUBS = 1 @@ -741,6 +742,21 @@ function tc_per_turn() { ++n } + // Jacobite Rising + if (game.power === P_PRAGMATIC) { + for (let p of all_power_generals[P_PRAGMATIC]) + if (game.pos[p] === ENGLAND) + --n + } + + // War of Jenkins' Ear + if (game.power === P_FRANCE) { + if (game.flags & F_WAR_OF_JENKINS_EAR) { + game.flags &= ~F_WAR_OF_JENKINS_EAR + --n + } + } + return n } @@ -2451,6 +2467,27 @@ states.recruit = { }, } +function enter_general_at(p, s) { + game.pos[p] = s + game.troops[p] = 1 + + // remove hussars + for (let p of all_hussars) { + if (game.pos[p] === s) { + log("P" + p + " removed.") + game.pos[p] = ELIMINATED + } + } + + // remove enemy supply trains + for (let p of all_enemy_trains(game.power)) { + if (game.pos[p] === s) { + log("P" + p + " eliminated.") + game.pos[p] = ELIMINATED + } + } +} + states.re_enter_general_where = { inactive: "recruit", prompt() { @@ -2462,26 +2499,32 @@ states.re_enter_general_where = { }, space(s) { let p = game.selected - game.pos[p] = s set_add(game.recruit.pieces, p) - if (is_general(p)) { - game.recruit.troops += 1 - game.troops[p] = 1 - } + enter_general_at(p, s) + game.recruit.troops += 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 - } +states.re_enter_general_from_political_card = { + inactive: "execute political card", + prompt() { + prompt("Re-enter " + format_selected() + ".") + view.selected = game.selected + 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 + log("P" + p + " re-entered at S" + s + ".") + enter_general_at(p, s) + game.selected = -1 + if (--game.count > 0) + game.state = "political_troops_place" + else + next_execute_political_card() }, } @@ -3239,7 +3282,7 @@ const INFLUENCE_ORDER = [ function goto_politics() { game.political = [] - game.display = [ 0, 0, 0, 0 ] + game.placed = [ 0, 0, 0, 0 ] game.stage = 0 log("Reveal") @@ -3277,9 +3320,9 @@ states.determine_trump_suit = { view.actions.suit = [ 0, 1, 2, 3 ] }, suit(s) { + game.trump = s log(power_name[game.power] + " chose " + suit_name[game.trump] + " as trump.") log_br() - game.trump = s goto_place_tc_on_display() } } @@ -3301,7 +3344,7 @@ states.place_tc_on_display = { push_undo() log(power_name[game.power] + " placed a TC.") set_delete(game.hand[game.power], c) - game.display[game.power] = c + game.placed[game.power] = c game.state = "place_tc_on_display_done" }, pass() { @@ -3317,6 +3360,7 @@ states.place_tc_on_display_done = { view.actions.next = 1 }, next() { + clear_undo() end_place_tc_on_display() }, } @@ -3334,9 +3378,9 @@ function goto_determine_order_of_influence() { // Turn cards face-up and return bluff cards for (let pow of POWER_FROM_POLITICAL_STAGE) { - let c = game.display[pow] + let c = game.placed[pow] if (c > 0) { - if (to_suit(c) === game.trump) { + if (is_reserve(c) || to_suit(c) === game.trump) { log(">" + format_card(c) + " " + power_name[pow]) set_add(game.saved[pow], c) } else { @@ -3345,7 +3389,7 @@ function goto_determine_order_of_influence() { } } } - delete game.display + delete game.placed log_br() game.stage = 0 @@ -3404,7 +3448,10 @@ states.select_political_card = { political(pc) { push_undo() log(power_name[game.power] + " chose C" + pc + ".") - throw "TODO" + game.saved[game.power] = [] + game.pc = pc + game.pcx = -1 + next_execute_political_card() }, pass() { log(power_name[game.power] + " saved its TC.") @@ -3430,6 +3477,221 @@ function end_politics() { goto_place_hussars() } +/* POLITICAL CARDS */ + +const event_shift = { + "Italy +1": { track: "italy", amount: [ 1 ] }, + "Italy +2": { track: "italy", amount: [ 2 ] }, + "Italy -1 or +1": { track: "italy", amount: [ -1, 1 ] }, + "Italy -1 or +2": { track: "italy", amount: [ -1, 2 ] }, + "Italy -1": { track: "italy", amount: [ -1 ] }, + "Italy -2 or +1": { track: "italy", amount: [ -2, 1 ] }, + "Italy -2": { track: "italy", amount: [ -2 ] }, + "Russia +1": { track: "russia", amount: [ 1 ] }, + "Russia -1 or +1": { track: "russia", amount: [ -1, 1 ] }, + "Russia -1 or +2": { track: "russia", amount: [ -1, 2 ] }, + "Russia -1": { track: "russia", amount: [ -1 ] }, + "Russia -2": { track: "russia", amount: [ -2 ] }, + "Saxony +1": { track: "saxony", amount: [ 1 ] }, + "Saxony +4": { track: "saxony", amount: [ 4 ] }, + "Saxony -1 if allied with Prussia": { + track: "saxony", + amount: [ -1 ], + condition: () => is_saxony_allied_with_prussia(), + }, +} + +const event_troops = { + "Your major power +3 troops": + { tcs: 0, troops: 3, power: () => [ game.power ] }, + "France +1 TC and +3 troops": + { tcs: 1, troops: 3, power: () => [ P_FRANCE ] }, + "Pragmatic +1 TC and +3 troops": + { tcs: 1, troops: 3, power: () => [ P_PRAGMATIC ] }, + "Pragmatic or France +1 TC and +2 troops": + { tcs: 1, troops: 2, power: () => [ P_PRAGMATIC, P_FRANCE ] }, + "France or Bavaria +2 troops": + { tcs: 0, troops: 2, power: () => [ P_FRANCE, P_BAVARIA ] }, + "Saxony +2 troops": + { tcs: 0, troops: 2, power: () => [ P_SAXONY ] }, +} + +const event_misc = { + "Mannheim to French control": goto_mannheim_to_french_control, + "Pragmatic general to England": goto_pragmatic_general_to_england, + "France -1 TC this turn": goto_france_minus_tc_this_turn, +} + +function current_political_effect() { + return political_cards[game.pc].effects[game.pcx] +} + +function next_execute_political_card() { + if (++game.pcx === political_cards[game.pc].effects.length) { + game.state = "political_card_done" + return + } + let fx = current_political_effect() + if (fx in event_shift) + game.state = "political_shift" + else if (fx in event_troops) + game.state = "political_troop_power" + else if (fx in event_misc) + event_misc[fx]() +} + +states.political_card_done = { + inactive: "execute political card", + prompt() { + prompt("Political card done.") + view.actions.end_political_card = 1 + }, + end_political_card() { + end_execute_political_card() + }, +} + +function end_execute_political_card() { + clear_undo() + array_remove_item(game.political, game.pc) + delete game.pc + delete game.pcx + goto_adjust_political_tracks() +} + +const TRACK_NAME = { saxony: "Saxony marker", russia: "Russia marker", italy: "Italy marker" } +const TRACK_SIZE = { saxony: 5, russia: 9, italy: 9 } + +states.political_shift = { + inactive: "execute political card", + prompt() { + let info = event_shift[current_political_effect()] + prompt("Shift " + TRACK_NAME[info.track] + ".") + if (info.condition === undefined || info.condition()) + view.actions.shift = info.amount + view.actions.pass = 1 + view.pc = game.pc + }, + shift(n) { + push_undo() + let info = event_shift[current_political_effect()] + game[info.track] += n + game[info.track] = Math.max(1, Math.min(TRACK_SIZE[info.track], game[info.track])) + log("Shifted " + TRACK_NAME[info.track] + " to " + game[info.track] + ".") + next_execute_political_card() + }, + pass() { + push_undo() + next_execute_political_card() + }, +} + +states.political_troop_power = { + inactive: "execute political card", + prompt() { + let fx = current_political_effect() + let info = event_troops[fx] + prompt(fx) + view.pc = game.pc + view.actions.power = info.power() + view.actions.pass = 1 + }, + power(pow) { + clear_undo() + let info = event_troops[current_political_effect()] + set_active_to_power(pow) + if (info.tcs > 0) { + draw_tc(info.tcs) + game.state = "political_troops_draw" + } else { + game.state = "political_troops_place" + } + game.count = info.troops + }, + pass() { + push_undo() + next_execute_political_card() + }, +} + +states.political_troops_draw = { + inactive: "execute political card", + prompt() { + let info = event_troops[current_political_effect()] + prompt("Draw " + format_card_list_prompt(game.draw) + ".") + view.draw = game.draw + view.actions.next = 1 + }, + next() { + push_undo() + let info = event_troops[current_political_effect()] + if (info.tcs > 0) { + for (let c of game.draw) + set_add(game.hand[game.power], c) + delete game.draw + } + game.state = "political_troops_place" + }, +} + +states.political_troops_place = { + inactive: "execute political card", + prompt() { + let info = event_troops[current_political_effect()] + if (game.count > 1) + prompt("Add " + game.count + " troops.") + else if (game.count === 1) + prompt("Add 1 troop.") + + if (game.count > 0) { + for (let p of all_power_generals[game.power]) { + if (is_piece_on_map(p) && game.troops[p] < 8) + gen_action_piece(p) + if (is_piece_eliminated(p) && has_re_entry_space_for_general()) + gen_action_piece(p) + } + } + + view.actions.pass = 1 + }, + piece(p) { + push_undo() + if (game.pos[p] === ELIMINATED) { + game.selected = p + game.state = "re_enter_general_from_political_card" + } else { + game.troops[p] += 1 + if (--game.count === 0) + next_execute_political_card() + } + }, + pass() { + push_undo() + next_execute_political_card() + }, +} + +function goto_mannheim_to_french_control() { + throw "TODO" +} + +function goto_pragmatic_general_to_england() { + throw "TODO" +} + +function goto_france_minus_tc_this_turn() { + throw "TODO" +} + +/* POLITICAL TRACKS */ + +function goto_adjust_political_tracks() { + // TODO: handle expeditionary corps + // TODO: handle victory points (italy) + // TODO: handle saxony alliance + goto_select_political_card() +} + /* SETUP */ const POWER_FROM_SETUP_STAGE = [ @@ -3602,7 +3864,6 @@ exports.setup = function (seed, _scenario, _options) { deck: null, hand: [ [], [], [], [], [], [] ], saved: [ [], [], [], [] ], - display: [ [], [], [], [] ], pos: setup_piece_position.slice(), oos: 0, @@ -3780,17 +4041,17 @@ function mask_hand(player) { return view_hand } -function mask_display(player) { - let view_display = [] +function mask_placed(player) { + let view_placed = [] for (let pow of all_major_powers) { - if (game.display[pow] === 0) - view_display[pow] = -1 + if (game.placed[pow] === 0) + view_placed[pow] = -1 else if (player_from_power(pow) === player) - view_display[pow] = game.display[pow] + view_placed[pow] = game.placed[pow] else - view_display[pow] = game.display[pow] & ~127 + view_placed[pow] = game.placed[pow] & ~127 } - return view_display + return view_placed } function mask_saved(player) { @@ -3853,8 +4114,8 @@ exports.view = function (state, player) { if (game.political) view.political = game.political - if (game.display) - view.display = mask_display(player) + if (game.placed) + view.placed = mask_placed(player) if (game.state === "game_over") { view.prompt = game.victory |