summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--images/shaking-hands.svg1
-rw-r--r--play.html9
-rw-r--r--play.js31
-rw-r--r--rules.js250
4 files changed, 270 insertions, 21 deletions
diff --git a/images/shaking-hands.svg b/images/shaking-hands.svg
new file mode 100644
index 0000000..f3b76cd
--- /dev/null
+++ b/images/shaking-hands.svg
@@ -0,0 +1 @@
+<svg style="height: 512px; width: 512px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M0 0h512v512H0z" fill="#ffffff" fill-opacity="0"></path><g class="" style="" transform="translate(0,0)"><path d="M494 61.363l-82.58 77.934 78.994 132.96 3.586-4.458V61.362zM18 62.5v225.893c4.48.582 9.863.903 15.295.96 11.87.125 21.654-.65 27.15-1.144L113.1 154.974 18 62.5zm389.154 104.86l-7.04 4.556c-.15.097-5.362 3.336-6.893 4.29l-10.605 6.42.15.09c-4.914 3.057-6.28 3.917-11.857 7.38-2.83 1.757-2.9 1.798-5.584 3.465-20.29-10.907-42.306-19.29-67.998-25.882-32.312 9.762-66.542 23.888-100.722 37.142 14.19 17.087 29.96 22.651 45.845 22.85 18.42.23 37.25-7.78 50.218-16.754l7.4-5.12 7.426 10.73 115.453 83.33 45.112-29.987-60.906-102.51zM126.477 170.1L81.11 284.887 97.76 297.69l30.795-34.905 2.467-2.795 3.72-.232c1.5-.094 2.98-.138 4.44-.13 10.212.066 19.342 2.716 26.19 8.76 5.072 4.472 8.444 10.426 10.4 17.32l2.28-.142c11.995-.75 22.802 1.725 30.63 8.63 7.827 6.907 11.63 17.323 12.38 29.32l.07 1.08c6.44 1.216 12.205 3.752 16.893 7.888 7.828 6.906 11.63 17.32 12.38 29.317l.197 3.12c.642.202 1.275.424 1.9.658l2.033-2.853 5.47-7.678 2.813-3.95 7.33 5.223 59.428 42.336c6.464-1.594 10.317-4.075 12.46-7.086 2.147-3.012 3.233-7.47 2.624-14.107l-71.258-51.03-7.318-5.24 5.19-7.246 6.67-9.365 7.33 5.223 80.335 57.226c6.464-1.593 10.32-4.074 12.463-7.085 2.144-3.01 3.23-7.457 2.625-14.082l-92.398-65.55-7.34-5.21 10.414-14.68 7.343 5.208 92.414 65.565c6.47-1.594 10.327-4.075 12.473-7.088 2.148-3.015 3.233-7.476 2.62-14.125l-110.44-79.71c-14.655 8.688-33.402 15.648-53.557 15.396-23.587-.295-48.817-11.566-67.377-40.05a9 9 0 0 1 4.343-13.327c13.014-4.945 26.163-10.17 39.343-15.354l-92.056-6.834zm12.902 107.62l-47.564 53.91c.927 6.746 3.04 10.942 5.887 13.454 2.847 2.512 7.275 4.085 14.084 4.164l47.563-53.908c-.927-6.747-3.04-10.945-5.887-13.457-2.847-2.512-7.274-4.084-14.084-4.162zm43.308 25.81l-53.713 60.88c.926 6.747 3.04 10.945 5.886 13.457 2.85 2.51 7.275 4.083 14.085 4.16l53.713-60.878c-.926-6.748-3.04-10.944-5.887-13.457-2.846-2.512-7.273-4.085-14.083-4.164zm29.34 38.286l-47.56 53.91c.927 6.746 3.04 10.943 5.887 13.456 2.848 2.512 7.275 4.083 14.084 4.162L232 359.44c-.927-6.75-3.04-10.947-5.887-13.46-2.847-2.512-7.274-4.083-14.084-4.162zm24.702 39.137l-38.794 44.28c.925 6.76 3.038 10.962 5.888 13.476 2.845 2.51 7.267 4.082 14.067 4.163l38.796-44.28c-.926-6.758-3.04-10.96-5.89-13.476-2.844-2.51-7.266-4.08-14.066-4.162zm35.342 4.79c1.694 4.62 2.673 9.74 3.014 15.192l.232 3.704-8.277 9.448 26.724 19.037c6.464-1.594 10.316-4.075 12.46-7.086 2.145-3.01 3.233-7.464 2.628-14.093l-36.78-26.2z" fill="#000000" fill-opacity="1"></path></g></svg> \ No newline at end of file
diff --git a/play.html b/play.html
index 2975890..fc19c08 100644
--- a/play.html
+++ b/play.html
@@ -30,6 +30,15 @@
</menu>
</details>
<button onclick="toggle_pieces()"><img src="/images/earth-africa-europe.svg"></button>
+ <details id="subsidy_menu">
+ <summary>
+ <img src="images/shaking-hands.svg">
+ </summary>
+ <menu>
+ <li id="propose_subsidy_menu" onclick="send_action('propose_subsidy')">Propose subsidy contract
+ <li id="cancel_subsidy_menu" onclick="send_action('cancel_subsidy')">Cancel subsidy contract
+ </menu>
+ </details>
</div>
</header>
diff --git a/play.js b/play.js
index 5e8357f..f3f5630 100644
--- a/play.js
+++ b/play.js
@@ -22,6 +22,23 @@ function toggle_shift() {
document.body.classList.toggle("shift")
}
+function action_menu_item(action) {
+ let menu = document.getElementById(action + "_menu")
+ if (view.actions && action in view.actions) {
+ menu.classList.toggle("disabled", view.actions[action] === 0)
+ return 1
+ } else {
+ menu.classList.toggle("disabled", true)
+ return 0
+ }
+}
+
+function action_menu(menu, action_list) {
+ let x = 0
+ for (let action of action_list)
+ x |= action_menu_item(action)
+}
+
/* DATA (SHARED) */
const deck_name = [ "red", "green", "blue", "yellow" ]
@@ -1267,6 +1284,11 @@ function on_update() {
layout_combat_marker()
}
+ action_menu(document.getElementById("subsidy_menu"), [
+ "propose_subsidy",
+ "cancel_subsidy",
+ ])
+
action_button_with_argument("suit", SPADES, colorize_S)
action_button_with_argument("suit", CLUBS, colorize_C)
action_button_with_argument("suit", HEARTS, colorize_H)
@@ -1287,10 +1309,13 @@ function on_update() {
for (let pow of all_powers)
action_button_with_argument("power", pow, power_name[pow])
- action_button("reduce", "Reduce")
- action_button("offer", "Offer")
+ action_button("subsidy", "Subsidy")
+ action_button("cancel", "Cancel")
+
+ action_button("reduce", "Reduce military objectives")
+ action_button("offer", "Offer peace")
action_button("accept", "Accept")
- action_button("deny", "Deny")
+ action_button("refuse", "Refuse")
action_button("re_enter", "Re-enter")
action_button("force_march", "Force march")
diff --git a/rules.js b/rules.js
index 8ebf3cc..18ed12c 100644
--- a/rules.js
+++ b/rules.js
@@ -543,6 +543,14 @@ function all_enemy_powers(pow) {
}
}
+function is_allied_power(a, b) {
+ return !all_enemy_powers(a).includes(b)
+}
+
+function is_controlled_power(major, other) {
+ return player_from_power(major) === player_from_power(other)
+}
+
function all_non_coop_powers(pow) {
switch (pow) {
case P_FRANCE:
@@ -2485,13 +2493,12 @@ states.re_enter_train_power = {
inactive: "move",
prompt() {
prompt("Re-enter supply train from which power?")
- view.actions.power = []
for (let pow of all_controlled_powers(game.power)) {
if (game.move_re_entered & (1 << pow))
continue
for (let p of all_power_trains[pow]) {
if (can_train_re_enter(p)) {
- view.actions.power.push(pow)
+ gen_action_power(pow)
break
}
}
@@ -4530,12 +4537,12 @@ states.accept_peace = {
prompt() {
prompt("Accept Prussia's offer of temporary peace to annex Silesia?")
view.actions.accept = 1
- view.actions.deny = 1
+ view.actions.refuse = 1
},
accept() {
goto_annex_silesia()
},
- deny() {
+ refuse() {
end_action_stage_2()
},
}
@@ -4874,6 +4881,195 @@ function end_imperial_election() {
goto_start_turn()
}
+/* SUBSIDY CONTRACTS - CREATE */
+
+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)
+ },
+ power(from) {
+ game.proposal.from = from
+ game.state = "propose_subsidy_to"
+ }
+}
+
+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?`)
+ for (let to of all_powers) {
+ if (from !== to && is_allied_power(from, to)) {
+ if (is_controlled_power(player, from) || is_controlled_power(player, to))
+ gen_action_power(to)
+ }
+ }
+ },
+ power(to) {
+ game.proposal.to = to
+ game.state = "propose_subsidy_length"
+ }
+}
+
+states.propose_subsidy_length = {
+ inactive: "create subsidy contract",
+ 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?`)
+ view.actions.value = [ 1, 2, 3, 4, 5, 6 ]
+ },
+ value(n) {
+ clear_undo()
+ game.proposal.n = n
+ set_active_to_power(game.proposal.from)
+ game.state = "propose_subsidy_approve"
+ },
+}
+
+states.propose_subsidy_approve = {
+ 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?`)
+ 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()
+ },
+ refuse() {
+ 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()
+ },
+}
+
+function end_propose_subsidy() {
+ set_active_to_power(game.proposal.save_power)
+ game.state = game.proposal.save_state
+ delete game.proposal
+}
+
+/* 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
+ })
+ }
+ return result
+}
+
+function goto_cancel_subsidy() {
+ game.proposal = { save_power: game.power, save_state: game.state, from: -1, to: -1 }
+ game.state = "cancel_subsidy_from"
+}
+
+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)
+ })
+ }
+ },
+ power(from) {
+ game.proposal.from = from
+ game.state = "cancel_subsidy_to"
+ }
+}
+
+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?`)
+ 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)
+ })
+ },
+ power(to) {
+ let player = game.power
+ let from = game.proposal.from
+ clear_undo()
+ 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"
+ }
+}
+
+states.cancel_subsidy_approve = {
+ 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]}?`)
+ view.actions.accept = 1
+ view.actions.refuse = 1
+ },
+ accept() {
+ let from = game.proposal.from
+ let to = game.proposal.to
+ log(`Canceled subsidy contract between ${power_name[from]} and ${power_name[to]}.`)
+ map_delete(game.contracts[from], to)
+ end_cancel_subsidy()
+ },
+ refuse(n) {
+ let from = game.proposal.from
+ let to = game.proposal.to
+ log(power_name[game.power] + ` refused to cancel subsidy contract from ${power_name[from]}.`)
+ end_cancel_subsidy()
+ },
+}
+
+function end_cancel_subsidy() {
+ set_active_to_power(game.proposal.save_power)
+ game.state = game.proposal.save_state
+ delete game.proposal
+}
+
/* SETUP */
const POWER_FROM_SETUP_STAGE = [
@@ -5334,11 +5530,39 @@ exports.view = function (state, player) {
else
view.actions.undo = 0
}
+
+ // subsidy contracts actions for active player
+ if (!game.proposal) {
+ if (may_cancel_subsidy())
+ view.actions.cancel_subsidy = 1
+ view.actions.propose_subsidy = 1
+ }
+
}
return view
}
+exports.action = function (state, _player, action, arg) {
+ game = state
+ let S = states[game.state]
+ if (S && action in S) {
+ S[action](arg)
+ } else {
+ if (action === "undo" && game.undo && game.undo.length > 0) {
+ pop_undo()
+ } else if (action === "propose_subsidy") {
+ push_undo()
+ goto_propose_subsidy()
+ } else if (action === "cancel_subsidy") {
+ push_undo()
+ goto_cancel_subsidy()
+ } else
+ throw new Error("Invalid action: " + action)
+ }
+ return game
+}
+
/* COMMON FRAMEWORK */
function goto_game_over(result, victory) {
@@ -5355,20 +5579,6 @@ function prompt(str) {
view.prompt = power_name[game.power] + ": " + str
}
-exports.action = function (state, _player, action, arg) {
- game = state
- let S = states[game.state]
- if (S && action in S) {
- S[action](arg)
- } else {
- if (action === "undo" && game.undo && game.undo.length > 0)
- pop_undo()
- else
- throw new Error("Invalid action: " + action)
- }
- return game
-}
-
function gen_action(action, argument) {
if (view.actions[action] === undefined)
view.actions[action] = [ argument ]
@@ -5376,6 +5586,10 @@ function gen_action(action, argument) {
set_add(view.actions[action], argument)
}
+function gen_action_power(p) {
+ gen_action("power", p)
+}
+
function gen_action_piece(p) {
gen_action("piece", p)
}