summaryrefslogtreecommitdiff
path: root/rules.js
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2024-11-24 15:53:08 +0100
committerTor Andersson <tor@ccxvii.net>2024-11-24 15:53:08 +0100
commita2ea835f0df0f1bcd5d50792679626f85f39cc75 (patch)
tree00348fc4aad99451539d515f3caffa56f1636799 /rules.js
parent090b2edecbc7bb5dad76d5ed50570f6b413ff462 (diff)
downloadmaria-a2ea835f0df0f1bcd5d50792679626f85f39cc75.tar.gz
Validate promises.
Rework movement restarts. Use "Reject" instead of "Refuse". Use "Resume" after jumping back in time. # Conflicts: # rules.js
Diffstat (limited to 'rules.js')
-rw-r--r--rules.js659
1 files changed, 479 insertions, 180 deletions
diff --git a/rules.js b/rules.js
index 2f1e250..92b84f9 100644
--- a/rules.js
+++ b/rules.js
@@ -88,6 +88,8 @@ const DI_A_POWER = 1
const DI_B_POWER = 2
const DI_A_PROMISE = 3
const DI_B_PROMISE = 4
+const DI_A_PHASE = 5
+const DI_B_PHASE = 6
const F_EMPEROR_FRANCE = 1
const F_EMPEROR_AUSTRIA = 2
@@ -107,6 +109,12 @@ const F_TWO_PLAYER = 8192
const F_PLAYER_A_PICK = 16384 // picked PC card
const F_PLAYER_B_PICK = 32768
+// promise validation phases
+const V_POLITICS = 1
+const V_HUSSARS = 2
+const V_MOVEMENT = 4
+const V_RETREAT = 8
+
const SPADES = 0
const CLUBS = 1
const HEARTS = 2
@@ -1416,9 +1424,8 @@ const advanced_sequence_of_play = [
{ power: P_BAVARIA, action: goto_tactical_cards },
{ power: P_FRANCE, action: goto_supply },
{ power: P_BAVARIA, action: goto_supply },
- { power: P_FRANCE, action: init_movement },
- { power: P_FRANCE, action: goto_movement_global },
- { power: P_FRANCE, action: end_movement },
+ { power: P_FRANCE, action: goto_movement },
+ { power: P_FRANCE, action: validate_end_movement },
{ power: P_FRANCE, action: goto_combat },
{ power: P_FRANCE, action: end_action_stage },
{ power: P_FRANCE, action: goto_france_reduces_military_objectives },
@@ -1429,9 +1436,8 @@ const advanced_sequence_of_play = [
{ power: P_SAXONY, action: goto_tactical_cards, condition: is_saxony_prussian },
{ power: P_PRUSSIA, action: goto_supply },
{ power: P_SAXONY, action: goto_supply, condition: is_saxony_prussian },
- { power: P_PRUSSIA, action: init_movement },
- { power: P_PRUSSIA, action: goto_movement_global },
- { power: P_PRUSSIA, action: end_movement },
+ { power: P_PRUSSIA, action: goto_movement },
+ { power: P_PRUSSIA, action: validate_end_movement },
{ power: P_PRUSSIA, action: goto_combat },
{ power: P_PRUSSIA, action: end_action_stage },
{ power: P_PRUSSIA, action: goto_prussia_annexes_silesia },
@@ -1446,9 +1452,8 @@ const advanced_sequence_of_play = [
{ power: P_PRAGMATIC, action: goto_supply },
// alternate moves on flanders starting with pragmatic army
- { power: P_PRAGMATIC, action: init_movement },
- { power: P_PRAGMATIC, action: goto_movement_flanders },
- { power: P_PRAGMATIC, action: end_movement },
+ { power: P_PRAGMATIC, action: goto_movement },
+ { power: P_PRAGMATIC, action: validate_end_movement },
{ power: P_AUSTRIA, action: goto_combat },
{ power: P_PRAGMATIC, action: goto_combat },
@@ -1465,8 +1470,7 @@ const two_player_sequence_of_play = [
{ power: P_BAVARIA, action: goto_tactical_cards },
{ power: P_FRANCE, action: goto_supply },
{ power: P_BAVARIA, action: goto_supply },
- { power: P_FRANCE, action: init_movement },
- { power: P_FRANCE, action: goto_movement_global },
+ { power: P_FRANCE, action: goto_movement },
{ power: P_FRANCE, action: end_movement },
{ power: P_FRANCE, action: goto_combat },
{ power: P_FRANCE, action: end_action_stage },
@@ -1478,8 +1482,7 @@ const two_player_sequence_of_play = [
{ power: P_SAXONY, action: goto_tactical_cards, condition: is_saxony_prussian },
{ power: P_PRUSSIA, action: goto_supply },
{ power: P_SAXONY, action: goto_supply, condition: is_saxony_prussian },
- { power: P_PRUSSIA, action: init_movement },
- { power: P_PRUSSIA, action: goto_movement_global },
+ { power: P_PRUSSIA, action: goto_movement },
{ power: P_PRUSSIA, action: end_movement },
{ power: P_PRUSSIA, action: goto_combat },
{ power: P_PRUSSIA, action: end_action_stage },
@@ -1494,8 +1497,7 @@ const two_player_sequence_of_play = [
{ power: P_PRAGMATIC, action: goto_supply },
// austria and pragmatic share movement (like with minor power)
- { power: P_AUSTRIA, action: init_movement },
- { power: P_AUSTRIA, action: goto_movement_global },
+ { power: P_AUSTRIA, action: goto_movement },
{ power: P_AUSTRIA, action: end_movement },
// austria and pragmatic share combat (like with minor power)
@@ -1514,8 +1516,7 @@ const intro_sequence_of_play = [
{ power: P_BAVARIA, action: goto_tactical_cards },
{ power: P_FRANCE, action: goto_supply },
{ power: P_BAVARIA, action: goto_supply },
- { power: P_FRANCE, action: init_movement },
- { power: P_FRANCE, action: goto_movement_global },
+ { power: P_FRANCE, action: goto_movement },
{ power: P_FRANCE, action: end_movement },
{ power: P_FRANCE, action: goto_combat },
{ power: P_FRANCE, action: end_action_stage },
@@ -1525,8 +1526,7 @@ const intro_sequence_of_play = [
{ power: P_SAXONY, action: goto_tactical_cards },
{ power: P_PRUSSIA, action: goto_supply },
{ power: P_SAXONY, action: goto_supply },
- { power: P_PRUSSIA, action: init_movement },
- { power: P_PRUSSIA, action: goto_movement_global },
+ { power: P_PRUSSIA, action: goto_movement },
{ power: P_PRUSSIA, action: end_movement },
{ power: P_PRUSSIA, action: goto_combat },
{ power: P_PRUSSIA, action: end_action_stage },
@@ -1534,8 +1534,7 @@ const intro_sequence_of_play = [
{ power: P_AUSTRIA, action: start_action_stage },
{ power: P_AUSTRIA, action: goto_tactical_cards },
{ power: P_AUSTRIA, action: goto_supply },
- { power: P_AUSTRIA, action: init_movement },
- { power: P_AUSTRIA, action: goto_movement_global },
+ { power: P_AUSTRIA, action: goto_movement },
{ power: P_AUSTRIA, action: end_movement },
{ power: P_AUSTRIA, action: goto_combat },
{ power: P_AUSTRIA, action: end_action_stage },
@@ -1602,9 +1601,13 @@ function goto_place_hussars() {
log("=" + P_AUSTRIA + " Hussars")
set_active_to_power(P_AUSTRIA)
game.state = "place_hussars"
+ set_clear(game.moved)
+ save_checkpoint()
}
function end_place_hussars() {
+ clear_checkpoint()
+
set_clear(game.moved)
for (let p of all_hussars)
log("P" + p + " at S" + game.pos[p] + ".")
@@ -1629,7 +1632,9 @@ states.place_hussars = {
game.state = "place_hussars_where"
},
end_place_hussars() {
- end_place_hussars()
+ game.proposal = game.undo[0]
+ clear_undo()
+ goto_validate_hussars()
},
}
@@ -2360,7 +2365,7 @@ function give_troops(total) {
/* MOVEMENT */
-function init_movement() {
+function goto_movement() {
set_clear(game.moved)
if (is_intro()) {
@@ -2374,7 +2379,8 @@ function init_movement() {
game.move_re_entered = 0
game.move_conq = []
- next_sequence_of_play()
+ game.state = "movement"
+ save_checkpoint()
}
function has_unmoved_piece(pow) {
@@ -2398,20 +2404,6 @@ function has_unmoved_piece_on_flanders_map(pow) {
return false
}
-function goto_movement_flanders() {
- game.state = "movement"
-
- // save RESTART point
- clear_undo()
- push_undo()
- game.restart = game.undo[0]
- clear_undo()
-}
-
-function goto_movement_global() {
- game.state = "movement"
-}
-
function resume_movement() {
if (is_two_player() && game.power === P_PRAGMATIC)
game.power = P_AUSTRIA
@@ -2511,6 +2503,12 @@ states.movement_flanders_next = {
states.movement = {
inactive: "move",
+ dont_snap() {
+ // don't snapshot during alternating move phase
+ if (!is_two_player() && !is_intro() && (game.power === P_PRAGMATIC || game.power === P_AUSTRIA))
+ return true
+ return false
+ },
prompt() {
let done_generals = true
let done_trains = true
@@ -2631,50 +2629,49 @@ states.movement = {
},
restart() {
let dispute = game.flags & F_MOVE_DISPUTE
- game.undo = [ game.restart ]
- delete game.restart
- pop_undo()
- goto_movement_flanders()
+ restore_checkpoint()
game.state = "restart_flanders_movement"
game.flags |= dispute // preserve disputed mode
},
dispute() {
- game.undo = [ game.restart ]
- delete game.restart
- pop_undo()
- goto_movement_flanders()
+ restore_checkpoint()
game.state = "dispute_flanders_movement"
game.flags |= F_MOVE_DISPUTE
},
}
states.restart_flanders_movement = {
+ dont_snap: true,
inactive: "move",
prompt() {
prompt("Reset moves on Flanders map due to request.")
- view.actions.next = 1
+ view.actions.resume = 1
+ view.actions.undo = 0
},
- next() {
+ resume() {
game.state = "movement"
},
}
states.dispute_flanders_movement = {
+ dont_snap: true,
inactive: "move",
prompt() {
prompt("Alternating moves on Flanders map due to dispute.")
- view.actions.next = 1
+ view.actions.resume = 1
+ view.actions.undo = 0
},
- next() {
+ resume() {
log("Disputed Flanders movement.")
game.state = "movement"
},
}
function end_movement() {
+ clear_checkpoint()
+
game.flags &= ~F_MOVE_DISPUTE
game.flags &= ~F_MOVE_FLANDERS
- delete game.restart
if (game.moved.length === 0)
log("Nothing moved.")
@@ -3151,6 +3148,7 @@ function goto_confirm_flanders_stack() {
}
states.confirm_flanders_stack_1 = {
+ dont_snap: true,
inactive: "move",
prompt() {
let p = game.selected
@@ -3179,6 +3177,7 @@ states.confirm_flanders_stack_1 = {
}
states.confirm_flanders_stack_2 = {
+ dont_snap: true,
inactive: "confirm mixed stack",
prompt() {
let p = game.selected
@@ -3191,20 +3190,21 @@ states.confirm_flanders_stack_2 = {
other = find_general_of_power(s, P_PRAGMATIC)
prompt(`${piece_name[p]} wants to stack with ${piece_name[other]} at ${data.cities.name[s]}.`)
view.actions.accept = 1
- view.actions.refuse = 1
+ view.actions.reject = 1
},
accept() {
push_undo()
delete game.proposal
resume_movement_after_flanders_stacking()
},
- refuse() {
+ reject() {
set_active_to_power(piece_power[game.selected])
game.state = "confirm_flanders_stack_3"
},
}
states.confirm_flanders_stack_3 = {
+ dont_snap: true,
inactive: "move",
prompt() {
let p = game.selected
@@ -4286,7 +4286,7 @@ states.retreat_done = {
view.actions.next = 1
},
next() {
- finish_combat()
+ goto_validate_retreat()
},
}
@@ -4305,6 +4305,8 @@ function lose_vp(pow, n) {
}
function finish_combat() {
+ game.selected = -1
+
if (is_intro()) {
delete game.lost_generals
delete game.lost_troops
@@ -5728,13 +5730,13 @@ states.accept_peace = {
prompt() {
prompt("Accept Prussia's offer of peace for annexion of Silesia?")
view.actions.accept = 1
- view.actions.refuse = 1
+ view.actions.reject = 1
},
accept() {
log("Austria accepts peace.")
goto_annex_silesia()
},
- refuse() {
+ reject() {
log("Austria refuses peace.")
next_sequence_of_play()
},
@@ -6080,9 +6082,9 @@ function end_imperial_election() {
/* 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"
+function undo_subsidy_wizard() {
+ game.state = game.proposal.save_state
+ delete game.proposal
}
function may_propose_subsidy_from(pow) {
@@ -6093,21 +6095,39 @@ function may_propose_subsidy_from(pow) {
return true
}
+function create_subsidy() {
+ let from = game.proposal.from
+ let to = game.proposal.to
+ let n = game.proposal.n
+ map_set(game.contracts[from], to, n)
+}
+
+function goto_propose_subsidy(save_power) {
+ game.proposal = { save_power, save_state: game.state, from: -1, to: -1, n: 0 }
+ game.state = "propose_subsidy_from"
+}
+
states.propose_subsidy_from = {
+ dont_snap: true,
inactive: "create subsidy contract",
prompt() {
prompt("Subsidy contract from which major power?")
for (let pow of all_major_powers)
if (may_propose_subsidy_from(pow))
gen_action_power(pow)
+ view.actions.undo = 1
},
power(from) {
game.proposal.from = from
game.state = "propose_subsidy_to"
- }
+ },
+ undo() {
+ undo_subsidy_wizard()
+ },
}
states.propose_subsidy_to = {
+ dont_snap: true,
inactive: "create subsidy contract",
prompt() {
let from = game.proposal.from
@@ -6121,50 +6141,46 @@ states.propose_subsidy_to = {
gen_action_power(to)
}
}
+ view.actions.undo = 1
},
power(to) {
game.proposal.to = to
game.state = "propose_subsidy_length"
- }
+ },
+ undo() {
+ undo_subsidy_wizard()
+ },
}
states.propose_subsidy_length = {
+ dont_snap: true,
inactive: "create subsidy contract",
prompt() {
let from = game.proposal.from
let to = game.proposal.to
prompt(`Subsidy contract from ${power_name[from]} to ${power_name[to]} for how many turns?`)
view.actions.value = [ 1, 2, 3, 4, 5, 6 ]
+ view.actions.undo = 1
},
value(n) {
game.proposal.n = n
-
- 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"
- }
+ if (ctl_from && ctl_to) {
+ create_subsidy()
+ end_propose_subsidy()
} else {
- set_active_to_power(game.proposal.from)
- if (ctl_to)
- game.state = "propose_subsidy_approve_last"
- else
- game.state = "propose_subsidy_approve_both"
+ set_active_to_power_keep_undo(game.proposal.from)
+ game.state = "propose_subsidy_approve_from"
}
},
+ undo() {
+ undo_subsidy_wizard()
+ },
}
-states.propose_subsidy_approve_both = {
+states.propose_subsidy_approve_from = {
+ dont_snap: true,
inactive: "approve subsidy contract",
prompt() {
let from = game.proposal.from
@@ -6172,22 +6188,19 @@ states.propose_subsidy_approve_both = {
let n = game.proposal.n
prompt(`Subsidy contract from ${power_name[from]} to ${power_name[to]} for ${n} turns?`)
view.actions.accept = 1
- view.actions.refuse = 1
+ view.actions.reject = 1
},
accept() {
- if (is_controlled_power(game.power, game.proposal.to)) {
- end_propose_subsidy(true)
- } else {
- set_active_to_power(game.proposal.to)
- game.state = "propose_subsidy_approve_last"
- }
+ set_active_to_power_keep_undo(game.proposal.to)
+ game.state = "propose_subsidy_approve_to"
},
- refuse() {
- end_propose_subsidy(false)
+ reject() {
+ goto_reject_propose_subsidy()
},
}
-states.propose_subsidy_approve_last = {
+states.propose_subsidy_approve_to = {
+ dont_snap: true,
inactive: "approve subsidy contract",
prompt() {
let from = game.proposal.from
@@ -6195,27 +6208,50 @@ states.propose_subsidy_approve_last = {
let n = game.proposal.n
prompt(`Subsidy contract from ${power_name[from]} to ${power_name[to]} for ${n} turns?`)
view.actions.accept = 1
- view.actions.refuse = 1
+ view.actions.reject = 1
},
accept() {
- end_propose_subsidy(true)
+ create_subsidy()
+ end_propose_subsidy()
},
- refuse() {
- end_propose_subsidy(false)
+ reject() {
+ goto_reject_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)
+function goto_reject_propose_subsidy() {
+ if (game.power === game.proposal.to)
+ set_active_to_power_keep_undo(game.proposal.from)
+ else
+ set_active_to_power_keep_undo(game.proposal.save_power)
+ game.state = "reject_propose_subsidy"
+}
+
+function next_reject_propose_subsidy() {
+ if (game.power !== game.proposal.save_power)
+ set_active_to_power_keep_undo(game.proposal.save_power)
+ else
+ end_propose_subsidy()
+}
+
+states.reject_propose_subsidy = {
+ dont_snap: true,
+ inactive: "approve subsidy contract",
+ prompt() {
+ let from = game.proposal.from
+ let to = game.proposal.to
+ let n = game.proposal.n
+ prompt(`Subsidy contract from ${power_name[from]} to ${power_name[to]} for ${n} turns was NOT accepted.`)
+ view.actions.resume = 1
+ view.actions.undo = 0
+ },
+ resume() {
+ next_reject_propose_subsidy()
+ },
+}
+
+function end_propose_subsidy() {
+ set_active_to_power_keep_undo(game.proposal.save_power)
game.state = game.proposal.save_state
delete game.proposal
}
@@ -6235,12 +6271,19 @@ function may_cancel_subsidy() {
return result
}
-function goto_cancel_subsidy() {
- game.proposal = { save_power: game.power, save_state: game.state, from: -1, to: -1 }
+function cancel_subsidy() {
+ let from = game.proposal.from
+ let to = game.proposal.to
+ map_delete(game.contracts[from], to)
+}
+
+function goto_cancel_subsidy(save_power) {
+ game.proposal = { save_power, save_state: game.state, from: -1, to: -1 }
game.state = "cancel_subsidy_from"
}
states.cancel_subsidy_from = {
+ dont_snap: true,
inactive: "cancel subsidy contract",
prompt() {
prompt("Cancel which subsidy contract?")
@@ -6252,14 +6295,19 @@ states.cancel_subsidy_from = {
gen_action_power(from)
})
}
+ view.actions.undo = 1
},
power(from) {
game.proposal.from = from
game.state = "cancel_subsidy_to"
- }
+ },
+ undo() {
+ undo_subsidy_wizard()
+ },
}
states.cancel_subsidy_to = {
+ dont_snap: true,
inactive: "cancel subsidy contract",
prompt() {
let from = game.proposal.from
@@ -6270,30 +6318,28 @@ states.cancel_subsidy_to = {
return
gen_action_power(to)
})
+ view.actions.undo = 1
},
power(to) {
game.proposal.to = to
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"
- }
+ if (ctl_from && ctl_to) {
+ cancel_subsidy()
+ end_cancel_subsidy()
} else {
- set_active_to_power(game.proposal.from)
- if (ctl_to)
- game.state = "cancel_subsidy_approve_last"
- else
- game.state = "cancel_subsidy_approve_both"
+ set_active_to_power_keep_undo(game.proposal.from)
+ game.state = "cancel_subsidy_approve_from"
}
- }
+ },
+ undo() {
+ undo_subsidy_wizard()
+ },
}
-states.cancel_subsidy_approve_both = {
+states.cancel_subsidy_approve_from = {
+ dont_snap: true,
inactive: "cancel subsidy contract",
prompt() {
let from = game.proposal.from
@@ -6301,22 +6347,19 @@ states.cancel_subsidy_approve_both = {
prompt(`Cancel subsidy contract from ${power_name[from]} to ${power_name[to]}?`)
view.actions.accept = 1
// TODO: cannot refuse if minor power fortress is occupied!
- view.actions.refuse = 1
+ view.actions.reject = 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"
- }
+ set_active_to_power_keep_undo(game.proposal.to)
+ game.state = "cancel_subsidy_approve_to"
},
- refuse() {
- end_cancel_subsidy(false)
+ reject() {
+ goto_reject_cancel_subsidy()
},
}
-states.cancel_subsidy_approve_last = {
+states.cancel_subsidy_approve_to = {
+ dont_snap: true,
inactive: "cancel subsidy contract",
prompt() {
let from = game.proposal.from
@@ -6324,26 +6367,49 @@ states.cancel_subsidy_approve_last = {
prompt(`Cancel subsidy contract from ${power_name[from]} to ${power_name[to]}?`)
view.actions.accept = 1
// TODO: cannot refuse if minor power fortress is occupied!
- view.actions.refuse = 1
+ view.actions.reject = 1
},
accept() {
- end_cancel_subsidy(true)
+ cancel_subsidy()
+ end_cancel_subsidy()
},
- refuse() {
- end_cancel_subsidy(false)
+ reject() {
+ goto_reject_cancel_subsidy()
},
}
-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)
- } else {
- log(`Did not cancel subsidy contract from ${power_name[from]} to ${power_name[to]}.`)
- }
- set_active_to_power(game.proposal.save_power)
+function goto_reject_cancel_subsidy() {
+ if (game.power === game.proposal.to)
+ set_active_to_power_keep_undo(game.proposal.from)
+ else
+ set_active_to_power_keep_undo(game.proposal.save_power)
+ game.state = "reject_cancel_subsidy"
+}
+
+function next_reject_cancel_subsidy() {
+ if (game.power !== game.proposal.save_power)
+ set_active_to_power_keep_undo(game.proposal.save_power)
+ else
+ end_cancel_subsidy()
+}
+
+states.reject_cancel_subsidy = {
+ dont_snap: true,
+ inactive: "cancel subsidy contract",
+ prompt() {
+ let from = game.proposal.from
+ let to = game.proposal.to
+ prompt(`Subsidy from ${power_name[from]} to ${power_name[to]} was NOT canceled.`)
+ view.actions.resume = 1
+ view.actions.undo = 0
+ },
+ resume() {
+ next_reject_cancel_subsidy()
+ },
+}
+
+function end_cancel_subsidy() {
+ set_active_to_power_keep_undo(game.proposal.save_power)
game.state = game.proposal.save_state
delete game.proposal
}
@@ -6357,24 +6423,16 @@ function goto_propose_deal(save_power, deal) {
game.state = "accept_deal_from"
}
-function log_deal(deal, prefix, postfix = "") {
- log(prefix +
- "Deal until " + turn_name[deal[DI_TURN]] + ".\n" +
- power_name[deal[DI_A_POWER]] + ": " + deal[DI_A_PROMISE] + "\n" +
- power_name[deal[DI_B_POWER]] + ": " + deal[DI_B_PROMISE] + "\n" +
- postfix
- )
-}
-
states.accept_deal_from = {
- inactive: "accept deal",
+ dont_snap: true,
+ inactive: "negotiate",
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
+ view.actions.reject = 1
view.actions.undo = 0
},
accept() {
@@ -6382,55 +6440,85 @@ states.accept_deal_from = {
set_active_to_power_keep_undo(to)
game.state = "accept_deal_to"
},
- refuse() {
- end_accept_deal(false)
+ reject() {
+ goto_reject_deal()
},
}
states.accept_deal_to = {
- inactive: "accept deal",
+ dont_snap: true,
+ inactive: "negotiate",
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
+ view.actions.reject = 1
view.actions.undo = 0
},
accept() {
- end_accept_deal(true)
+ game.deals.push(game.proposal.deal)
+ end_accept_deal()
},
- refuse() {
- end_accept_deal(false)
+ reject() {
+ goto_reject_deal()
},
}
-function end_accept_deal(okay) {
- if (okay) {
- log_deal(game.proposal.deal, "{")
- game.deals.push(game.proposal.deal)
- } else {
- log_deal(game.proposal.deal, "}", "Rejected by " + power_name[game.power] + ".")
- }
- log_br()
+function goto_reject_deal() {
+ let from = game.proposal.deal[DI_A_POWER]
+ let to = game.proposal.deal[DI_B_POWER]
+ if (game.power === to)
+ set_active_to_power_keep_undo(from)
+ else
+ set_active_to_power_keep_undo(game.proposal.save_power)
+ game.state = "reject_deal"
+}
+
+function next_reject_deal() {
+ if (game.power !== game.proposal.save_power)
+ set_active_to_power_keep_undo(game.proposal.save_power)
+ else
+ end_accept_deal()
+}
+states.reject_deal = {
+ dont_snap: true,
+ inactive: "negotiate",
+ prompt() {
+ let from = game.proposal.deal[DI_A_POWER]
+ let to = game.proposal.deal[DI_B_POWER]
+ prompt(`Deal between ${power_name[from]} and ${power_name[to]} was NOT accepted.`)
+ view.proposed_deal = game.proposal.deal
+ view.actions.resume = 1
+ view.actions.undo = 0
+ },
+ resume() {
+ next_reject_cancel_subsidy()
+ },
+}
+
+function end_accept_deal() {
set_active_to_power_keep_undo(game.proposal.save_power)
game.state = game.proposal.save_state
delete game.proposal
}
+/* NEGOTIATION - PING PLAYER */
+
function goto_ping() {
game.proposal = { save_power: game.power, save_state: game.state }
game.state = "ping"
}
states.ping = {
+ dont_snap: true,
inactive: "ping",
prompt() {
prompt("Ping which power to respond to chat?")
for (let pow of all_powers)
- if (pow !== game.power)
+ if (!is_controlled_power(game.power, pow))
gen_action_power(pow)
view.actions.undo = 1
},
@@ -6444,6 +6532,7 @@ states.ping = {
}
states.pong = {
+ dont_snap: true,
inactive: "respond",
prompt() {
prompt(power_name[game.proposal.save_power] + " has requested your response in chat.")
@@ -6468,6 +6557,187 @@ states.pong = {
},
}
+/* VALIDATE PROMISES */
+
+function should_validate_promise(me, you, phase) {
+ for (let deal of game.deals) {
+ if (deal[DI_A_POWER] === me && deal[DI_B_POWER] === you && (deal[DI_A_PHASE] & phase))
+ return true
+ if (deal[DI_B_POWER] === me && deal[DI_A_POWER] === you && (deal[DI_B_PHASE] & phase))
+ return true
+ }
+ return false
+}
+
+function goto_validate_promise(phase, resume) {
+ game.proposal = 0
+ for (let other of all_powers)
+ if (should_validate_promise(game.power, other, phase))
+ game.proposal |= (1 << other)
+ resume()
+}
+
+function resume_validate_promise(state, end) {
+ if (game.proposal) {
+ for (let other of all_powers) {
+ if (game.proposal & (1 << other)) {
+ game.proposal &= ~(1 << other)
+ game.state = state
+ set_active_to_power(other)
+ return
+ }
+ }
+ }
+ delete game.proposal
+ end()
+}
+
+// TODO: validate_political_card
+
+function goto_validate_hussars() {
+ game.proposal = 0
+ for (let other of all_powers)
+ if (should_validate_promise(game.power, other, V_HUSSARS))
+ game.proposal |= (1 << other)
+ resume_validate_hussars()
+}
+
+function resume_validate_hussars() {
+ resume_validate_promise("validate_hussars", end_place_hussars)
+}
+
+states.validate_hussars = {
+ dont_snap: true,
+ inactive: "validate promise",
+ prompt() {
+ let other = current_sequence_of_play().power
+ prompt("Did " + power_name[other] + " keep their hussar promise?")
+ view.actions.yes = 1
+ view.actions.no = 1
+ },
+ yes() {
+ resume_validate_hussars()
+ },
+ no() {
+ let other = game.power
+ restore_checkpoint()
+ game.proposal = other
+ game.state = "validate_hussars_fail"
+ },
+}
+
+states.validate_hussars_fail = {
+ dont_snap: true,
+ inactive: "place hussars",
+ prompt() {
+ prompt("Promise to " + power_name[game.proposal] + " was not kept.")
+ view.actions.resume = 1
+ view.actions.undo = 0
+ },
+ resume() {
+ game.state = "place_hussars"
+ delete game.proposal
+ },
+}
+
+function should_validate_movement(power) {
+ for (let other of all_powers)
+ if (should_validate_promise(power, other, V_MOVEMENT))
+ game.proposal |= (1 << other)
+}
+
+function validate_end_movement() {
+ game.proposal = 0
+ should_validate_movement(game.power)
+ if (game.power === P_PRAGMATIC)
+ should_validate_movement(P_AUSTRIA)
+ resume_validate_movement()
+}
+
+function resume_validate_movement() {
+ resume_validate_promise("validate_movement", end_movement)
+}
+
+states.validate_movement = {
+ dont_snap: true,
+ inactive: "validate promise",
+ prompt() {
+ let other = current_sequence_of_play().power
+ prompt("Did " + power_name[other] + " keep their movement promise?")
+ view.actions.yes = 1
+ view.actions.no = 1
+ },
+ yes() {
+ resume_validate_movement()
+ },
+ no() {
+ let other = game.power
+ restore_checkpoint()
+ game.proposal = other
+ game.state = "validate_movement_fail"
+ },
+}
+
+states.validate_movement_fail = {
+ dont_snap: true,
+ inactive: "move",
+ prompt() {
+ prompt("Promise to " + power_name[game.proposal] + " was not kept. Movement phase restarted.")
+ view.actions.resume = 1
+ view.actions.undo = 0
+ },
+ resume() {
+ game.state = "movement"
+ delete game.proposal
+ },
+}
+
+function goto_validate_retreat() {
+ // TODO: all powers
+ let other = piece_power[game.selected[0]]
+ if (should_validate_promise(game.power, other, V_RETREAT)) {
+ set_active_to_power_keep_undo(other)
+ game.state = "validate_retreat"
+ } else {
+ finish_combat()
+ }
+}
+
+states.validate_retreat = {
+ dont_snap: true,
+ inactive: "validate promise",
+ prompt() {
+ let other = current_sequence_of_play().power
+ prompt("Did " + power_name[other] + " keep their retreat promise?")
+ view.actions.yes = 1
+ view.actions.no = 1
+ },
+ yes() {
+ clear_undo()
+ finish_combat()
+ },
+ no() {
+ let other = game.power
+ pop_undo()
+ game.proposal = other
+ game.state = "validate_retreat_fail"
+ },
+}
+
+states.validate_retreat_fail = {
+ dont_snap: true,
+ inactive: "retreat defeated general",
+ prompt() {
+ prompt("Promise to " + power_name[game.proposal] + " was not kept.")
+ view.actions.resume = 1
+ view.actions.undo = 0
+ },
+ resume() {
+ game.state = "retreat"
+ delete game.proposal
+ },
+}
+
/* SETUP */
const POWER_FROM_SETUP_STAGE = [
@@ -6974,8 +7244,8 @@ exports.view = function (state, player) {
view.actions.undo = 0
}
- // subsidy contracts actions for active player
- if (!game.proposal && !is_intro() && view.turn > 0) {
+ // negotiation actions for active player
+ if (game.proposal === undefined && !is_intro() && view.turn > 0) {
if (may_cancel_subsidy())
view.actions.cancel_subsidy = 1
if (!is_intro())
@@ -6998,11 +7268,9 @@ exports.action = function (state, _player, action, arg) {
if (action === "undo" && game.undo && game.undo.length > 0) {
pop_undo()
} else if (action === "propose_subsidy") {
- push_undo()
- goto_propose_subsidy()
+ goto_propose_subsidy(game.power)
} else if (action === "cancel_subsidy") {
- push_undo()
- goto_cancel_subsidy()
+ goto_cancel_subsidy(game.power)
} else if (action === "propose_deal") {
goto_propose_deal(game.power, arg)
} else if (action === "ping") {
@@ -7013,6 +7281,15 @@ exports.action = function (state, _player, action, arg) {
return game
}
+exports.dont_snap = function (state) {
+ let dont_snap = states[state.state].dont_snap
+ if (typeof dont_snap === "function") {
+ game = state
+ return dont_snap()
+ }
+ return !!dont_snap
+}
+
/* COMMON FRAMEWORK */
function goto_game_over(result, victory_reason) {
@@ -7081,6 +7358,24 @@ function log_br() {
/* COMMON LIBRARY */
+function save_checkpoint() {
+ clear_undo()
+ push_undo()
+ game.restart = game.undo.pop()
+}
+
+function restore_checkpoint() {
+ game.undo = [ game.restart ]
+ delete game.restart
+ pop_undo()
+ push_undo()
+ game.restart = game.undo.pop()
+}
+
+function clear_checkpoint() {
+ delete game.restart
+}
+
function clear_undo() {
game.undo = []
}
@@ -7090,6 +7385,8 @@ function push_undo() {
let copy = {}
for (let k in game) {
let v = game[k]
+ if (k === "subsidies")
+ continue
if (k === "deals")
continue
if (k === "restart")
@@ -7110,12 +7407,14 @@ function pop_undo() {
if (game.undo) {
let save_log = game.log
let save_undo = game.undo
+ let save_subsidies = game.subsidies
let save_deals = game.deals
let save_restart = game.restart
game = save_undo.pop()
save_log.length = game.log
game.log = save_log
game.undo = save_undo
+ game.subsidies = save_subsidies
game.deals = save_deals
game.restart = save_restart
}