summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2023-05-07 16:16:10 +0200
committerTor Andersson <tor@ccxvii.net>2023-05-24 21:06:18 +0200
commit0df5b3971cf65d23258ae080e8c1163077920fe6 (patch)
tree4405a2421b64766723e0df6ca5ea611e935865c4
parent06baa3704a7e7466fb5fca11aba6a81a43f5ea3a (diff)
downloadred-flag-over-paris-0df5b3971cf65d23258ae080e8c1163077920fe6.tar.gz
Breach the Crisis Tracks!
-rw-r--r--play.js12
-rw-r--r--rules.js204
2 files changed, 168 insertions, 48 deletions
diff --git a/play.js b/play.js
index af717c9..95112eb 100644
--- a/play.js
+++ b/play.js
@@ -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, "&amp;")
text = text.replace(/</g, "&lt;")
text = text.replace(/>/g, "&gt;")
- 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)
diff --git a/rules.js b/rules.js
index ac7e15e..49014ac 100644
--- a/rules.js
+++ b/rules.js
@@ -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,