From a35c243e4a11ee2932f83380e337a21ea82be497 Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Sun, 7 May 2023 19:26:20 +0200 Subject: Final Crisis. Move events. --- events.txt | 18 ++- rules.js | 472 +++++++++++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 421 insertions(+), 69 deletions(-) diff --git a/events.txt b/events.txt index 1d1446f..75a61b1 100644 --- a/events.txt +++ b/events.txt @@ -69,12 +69,16 @@ CARD 13 - Général Ernest de Cissey ops 4 FORT_D_ISSY CARD 14 - Colonel de Lochner - switch ["mont_valerien","butte_montmartre"] - case "mont_valerien" + if (is_versailles_control(MONT_VALERIEN)) + switch ["mont_valerien","butte_montmartre"] + case "mont_valerien" + ops 3 MONT_VALERIEN + case "butte_montmartre" + ops 3 BUTTE_MONTMARTRE + endswitch + else ops 3 MONT_VALERIEN - case "butte_montmartre" - ops 3 BUTTE_MONTMARTRE - endswitch + endif CARD 15 - Jules Favre increase_prussian_collaboration @@ -241,7 +245,7 @@ CARD 45 - Fighting in Issy Village CARD 46 - Battle of Mont-Valérien ops 3 FORTS - if (game.current === COMMUNE) + if (game.active === COMMUNE) decrease_prussian_collaboration else increase_prussian_collaboration @@ -274,7 +278,7 @@ CARD 49 - Pius IX CARD 50 - Socialist International ops 2 PUBLIC_OPINION - if (game.current === COMMUNE) + if (game.active === COMMUNE) increase_revolutionary_momentum else decrease_revolutionary_momentum 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 ], -- cgit v1.2.3