diff options
Diffstat (limited to 'rules.js')
-rw-r--r-- | rules.js | 339 |
1 files changed, 268 insertions, 71 deletions
@@ -81,6 +81,12 @@ const turn_name = [ "Winter 1744", ] +const DI_TURN = 0 +const DI_A_POWER = 1 +const DI_B_POWER = 2 +const DI_A_PROMISE = 3 +const DI_B_PROMISE = 4 + const F_EMPEROR_FRANCE = 1 const F_EMPEROR_AUSTRIA = 2 const F_ITALY_FRANCE = 4 @@ -1319,6 +1325,9 @@ function goto_start_turn() { else goto_politics() } + + // remove expired deals + game.deals = game.deals.filter(deal => deal[DI_TURN] < game.turn) } function goto_end_turn() { @@ -1728,11 +1737,11 @@ function draw_tc(draw, n, pow) { function give_subsidy(other) { if (other === P_BAVARIA && is_enemy_controlled_fortress(MUNCHEN)) { - log("Bavaria 1 TC lost (S" + MUNCHEN + " is enemy controlled)") + log("Bavaria TC subsidy lost\nS" + MUNCHEN + " is enemy controlled") return } if (other === P_SAXONY && is_enemy_controlled_fortress(DRESDEN)) { - log("Saxony 1 TC lost (S" + DRESDEN + " is enemy controlled)") + log("Saxony TC subsidy lost\nS" + DRESDEN + " is enemy controlled") return } draw_tc(game.hand2[other], 1, other) @@ -1752,9 +1761,9 @@ function goto_tactical_cards() { game.draw = [] if (game.power === P_BAVARIA && is_enemy_controlled_fortress(MUNCHEN)) { - log("S" + MUNCHEN + " is enemy controlled.") + log("Bavaria TC draw lost\nS" + MUNCHEN + " is enemy controlled.") } else if (game.power === P_SAXONY && is_enemy_controlled_fortress(DRESDEN)) { - log("S" + DRESDEN + " is enemy controlled.") + log("Saxony TC draw lost\nS" + DRESDEN + " is enemy controlled.") } else { let n_cards = tc_per_turn() @@ -1764,6 +1773,48 @@ function goto_tactical_cards() { if (map_get(game.contracts[game.power], other, 0) > 0) --n_cards + // Too many subsidies to hand out! + // NOTE: This can only happen with Prussia. + // If giving subsidies to Saxony, Bavaria, and/or France + // while at the -1 or -2 TC on the Russia track. + while (n_cards < 0) { + // Cancel cooperative subsidy first. + if (map_get(game.contracts[P_PRUSSIA], P_SAXONY, 0) > 0) { + log("Canceled subsidy to Saxony (forced).") + map_delete(game.contracts[P_PRUSSIA], P_SAXONY) + n_cards++ + continue + } + + // Then the shortest of the Bavarian or France subsidy. + let n_france = map_get(game.contracts[P_PRUSSIA], P_FRANCE, 0) + let n_bavaria = map_get(game.contracts[P_PRUSSIA], P_BAVARIA, 0) + if (n_france > 0 && n_bavaria > 0) { + // Cancel the shortest remaining subsidy. + if (n_france > n_bavaria) + n_france = 0 + else + n_bavaria = 0 + } + + if (n_france > 0) { + log("Canceled subsidy to France (forced).") + map_delete(game.contracts[P_PRUSSIA], P_FRANCE) + n_cards++ + continue + } + + if (n_bavaria > 0) { + log("Canceled subsidy to Bavaria (forced).") + map_delete(game.contracts[P_PRUSSIA], P_BAVARIA) + n_cards++ + continue + } + + // Should never happen! + n_cards = 0 + } + draw_tc(game.draw, n_cards, game.power) if (game.contracts[game.power]) { @@ -2094,7 +2145,7 @@ states.supply_hussars = { // put back into hand unused cards for (let c of game.supply.pool) - set_add(game.hand2[game.power], c) // TODO: or hand1 + set_add(game.hand2[game.power], c) delete game.supply.pool delete game.supply.used @@ -3215,7 +3266,7 @@ function end_re_enter_train() { // put back into hand unused cards for (let c of game.recruit.pool) - set_add(game.hand2[game.power], c) // TODO: or hand1 + set_add(game.hand2[game.power], c) delete game.recruit @@ -3531,7 +3582,7 @@ function end_recruit() { // put back into hand unused cards for (let c of game.recruit.pool) - set_add(game.hand2[game.power], c) // TODO: or hand1 + set_add(game.hand2[game.power], c) delete game.recruit } else { @@ -3972,6 +4023,7 @@ function resume_retreat() { set_active_winner() game.state = "retreat" } else { + // TODO: if mixed french/bavarian, eliminate bavarians and try again? // eliminate if there are no retreat possibilities delete game.retreat game.state = "retreat_eliminate_trapped" @@ -4760,10 +4812,10 @@ states.political_troop_power = { view.actions.pass = 1 }, power(pow) { - clear_undo() // reveal random cards let info = event_troops[current_political_effect()] set_active_to_power(pow) if (info.tcs > 0) { + clear_undo() // reveal random cards draw_tc(game.draw = [], info.tcs, game.power) game.state = "political_troops_draw" } else { @@ -4814,11 +4866,13 @@ function can_add_troops() { function goto_political_troops_place() { let info = event_troops[current_political_effect()] game.count = info.troops - log(power_name[game.power] + " " + game.count + " troops.") - if (can_add_troops()) + if (can_add_troops()) { + log(power_name[game.power] + " " + game.count + " troops.") game.state = "political_troops_place" - else + } else { + log(power_name[game.power] + " cannot receive troops.") next_execute_political_card() + } } states.political_troops_place = { @@ -5106,7 +5160,7 @@ states.recruit_for_expeditionary_corps = { prompt() { prompt("Recruit 2 troops for expeditionary corps at a price of 8 TC-points.") view.draw = game.recruit.pool - if (sum_card_values(game.recruit.pool) >= 8) + if (sum_card_values(game.recruit.pool) >= 8 || count_cards_in_hand() === 0) view.actions.next = 1 else gen_cards_in_hand() @@ -5119,7 +5173,12 @@ states.recruit_for_expeditionary_corps = { next() { push_undo() - spend_card_value(game.recruit.pool, game.recruit.used, 8) + if (sum_card_values(game.recruit.pool) >= 8) { + spend_card_value(game.recruit.pool, game.recruit.used, 8) + } else { + game.recruit.used = game.recruit.pool + game.recruit.pool = [] + } log(power_name[game.power] + " spent " + game.recruit.used.map(format_card).join(", ") + ".") @@ -5800,27 +5859,26 @@ function end_imperial_election() { /* SUBSIDY CONTRACTS - CREATE */ -function may_create_subsidy() { +function goto_propose_subsidy() { + game.proposal = { save_power: game.power, save_state: game.state, from: -1, to: -1, n: 0 } + game.state = "propose_subsidy_from" +} + +function may_propose_subsidy_from(pow) { if (is_two_player()) { - let major = coop_major_power(game.power) - if (major === P_PRAGMATIC || major === P_AUSTRIA) + if (pow === P_PRAGMATIC || pow === P_AUSTRIA) return is_saxony_austrian_ally() } return true } -function goto_propose_subsidy() { - game.proposal = { save_power: game.power, save_state: game.state, from: -1, to: -1, n: 0 } - game.state = "propose_subsidy_from" -} - states.propose_subsidy_from = { inactive: "create subsidy contract", prompt() { prompt("Subsidy contract from which major power?") - for (let from of all_major_powers) - if (is_allied_power(game.power, from)) - gen_action_power(from) + for (let pow of all_major_powers) + if (may_propose_subsidy_from(pow)) + gen_action_power(pow) }, power(from) { game.proposal.from = from @@ -5831,15 +5889,13 @@ states.propose_subsidy_from = { states.propose_subsidy_to = { inactive: "create subsidy contract", prompt() { - let player = game.power let from = game.proposal.from - prompt(`Subsidy contract between ${power_name[from]} and who?`) + prompt(`Subsidy contract from ${power_name[from]} to who?`) for (let to of (is_two_player() ? all_minor_powers : all_powers)) { if (from !== to && is_allied_power(from, to)) { if (to === P_SAXONY && is_saxony_neutral()) continue - if (is_controlled_power(player, from) || is_controlled_power(player, to)) - gen_action_power(to) + gen_action_power(to) } } }, @@ -5854,47 +5910,88 @@ states.propose_subsidy_length = { prompt() { let from = game.proposal.from let to = game.proposal.to - prompt(`Subsidy contract between ${power_name[from]} and ${power_name[to]} for how many turns?`) + prompt(`Subsidy contract from ${power_name[from]} to ${power_name[to]} for how many turns?`) view.actions.value = [ 1, 2, 3, 4, 5, 6 ] }, value(n) { game.proposal.n = n - if (is_controlled_power(game.power, game.proposal.from)) - set_active_to_power(game.proposal.to) - else + + if (false) { + set_active_to_power(game.proposal.from) + game.state = "propose_subsidy_approve_both" + return + } + + let ctl_from = is_controlled_power(game.power, game.proposal.from) + let ctl_to = is_controlled_power(game.power, game.proposal.to) + if (ctl_from) { + if (ctl_to) { + end_propose_subsidy(true) + } else { + set_active_to_power(game.proposal.to) + game.state = "propose_subsidy_approve_last" + } + } else { set_active_to_power(game.proposal.from) - game.state = "propose_subsidy_approve" + if (ctl_to) + game.state = "propose_subsidy_approve_last" + else + game.state = "propose_subsidy_approve_both" + } }, } -states.propose_subsidy_approve = { +states.propose_subsidy_approve_both = { inactive: "approve subsidy contract", prompt() { let from = game.proposal.from let to = game.proposal.to let n = game.proposal.n - prompt(`Subsidy contract between ${power_name[from]} and ${power_name[to]} for ${n} turns?`) + prompt(`Subsidy contract from ${power_name[from]} to ${power_name[to]} for ${n} turns?`) view.actions.accept = 1 view.actions.refuse = 1 }, accept() { - let from = game.proposal.from - let to = game.proposal.to - let n = game.proposal.n - log(`Subsidy contract between ${power_name[from]} and ${power_name[to]} for ${n} turns.`) - map_set(game.contracts[from], to, map_get(game.contracts[from], to, 0) + n) - end_propose_subsidy() + if (is_controlled_power(game.power, game.proposal.to)) { + end_propose_subsidy(true) + } else { + set_active_to_power(game.proposal.to) + game.state = "prpose_subsidy_approve_last" + } }, refuse() { + end_propose_subsidy(false) + }, +} + +states.propose_subsidy_approve_last = { + inactive: "approve subsidy contract", + prompt() { let from = game.proposal.from let to = game.proposal.to let n = game.proposal.n - log(`${power_name[from]} refused to create subsidy contract to ${power_name[to]} for ${n} turns.`) - end_propose_subsidy() + prompt(`Subsidy contract from ${power_name[from]} to ${power_name[to]} for ${n} turns?`) + view.actions.accept = 1 + view.actions.refuse = 1 + }, + accept() { + end_propose_subsidy(true) + }, + refuse() { + end_propose_subsidy(false) }, } -function end_propose_subsidy() { +function end_propose_subsidy(okay) { + let from = game.proposal.from + let to = game.proposal.to + let n = game.proposal.n + if (okay) { + log(`Accepted subsidy contract from ${power_name[from]} to ${power_name[to]} for ${n} turns.`) + map_set(game.contracts[from], to, n) + } else { + log(`Rejected subsidy contract from ${power_name[from]} to ${power_name[to]} for ${n} turns.`) + } set_active_to_power(game.proposal.save_power) game.state = game.proposal.save_state delete game.proposal @@ -5903,15 +6000,13 @@ function end_propose_subsidy() { /* SUBSIDY CONTRACTS - CANCEL */ function may_cancel_subsidy() { - let player = game.power let result = false for (let from of all_major_powers) { map_for_each(game.contracts[from], (to, _n) => { // cannot cancel initial contract! if (from === P_FRANCE && to === P_BAVARIA && game.turn < 3) return - if (is_controlled_power(player, from) || is_controlled_power(player, to)) - result = true + result = true }) } return result @@ -5925,15 +6020,13 @@ function goto_cancel_subsidy() { states.cancel_subsidy_from = { inactive: "cancel subsidy contract", prompt() { - let player = game.power prompt("Cancel which subsidy contract?") for (let from of all_major_powers) { map_for_each(game.contracts[from], (to, _n) => { // cannot cancel initial contract! if (from === P_FRANCE && to === P_BAVARIA && game.turn < 3) return - if (is_controlled_power(player, from) || is_controlled_power(player, to)) - gen_action_power(from) + gen_action_power(from) }) } }, @@ -5946,53 +6039,146 @@ states.cancel_subsidy_from = { states.cancel_subsidy_to = { inactive: "cancel subsidy contract", prompt() { - let player = game.power let from = game.proposal.from - prompt(`Cancel subsidy contract between ${power_name[from]} and who?`) + prompt(`Cancel subsidy contract from ${power_name[from]} to who?`) map_for_each(game.contracts[from], (to, _n) => { // cannot cancel initial contract! if (from === P_FRANCE && to === P_BAVARIA && game.turn < 3) return - if (is_controlled_power(player, from) || is_controlled_power(player, to)) - gen_action_power(to) + gen_action_power(to) }) }, power(to) { - let player = game.power - let from = game.proposal.from game.proposal.to = to - if (is_controlled_power(player, from)) - set_active_to_power(to) - else - set_active_to_power(from) - game.state = "cancel_subsidy_approve" + + let ctl_from = is_controlled_power(game.power, game.proposal.from) + let ctl_to = is_controlled_power(game.power, game.proposal.to) + if (ctl_from) { + if (ctl_to) { + end_cancel_subsidy(true) + } else { + set_active_to_power(game.proposal.to) + game.state = "cancel_subsidy_approve_last" + } + } else { + set_active_to_power(game.proposal.from) + if (ctl_to) + game.state = "cancel_subsidy_approve_last" + else + game.state = "cancel_subsidy_approve_both" + } } } -states.cancel_subsidy_approve = { +states.cancel_subsidy_approve_both = { inactive: "cancel subsidy contract", prompt() { let from = game.proposal.from let to = game.proposal.to - prompt(`Cancel subsidy contract between ${power_name[from]} and ${power_name[to]}?`) + prompt(`Cancel subsidy contract from ${power_name[from]} to ${power_name[to]}?`) view.actions.accept = 1 view.actions.refuse = 1 }, accept() { + if (is_controlled_power(game.power, game.proposal.to)) { + end_cancel_subsidy(true) + } else { + set_active_to_power(game.proposal.to) + game.state = "cancel_subsidy_approve_last" + } + }, + refuse() { + end_cancel_subsidy(false) + }, +} + +states.cancel_subsidy_approve_last = { + inactive: "cancel subsidy contract", + prompt() { let from = game.proposal.from let to = game.proposal.to - log(`Canceled subsidy contract between ${power_name[from]} and ${power_name[to]}.`) + prompt(`Cancel subsidy contract from ${power_name[from]} to ${power_name[to]}?`) + view.actions.accept = 1 + view.actions.refuse = 1 + }, + accept() { + end_cancel_subsidy(true) + }, + refuse() { + end_cancel_subsidy(false) + }, +} + +function end_cancel_subsidy(okay) { + let from = game.proposal.from + let to = game.proposal.to + if (okay) { + log(`Canceled subsidy contract from ${power_name[from]} to ${power_name[to]}.`) map_delete(game.contracts[from], to) - end_cancel_subsidy() + } else { + log(`Did not cancel subsidy contract from ${power_name[from]} to ${power_name[to]}.`) + } + set_active_to_power(game.proposal.save_power) + game.state = game.proposal.save_state + delete game.proposal +} + +/* NEGOTIATION - DEALS */ + +function goto_propose_deal(deal) { + game.proposal = { save_power: game.power, save_state: game.state, deal } + let from = game.proposal.deal[DI_A_POWER] + set_active_to_power(from) + game.state = "accept_deal_from" +} + +states.accept_deal_from = { + inactive: "accept deal", + prompt() { + let from = game.proposal.deal[DI_A_POWER] + let to = game.proposal.deal[DI_B_POWER] + prompt(`Accept ${power_name[from]} - ${power_name[to]} deal?`) + view.proposed_deal = game.proposal.deal + view.actions.accept = 1 + view.actions.refuse = 1 + }, + accept() { + let to = game.proposal.deal[DI_B_POWER] + set_active_to_power(to) + game.state = "accept_deal_to" }, refuse() { - let from = game.proposal.from - log(power_name[game.power] + ` refused to cancel subsidy contract from ${power_name[from]}.`) - end_cancel_subsidy() + end_accept_deal(false) }, } -function end_cancel_subsidy() { +states.accept_deal_to = { + inactive: "accept deal", + prompt() { + let from = game.proposal.deal[DI_A_POWER] + let to = game.proposal.deal[DI_B_POWER] + prompt(`Accept ${power_name[from]} - ${power_name[to]} deal?`) + view.proposed_deal = game.proposal.deal + view.actions.accept = 1 + view.actions.refuse = 1 + }, + accept() { + end_accept_deal(true) + }, + refuse() { + end_accept_deal(false) + }, +} + +function end_accept_deal(okay) { + let from = game.proposal.deal[DI_A_POWER] + let to = game.proposal.deal[DI_B_POWER] + if (okay) { + log(`Accepted ${power_name[from]} - ${power_name[to]} deal.`) + game.deals.push(game.proposal.deal) + } else { + log(`Rejected ${power_name[from]} - ${power_name[to]} deal.`) + } set_active_to_power(game.proposal.save_power) game.state = game.proposal.save_state delete game.proposal @@ -6185,6 +6371,7 @@ exports.setup = function (seed, scenario, _options) { hand1: [ [], [], [], [], [], [] ], hand2: [ [], [], [], [], [], [] ], + deals: [], // [ power, promise, turn ] tuples contracts: [ [ P_BAVARIA, 3 ], [], @@ -6453,6 +6640,7 @@ exports.view = function (state, player) { pt: total_troops_list(), discard: total_discard_list(), + deals: game.deals, contracts: game.contracts, face_up: game.face_up, face_down: mask_face_down(), @@ -6497,11 +6685,13 @@ exports.view = function (state, player) { } // subsidy contracts actions for active player - if (!game.proposal && !is_intro()) { + if (!game.proposal && !is_intro() && view.turn > 0) { if (may_cancel_subsidy()) view.actions.cancel_subsidy = 1 - if (may_create_subsidy()) + if (!is_intro()) view.actions.propose_subsidy = 1 + if (!is_two_player() && !is_intro()) + view.actions.propose_deal = 1 } } @@ -6523,6 +6713,9 @@ exports.action = function (state, _player, action, arg) { } else if (action === "cancel_subsidy") { push_undo() goto_cancel_subsidy() + } else if (action === "propose_deal") { + push_undo() + goto_propose_deal(arg) } else throw new Error("Invalid action: " + action) } @@ -6606,6 +6799,8 @@ function push_undo() { let copy = {} for (let k in game) { let v = game[k] + if (k === "deals") + continue if (k === "undo") continue else if (k === "log") @@ -6622,10 +6817,12 @@ function pop_undo() { if (game.undo) { let save_log = game.log let save_undo = game.undo + let save_deals = game.deals game = save_undo.pop() save_log.length = game.log game.log = save_log game.undo = save_undo + game.deals = save_deals } } |