summaryrefslogtreecommitdiff
path: root/rules.js
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2024-11-03 01:57:36 +0100
committerTor Andersson <tor@ccxvii.net>2024-11-06 01:49:09 +0100
commit719b7f1cd0917980021f8715c7f5bf8a642254f6 (patch)
treefb7005a1273955a71f52d135bb70330715b44dcd /rules.js
parent3c1ae85c4614cabbc84d2aa515885028c3e5bb0b (diff)
downloadmaria-719b7f1cd0917980021f8715c7f5bf8a642254f6.tar.gz
Propose deals and list accepted deals in political display.
Diffstat (limited to 'rules.js')
-rw-r--r--rules.js339
1 files changed, 268 insertions, 71 deletions
diff --git a/rules.js b/rules.js
index 9fa3eef..592d68f 100644
--- a/rules.js
+++ b/rules.js
@@ -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
}
}