diff options
-rw-r--r-- | images/shaking-hands.svg | 1 | ||||
-rw-r--r-- | play.html | 9 | ||||
-rw-r--r-- | play.js | 31 | ||||
-rw-r--r-- | rules.js | 250 |
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 @@ -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> @@ -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") @@ -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) } |