diff options
author | Tor Andersson <tor@ccxvii.net> | 2023-05-07 16:16:10 +0200 |
---|---|---|
committer | Tor Andersson <tor@ccxvii.net> | 2023-05-24 21:06:18 +0200 |
commit | 0df5b3971cf65d23258ae080e8c1163077920fe6 (patch) | |
tree | 4405a2421b64766723e0df6ca5ea611e935865c4 | |
parent | 06baa3704a7e7466fb5fca11aba6a81a43f5ea3a (diff) | |
download | red-flag-over-paris-0df5b3971cf65d23258ae080e8c1163077920fe6.tar.gz |
Breach the Crisis Tracks!
-rw-r--r-- | play.js | 12 | ||||
-rw-r--r-- | rules.js | 204 |
2 files changed, 168 insertions, 48 deletions
@@ -1,7 +1,6 @@ "use strict" -// TODO: layout cubes in spaces in two groups (max 4 each space) -// TODO: layout cubes on tracks/pools as one space +// TODO: show red out of play cubes on board let layout = [] let space_layout_cube = [] @@ -333,6 +332,12 @@ function sub_card_name(match, p1, offset, string) { return `<span class="tip" onmouseenter="on_focus_card_tip(${c})" onmouseleave="on_blur_card_tip()">${n}</span>` } +function sub_space_name(match, p1, offset, string) { + let c = p1 | 0 + let n = space_names[c] + return n +} + function on_log(text) { let p = document.createElement("div") @@ -344,7 +349,8 @@ function on_log(text) { text = text.replace(/&/g, "&") text = text.replace(/</g, "<") text = text.replace(/>/g, ">") - text = text.replace(/#(\d+)/g, sub_card_name) + text = text.replace(/C(\d+)/g, sub_card_name) + text = text.replace(/S(\d+)/g, sub_space_name) if (text.match(/^\.h1/)) { text = text.substring(4) @@ -21,6 +21,12 @@ const last_commune_disc = 37 const first_versailles_disc = 38 const last_versailles_disc = 39 +const crisis_names = [ + "Escalation", + "Tension", + "Final Crisis", +] + const card_names = [ "Initiative", "Jules Ducatel", @@ -136,6 +142,8 @@ const BLUE_CRISIS_TRACK = [25, 26, 27, 28] const BLUE_BONUS_CUBES = [29, 30, 31] const PRUSSIAN_COLLABORATION = [32, 33, 34] +const OUT_OF_PLAY = -1 + const NATIONAL_ASSEMBLY = 0 const ROYALISTS = 1 const REPUBLICANS = 2 @@ -253,6 +261,22 @@ function recycle_card(c) { game.strategy_deck.unshift(c) } +function add_political_vp(side, amount) { + if (side === COMMUNE) + game.political_vp += amount + else + game.political_vp -= amount + game.political_vp = Math.min(5, Math.max(-5, game.political_vp)) +} + +function add_military_vp(side, amount) { + if (side === COMMUNE) + game.military_vp += amount + else + game.military_vp -= amount + game.military_vp = Math.min(5, Math.max(-5, game.military_vp)) +} + function is_objective_card(c) { return c >= 42 && c <= 53 } @@ -426,6 +450,15 @@ function is_control_dimension(dim) { return true } +function count_cubes(s) { + return count_commune_cubes(s) + count_versailles_cubes(s) +} + +function for_each_cube(s, f) { + for_each_commune_cube(s, f) + for_each_versailles_cube(s, f) +} + function count_commune_cubes(s) { let n = 0 for (let p = first_commune_cube; p <= last_commune_cube; ++p) @@ -537,7 +570,7 @@ function find_friendly_disc(s) { } function find_available_disc() { - return find_friendly_disc(-1) + return find_friendly_disc(OUT_OF_PLAY) } function has_commune_cube(s) { @@ -572,8 +605,14 @@ function is_disc(p) { return p >= 36 } +function can_place_cube_in_any(list) { + for (let s of list) + if (can_place_cube(s)) + return true + return false +} + function can_place_cube(s) { - console.log("can_place_cube", s, find_available_cube(), count_friendly_cubes(s)) return find_available_cube() >= 0 && count_friendly_cubes(s) < 4 } @@ -598,16 +637,16 @@ function remove_piece(p) { else if (game.red_momentum >= 3 && count_commune_cubes(RED_CUBE_POOL[2]) < 1) game.pieces[p] = RED_CUBE_POOL[2] else - game.pieces[p] = -1 + game.pieces[p] = OUT_OF_PLAY } else if (is_versailles_cube(p)) { game.pieces[p] = BLUE_CUBE_POOL } else { - game.pieces[p] = -1 + game.pieces[p] = OUT_OF_PLAY } } function remove_piece_from_play(p) { - game.pieces[p] = -1 + game.pieces[p] = OUT_OF_PLAY } function place_piece(p, s) { @@ -798,7 +837,7 @@ states.censorship_phase = { gen_action("card", c) }, card(c) { - log(`Discarded #${c}.`) + log(`Discarded C${c}.`) discard_card(c) if (game.active === game.initiative) game.active = enemy_player() @@ -824,7 +863,7 @@ function resume_strategy_phase() { } } -function has_final_crisis_card() { +function player_final_crisis_card() { if (game.active === COMMUNE) return game.red_final return game.blue_final @@ -834,14 +873,32 @@ states.strategy_phase = { inactive: "play a card", prompt() { view.prompt = "Play a card." + for (let c of player_hand()) gen_action_card(c) + + if (player_hand().length > 0) { + let final = player_final_crisis_card() + if (final > 0) + gen_action_card(final) + + if (game.discard > 0) + if (can_play_event(game.discard)) + gen_action_card(game.discard) + } + }, card(c) { push_undo() - log(`Played #${c}.`) game.what = c - game.state = "play_card" + if (c === 17 || c === 34) { + game.state = "play_final_discard" + } else if (c === game.discard) { + game.state = "play_discard" + } else { + log(`Played C${c}.`) + game.state = "play_card" + } }, } @@ -849,6 +906,8 @@ states.play_card = { prompt() { let c = game.what + view.prompt = card_names[game.what] + ": Play for Event, Operations, or Momentum." + view.selected_card = game.what view.actions.political = 1 @@ -860,19 +919,12 @@ states.play_card = { view.actions.event = 0 if (can_advance_momentum()) { - view.actions.momentum = 1 + // view.actions.momentum = 1 if (game.active === COMMUNE) view.actions.red_momentum = 1 else view.actions.blue_momentum = 1 } - - if (game.discard > 0 && can_play_event(game.discard)) - gen_action_card(game.discard) - - let final = has_final_crisis_card() - if (final > 0) - gen_action_card(final) }, event() { push_undo() @@ -882,13 +934,13 @@ states.play_card = { }, political() { push_undo() - log("Ops.") + log(card_ops[game.what] + " Ops.") discard_card(game.what) goto_operations(card_ops[game.what], POLITICAL) }, military() { push_undo() - log("Ops.") + log(card_ops[game.what] + " Ops.") discard_card(game.what) goto_operations(card_ops[game.what], MILITARY) }, @@ -910,22 +962,39 @@ states.play_card = { blue_momentum() { this.momentum() }, +} + +states.play_discard = { + prompt() { + view.prompt = card_names[game.what] + ": Discard a card." + for (let c of player_hand()) + gen_action_card(c) + view.selected_card = game.what + }, card(c) { push_undo() - log(`Discarded for #${c}.`) - if (c === game.discard) { - discard_card(game.what) - game.what = c - goto_play_event(c) - } else { - discard_card(game.what) - game.what = c - game.state = "play_final" - } + log("Discarded C" + c + " to play C" + game.what + ".") + discard_card(c) + goto_play_event(game.what) + }, +} + +states.play_final_discard = { + prompt() { + view.prompt = card_names[game.what] + ": Discard a card." + for (let c of player_hand()) + gen_action_card(c) + view.selected_card = game.what + }, + card(c) { + push_undo() + log("Discarded C" + c + " to play C" + game.what + ".") + discard_card(c) + game.state = "play_final_ops" }, } -states.play_final = { +states.play_final_ops = { prompt() { view.prompt = card_names[game.what] + ": Use up to 4 Operations Points." view.selected_card = game.what @@ -935,11 +1004,13 @@ states.play_final = { political() { push_undo() discard_final() + log("4 Ops.") goto_operations(4, POLITICAL) }, military() { push_undo() discard_final() + log("4 Ops.") goto_operations(4, MILITARY) }, } @@ -956,22 +1027,22 @@ function discard_final() { function advance_revolutionary_momentum(x) { game.red_momentum += x for (let i = game.red_momentum; i < 3; ++i) - for_each_commune_cube(RED_CUBE_POOL[i], p => game.pieces[p] = -1) + for_each_commune_cube(RED_CUBE_POOL[i], remove_piece_from_play) game.momentum_active = game.active game.active = VERSAILLES - if (x > 0 && game.red_momentum >= 2) + if (x > 0 && game.red_momentum >= 2 && can_place_cube_in_any(INSTITUTIONAL)) game.state = "revolutionary_momentum_trigger" - else + else end_momentum_trigger() } function advance_prussian_collaboration(x) { game.blue_momentum += x for (let i = 0; i < game.blue_momentum; ++i) - for_each_versailles_cube(PRUSSIAN_COLLABORATION[i], p => game.pieces[p] = BLUE_CUBE_POOL) + for_each_versailles_cube(PRUSSIAN_COLLABORATION[i], remove_piece) game.momentum_active = game.active game.active = COMMUNE - if (x > 0 && game.blue_momentum >= 2) + if (x > 0 && game.blue_momentum >= 2 && can_place_cube_in_any(PUBLIC_OPINION)) game.state = "prussian_collaboration_trigger" else end_momentum_trigger() @@ -1017,7 +1088,48 @@ function end_momentum_trigger() { if (game.vm) vm_next() else - resume_strategy_phase() + end_card_play() +} + +// === CRISIS TRACK & CUBE POOLS === + +function end_card_play() { + assess_crisis_breach_all() + resume_strategy_phase() +} + +function assess_crisis_breach_all() { + assess_crisis_breach(COMMUNE, 0, RED_CRISIS_TRACK[1], RED_BONUS_CUBES[0], 2, 0) + assess_crisis_breach(COMMUNE, 1, RED_CRISIS_TRACK[2], RED_BONUS_CUBES[1], 2, 0) + assess_crisis_breach(COMMUNE, 2, RED_CRISIS_TRACK[3], RED_BONUS_CUBES[2], 2, BLUE_CRISIS_TRACK[3]) + assess_crisis_breach(VERSAILLES, 0, BLUE_CRISIS_TRACK[1], BLUE_BONUS_CUBES[0], 2, 0) + assess_crisis_breach(VERSAILLES, 1, BLUE_CRISIS_TRACK[2], BLUE_BONUS_CUBES[1], 1, 0) + 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) { + log(side + " Breached " + crisis_names[i] + "!") + if (enemy_final_crisis) { + if (count_cubes(enemy_final_crisis) === 2) { + for_each_cube(bonus_cubes, remove_piece) + log("-1 Political VP") + add_political_vp(side, -1) + } else { + for_each_cube(bonus_cubes, remove_piece_from_play) + } + } else { + for_each_cube(bonus_cubes, remove_piece) + } + } } // === OPERATIONS === @@ -1029,7 +1141,7 @@ function goto_operations(count, spaces) { } function goto_final_crisis_discard(c, spaces) { - log("Played #" + c + ".") + log("Played C" + c + ".") game.state = "discard_final_crisis" game.count = 4 game.spaces = spaces @@ -1045,7 +1157,7 @@ states.discard_final_crisis = { }, card(c) { push_undo() - log(`Discarded #${c} to play Final Crisis card for ops.`) + log(`Discarded C${c} to play Final Crisis card for ops.`) array_remove_item(player_hand(), c) game.discard = c goto_operations_remove() @@ -1107,7 +1219,7 @@ states.operations_remove = { for_each_enemy_disc(s, gen_action_piece) } } - view.actions.end_remove = 1 + view.actions.place = 1 }, piece(p) { push_undo() @@ -1138,7 +1250,7 @@ states.operations_remove = { resume_operations_remove() }, - end_remove() { + place() { push_undo() goto_operations_place() }, @@ -1178,7 +1290,7 @@ function attempt_remove_piece(extra) { let str = military_strength(s) + extra let ops = card_ops[c] log("Military strength " + str + ".") - log("Removed card #" + c + " for " + ops + " strength.") + log("Removed card C" + c + " for " + ops + " strength.") if (str >= ops) remove_piece(p) game.who = -1 @@ -1267,7 +1379,7 @@ states.operations_done = { function end_operations() { clear_undo() - resume_strategy_phase() + end_card_play() } // === SET ASIDE CARDS === @@ -1298,7 +1410,7 @@ function goto_play_event(c) { function end_event() { game.vm = null - resume_strategy_phase() + end_card_play() } function goto_vm(proc) { @@ -1872,9 +1984,11 @@ exports.setup = function (seed, scenario, options) { ROYALISTS, PRESS, // Commune discs - -1, -1, + OUT_OF_PLAY, + OUT_OF_PLAY, // Versailles discs - -1, -1, + OUT_OF_PLAY, + OUT_OF_PLAY, ], count: 0, |