summaryrefslogtreecommitdiff
path: root/rules.js
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2023-05-07 19:26:20 +0200
committerTor Andersson <tor@ccxvii.net>2023-05-24 21:06:18 +0200
commita35c243e4a11ee2932f83380e337a21ea82be497 (patch)
tree57607dccefd57885f2aba4d1568682be60d00975 /rules.js
parent23576f10dea881fe483287760af97b20666c6d16 (diff)
downloadred-flag-over-paris-a35c243e4a11ee2932f83380e337a21ea82be497.tar.gz
Final Crisis. Move events.
Diffstat (limited to 'rules.js')
-rw-r--r--rules.js472
1 files changed, 410 insertions, 62 deletions
diff --git a/rules.js b/rules.js
index 04754b2..1b4cb65 100644
--- a/rules.js
+++ b/rules.js
@@ -356,12 +356,6 @@ function player_hand() {
return game.blue_hand
}
-function player_set_aside(current) {
- if (game.active === COMMUNE)
- return game.red_set_aside
- return game.blue_set_aside
-}
-
function is_space(s) {
return s >= first_space && s <= last_space
}
@@ -494,9 +488,16 @@ function is_adjacent_to_control(here) {
return false
}
-function is_control_dimension(dim) {
+function is_commune_control_dimension(dim) {
+ for (let s of dim)
+ if (!is_commune_control(s))
+ return false
+ return true
+}
+
+function is_versailles_control_dimension(dim) {
for (let s of dim)
- if (!is_control(s))
+ if (!is_versailles_control(s))
return false
return true
}
@@ -636,6 +637,10 @@ function has_enemy_cube(s) {
return find_enemy_cube(s) >= 0
}
+function has_friendly_cube(s) {
+ return find_friendly_cube(s) >= 0
+}
+
function has_enemy_disc(s) {
return find_enemy_disc(s) >= 0
}
@@ -675,7 +680,7 @@ function can_place_cube(s) {
}
function can_place_disc(s) {
- return find_available_disc() >= 0 && count_friendly_discs(s) < 1
+ return count_friendly_discs(s) < 1
}
function can_replace_cube(s) {
@@ -707,7 +712,7 @@ function remove_piece_from_play(p) {
game.pieces[p] = OUT_OF_PLAY
}
-function place_piece(p, s) {
+function move_piece(p, s) {
game.pieces[p] = s
}
@@ -811,25 +816,69 @@ function versailles_political_vp() {
return -game.political_vp
}
+function commune_military_vp() {
+ return game.military_vp
+}
+
+function versailles_military_vp() {
+ return -game.military_vp
+}
+
function where_present(list) {
return list.filter(s => is_present(s))
}
+// === START ROUND ===
+
+function start_round() {
+ if (game.round === 3) {
+ goto_final_crisis()
+ return
+ }
+
+ if (count_cubes(RED_CRISIS_TRACK[3]) < 2 && count_cubes(BLUE_CRISIS_TRACK[3]) < 2) {
+ goto_final_crisis()
+ return
+ }
+
+ game.round += 1
+
+ log_h1("Round " + game.round)
+
+ let n = 4
+ if (game.scenario === "Censorship")
+ n = 5
+
+ for (let i = 0; i < n; ++i) {
+ game.red_hand.push(game.strategy_deck.pop())
+ game.blue_hand.push(game.strategy_deck.pop())
+ }
+
+ for (let i = 0; i < 2; ++i) {
+ game.red_objective.push(game.objective_deck.pop())
+ game.blue_objective.push(game.objective_deck.pop())
+ }
+
+ game.active = "Both"
+ game.state = "choose_objective_card"
+}
+
+
// === CHOOSE OBJECTIVE CARD ===
states.choose_objective_card = {
inactive: "choose an objective card",
- prompt(current) {
+ prompt(player) {
view.prompt = "Choose an Objective card to keep."
- if (current === COMMUNE)
+ if (player === COMMUNE)
for (let c of game.red_objective)
gen_action_card(c)
else
for (let c of game.blue_objective)
gen_action_card(c)
},
- card(c, current) {
- if (current === COMMUNE)
+ card(c, player) {
+ if (player === COMMUNE)
game.red_objective = [ c ]
else
game.blue_objective = [ c ]
@@ -881,7 +930,9 @@ states.initiative_phase = {
function end_initiative_phase() {
game.active = game.initiative
- if (game.scenario === "Censorship")
+ if (game.round === 4)
+ goto_final_crisis_events()
+ else if (game.scenario === "Censorship")
goto_censorship_phase()
else
goto_strategy_phase()
@@ -1159,7 +1210,10 @@ function end_momentum_trigger() {
function end_card_play() {
assess_crisis_breach_all()
- resume_strategy_phase()
+ if (game.round === 4)
+ resume_final_crisis()
+ else
+ resume_strategy_phase()
}
function assess_crisis_breach_all() {
@@ -1171,13 +1225,6 @@ function assess_crisis_breach_all() {
assess_crisis_breach(VERSAILLES, 2, BLUE_CRISIS_TRACK[3], BLUE_BONUS_CUBES[2], 2, RED_CRISIS_TRACK[3])
}
-function has_breached_final_crisis_track(track) {
- if (side === COMMUNE)
- return count_cubes(RED_BONUS_CUBES[2])
- else
- return !has_versailles_cube(BLUE_BONUS_CUBES[2])
-}
-
function assess_crisis_breach(side, i, crisis_track, bonus_cubes, start_count, enemy_final_crisis) {
let count = count_cubes(crisis_track)
if (count < start_count && count_cubes(bonus_cubes) > 0) {
@@ -1442,8 +1489,10 @@ states.operations_done = {
}
function end_operations() {
- clear_undo()
- end_card_play()
+ if (game.vm)
+ vm_next()
+ else
+ end_card_play()
}
// === SET ASIDE CARDS ===
@@ -1474,7 +1523,7 @@ function resume_pivotal_space_bonus_actions() {
if (game.spaces.length > 0)
game.state = "pivotal_space_bonus_actions"
else
- goto_objective_card_scoring()
+ goto_crisis_dimension_scoring()
}
states.pivotal_space_bonus_actions = {
@@ -1578,7 +1627,7 @@ states.spread_influence = {
game.who = p
},
space(s) {
- place_piece(game.who, s)
+ move_piece(game.who, s)
game.who = -1
if (--game.count === 0)
resume_pivotal_space_bonus_actions()
@@ -1602,6 +1651,51 @@ states.turncoat = {
},
}
+// === CRISIS DIMENSION SCORING ===
+
+function goto_crisis_dimension_scoring() {
+ update_presence_and_control()
+ game.spaces = PIVOTAL.slice()
+ game.active = game.initiative
+ game.state = "crisis_dimension_scoring"
+}
+
+states.crisis_dimension_scoring = {
+ prompt() {
+ view.prompt = "Crisis Dimension Scoring!"
+ for (let s of game.spaces)
+ gen_action_space(s)
+ },
+ space(s) {
+ array_remove_item(game.spaces, s)
+ let dimension = DIMENSION_SPACES[s]
+ if (is_political_space(s)) {
+ if (is_commune_control_dimension(dimension))
+ add_political_vp(COMMUNE, 1)
+ else if (is_versailles_control_dimension(dimension))
+ add_political_vp(VERSAILLES, 1)
+ else
+ log("Nobody controlled " + DIMENSION_NAME[s] + ".")
+ } else {
+ if (is_commune_control_dimension(dimension))
+ add_military_vp(COMMUNE, 1)
+ else if (is_versailles_control_dimension(dimension))
+ add_military_vp(VERSAILLES, 1)
+ else
+ log("Nobody controlled " + DIMENSION_NAME[s] + ".")
+ }
+ if (game.spaces.length === 0)
+ end_crisis_dimension_scoring()
+ },
+}
+
+function end_crisis_dimension_scoring() {
+ if (game.round === 4)
+ goto_final_victory()
+ else
+ goto_objective_card_scoring()
+}
+
// === OBJECTIVE CARD SCORING ===
function goto_objective_card_scoring() {
@@ -1709,6 +1803,165 @@ states.objective_card_events = {
},
}
+// === FINAL CRISIS ===
+
+function goto_final_crisis() {
+ log_h1("Final Crisis")
+
+ game.red_hand = game.red_set_aside
+ if (game.red_final)
+ game.red_hand.push(game.red_final)
+ game.red_set_aside = []
+
+ game.blue_hand = game.blue_set_aside
+ if (game.blue_final)
+ game.blue_hand.push(game.blue_final)
+ game.blue_set_aside = []
+
+ game.active = "Both"
+ game.state = "final_crisis_discard"
+}
+
+states.final_crisis_discard = {
+ prompt(player) {
+ view.prompt = "Discard down to " + game.round + " cards."
+ if (player === COMMUNE) {
+ if (game.red_hand.length > game.round)
+ for (let c of game.red_hand)
+ gen_action_card(c)
+ }
+ if (player === VERSAILLES) {
+ if (game.blue_hand.length > game.round)
+ for (let c of game.blue_hand)
+ gen_action_card(c)
+ }
+ },
+ card(c, player) {
+ push_undo()
+ if (player === COMMUNE)
+ array_remove_item(game.red_hand, c)
+ if (player === VERSAILLES)
+ array_remove_item(game.blue_hand, c)
+ if (game.red_hand.length > game.round && game.blue_hand.length > game.round)
+ game.active = "Both"
+ else if (game.red_hand.length > game.round)
+ game.active = COMMUNE
+ else if (game.blue_hand.length > game.round)
+ game.active = VERSAILLES
+ else {
+ clear_undo()
+ game.round = 4
+ goto_initiative_phase()
+ }
+ },
+}
+
+function goto_final_crisis_events() {
+ clear_undo()
+ log_h2(game.active)
+ game.state = "final_crisis_events"
+}
+
+function resume_final_crisis() {
+ if (game.red_hand.length === 0 && game.blue_hand.length === 0) {
+ goto_pivotal_space_bonus_actions()
+ } else {
+ game.active = game.final_active
+ game.active = enemy_player()
+ }
+}
+
+states.final_crisis_events = {
+ inactive: "play an event",
+ prompt() {
+ view.prompt = "Play a card."
+ for (let c of player_hand())
+ gen_action_card(c)
+ },
+ card(c) {
+ game.final_active = game.active
+ discard_card(c)
+ if (game.active === VERSAILLES && is_commune_card(c)) {
+ game.what = c
+ game.active = COMMUNE
+ game.state = "final_crisis_opponent_event"
+ }
+ else if (game.active === COMMUNE && is_versailles_card(c)) {
+ game.what = c
+ game.active = VERSAILLES
+ game.state = "final_crisis_opponent_event"
+ }
+ else {
+ goto_play_event(c)
+ }
+ },
+}
+
+states.final_crisis_opponent_event = {
+ inactive: "play an event",
+ prompt() {
+ view.prompt = card_names[game.what] + ": Play or skip event."
+ view.selected_card = game.what
+ view.actions.event = 1
+ view.actions.skip = 1
+ },
+ event() {
+ goto_play_event(game.what)
+ },
+ skip() {
+ end_card_play()
+ },
+}
+
+function goto_final_victory() {
+ update_presence_and_control()
+
+ if (game.red_momentum === 3)
+ add_political_vp(COMMUNE, 1)
+ if (game.blue_momentum === 3)
+ add_political_vp(VERSAILLES, 1)
+
+ if (versailles_military_vp() > commune_military_vp())
+ return goto_game_over(VERSAILLES, "Versailles won!")
+ if (commune_political_vp() > versailles_political_vp())
+ return goto_game_over(COMMUNE, "Commune won!")
+
+ let v = 0
+ let c = 0
+
+ if (versailles_military_vp() + versailles_political_vp() > commune_military_vp() + commune_political_vp())
+ ++v
+ if (commune_military_vp() + commune_political_vp() > versailles_military_vp() + versailles_political_vp())
+ ++c
+ if (game.blue_fulfilled > game.red_fulfilled)
+ ++v
+ if (game.red_fulfilled > game.blue_fulfilled)
+ ++c
+
+ let nv = 0
+ let nc = 0
+ for (let s of PIVOTAL) {
+ if (is_commune_control(s))
+ nc++
+ if (is_versailles_control(s))
+ nv++
+ }
+ if (nv > nc)
+ v++
+ if (nc > nv)
+ c++
+
+ if (game.initiative === VERSAILLES)
+ v++
+ else
+ c++
+
+ if (v > c)
+ return goto_game_over(VERSAILLES, "Versailles won!")
+
+ return goto_game_over(COMMUNE, "Commune won!")
+}
+
// === EVENTS ===
function goto_play_event(c) {
@@ -1956,16 +2209,18 @@ function vm_place_disc() {
}
function vm_move_up_to() {
- game.vm.count = 1
- game.vm.a = vm_operand_spaces(1)
- game.vm.b = vm_operand_spaces(2)
+ game.vm.count = vm_operand(1)
+ game.vm.a = vm_operand_spaces(2)
+ game.vm.b = vm_operand_spaces(3)
+ game.who = -1
game.state = "vm_move"
}
function vm_move_between_up_to() {
- game.vm.count = 1
- game.vm.a = vm_operand_spaces(1)
- game.vm.b = vm_operand_spaces(2)
+ game.vm.count = vm_operand(1)
+ game.vm.a = vm_operand_spaces(2)
+ game.vm.b = vm_operand_spaces(3)
+ game.who = -1
game.state = "vm_move_between"
}
@@ -2115,10 +2370,37 @@ function can_vm_place_disc() {
}
function goto_vm_place_disc() {
- if (can_vm_place_disc())
+ if (can_vm_place_disc()) {
+ if (find_available_disc() < 0)
+ game.state = "vm_move_disc"
+ else
+ game.state = "vm_place_disc"
+ } else {
+ vm_next()
+ }
+}
+
+states.vm_move_disc = {
+ prompt() {
+ event_prompt("Remove a disc to place it elsewhere.")
+ if (game.vm.upto)
+ view.actions.skip = 1
+ if (game.active === COMMUNE)
+ for (let p = first_commune_disc; p <= last_commune_disc; ++p)
+ gen_action_piece(p)
+ else
+ for (let p = first_versailles_disc; p <= last_versailles_disc; ++p)
+ gen_action_piece(p)
+ },
+ piece(p) {
+ push_undo()
+ remove_disc(p)
game.state = "vm_place_disc"
- else
+ },
+ skip() {
+ push_undo()
vm_next()
+ },
}
states.vm_place_disc = {
@@ -2211,6 +2493,91 @@ states.vm_remove = {
},
}
+function can_vm_move() {
+ let from = false
+ let to = false
+ for (let s of game.vm.a)
+ if (!game.vm.b.includes(s) && has_friendly_cube(s))
+ from = true
+ for (let s of game.vm.b)
+ if (count_friendly_cubes(s) < 4)
+ to = true
+ return from && to
+}
+
+function goto_vm_move() {
+ if (can_vm_move())
+ game.state = "vm_move"
+ else
+ vm_next()
+}
+
+states.vm_move = {
+ prompt() {
+ event_prompt("Move up to " + game.vm.count + " cubes.")
+ view.actions.skip = 1
+ if (game.who < 0) {
+ for (let s of game.vm.a)
+ if (!game.vm.b.includes(s))
+ for_each_friendly_cube(s, gen_action_piece)
+ } else {
+ game.selected_cube = game.who
+ for (let s of game.vm.b)
+ if (count_friendly_cubes(s) < 4)
+ gen_action_space(s)
+ }
+ },
+ piece(p) {
+ push_undo()
+ game.who = p
+ },
+ space(s) {
+ move_piece(game.who, s)
+ game.who = -1
+ if (--game.vm.count === 0 || !can_vm_move())
+ vm_next()
+ },
+ skip() {
+ push_undo()
+ vm_next()
+ },
+}
+
+states.vm_move_between = {
+ prompt() {
+ event_prompt("Move up to " + game.vm.count + " cubes between spaces.")
+ view.actions.skip = 1
+ if (game.who < 0) {
+ for (let s of game.vm.a)
+ for_each_friendly_cube(s, gen_action_piece)
+ for (let s of game.vm.b)
+ for_each_friendly_cube(s, gen_action_piece)
+ } else {
+ game.selected_cube = game.who
+ for (let s of game.vm.a)
+ if (s !== game.pieces[game.who] && count_friendly_cubes(s) < 4)
+ gen_action_space(s)
+ for (let s of game.vm.b)
+ if (s !== game.pieces[game.who] && count_friendly_cubes(s) < 4)
+ gen_action_space(s)
+ }
+ },
+ piece(p) {
+ push_undo()
+ game.who = p
+ },
+ space(s) {
+ move_piece(game.who, s)
+ game.who = -1
+ if (--game.vm.count === 0 || !can_vm_move())
+ vm_next()
+ },
+ skip() {
+ push_undo()
+ vm_next()
+ },
+}
+
// === COMPLICATED EVENT STATES ===
function can_play_freemason_parade() {
@@ -2326,29 +2693,6 @@ exports.setup = function (seed, scenario, options) {
return game
}
-function start_round() {
- game.round += 1
-
- log_h1("Round " + game.round)
-
- let n = 4
- if (game.scenario === "Censorship")
- n = 5
-
- for (let i = 0; i < n; ++i) {
- game.red_hand.push(game.strategy_deck.pop())
- game.blue_hand.push(game.strategy_deck.pop())
- }
-
- for (let i = 0; i < 2; ++i) {
- game.red_objective.push(game.objective_deck.pop())
- game.blue_objective.push(game.objective_deck.pop())
- }
-
- game.active = "Both"
- game.state = "choose_objective_card"
-}
-
// === VIEW ===
exports.is_checkpoint = function (a, b) {
@@ -2439,9 +2783,9 @@ exports.action = function (state, player, action, arg) {
exports.resign = function (state, player) {
game = state
if (game.state !== "game_over") {
- if (current === COMMUNE)
+ if (player === COMMUNE)
goto_game_over(VERSAILLES, "Commune resigned.");
- if (current === VERSAILLES)
+ if (player === VERSAILLES)
goto_game_over(COMMON, "Versailles resigned.");
}
return game
@@ -2882,12 +3226,16 @@ CODE[13] = [ // Général Ernest de Cissey
]
CODE[14] = [ // Colonel de Lochner
+ [ vm_if, ()=>(is_versailles_control(MONT_VALERIEN)) ],
[ vm_switch, ["mont_valerien","butte_montmartre"] ],
[ vm_case, "mont_valerien" ],
[ vm_ops, 3, MONT_VALERIEN ],
[ vm_case, "butte_montmartre" ],
[ vm_ops, 3, BUTTE_MONTMARTRE ],
[ vm_endswitch ],
+ [ vm_else ],
+ [ vm_ops, 3, MONT_VALERIEN ],
+ [ vm_endif ],
[ vm_return ],
]
@@ -3105,7 +3453,7 @@ CODE[45] = [ // Fighting in Issy Village
CODE[46] = [ // Battle of Mont-Valérien
[ vm_ops, 3, FORTS ],
- [ vm_if, ()=>(game.current === COMMUNE) ],
+ [ vm_if, ()=>(game.active === COMMUNE) ],
[ vm_decrease_prussian_collaboration ],
[ vm_else ],
[ vm_increase_prussian_collaboration ],
@@ -3145,7 +3493,7 @@ CODE[49] = [ // Pius IX
CODE[50] = [ // Socialist International
[ vm_ops, 2, PUBLIC_OPINION ],
- [ vm_if, ()=>(game.current === COMMUNE) ],
+ [ vm_if, ()=>(game.active === COMMUNE) ],
[ vm_increase_revolutionary_momentum ],
[ vm_else ],
[ vm_decrease_revolutionary_momentum ],