summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--play.css21
-rw-r--r--play.html27
-rw-r--r--play.js65
-rw-r--r--rules.js190
4 files changed, 266 insertions, 37 deletions
diff --git a/play.css b/play.css
index e284d0b..2f41c08 100644
--- a/play.css
+++ b/play.css
@@ -76,6 +76,27 @@ path.selected { stroke: yellow; }
.box.loc.action{background-color:#fff5;border-color:white;}
.box.selected{border-color:yellow;}
+#capabilities .token {
+ position: static;
+ pointer-events: auto;
+}
+#capabilities {
+ display: flex;
+ justify-content: center;
+ align-content: center;
+ flex-wrap: wrap;
+ gap: 4px;
+}
+#insurgent_momentum {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 17px;
+}
+.card.momentum {
+ width: 186px;
+ height: 261px;
+ border-radius: 12px;
+}
.piece {
position: absolute;
diff --git a/play.html b/play.html
index 7ed9757..c9735a2 100644
--- a/play.html
+++ b/play.html
@@ -141,7 +141,6 @@
<div id="PRES1" class="box president" style="top:129px;left:240px;width:76px;height:168px"></div>
<div id="PRES2" class="box president" style="top:129px;left:322px;width:78px;height:168px"></div>
<div id="PRES3" class="box president" style="top:129px;left:406px;width:76px;height:168px"></div>
-
<div id="SOP_A1" class="box" style="top:438px;left:1261px;width:74px;height:74px"></div>
<div id="SOP_A2" class="box" style="top:438px;left:1336px;width:74px;height:74px"></div>
<div id="SOP_B1" class="box" style="top:516px;left:1261px;width:74px;height:74px"></div>
@@ -150,15 +149,25 @@
<div id="SOP_C2" class="box" style="top:595px;left:1336px;width:74px;height:74px"></div>
<div id="SOP_PASS" class="box" style="top:663px;left:1112px;width:105px;height:49px"></div>
-<div id="IM1" class="box" style="top:1935px;left:14px;width:196px;height:274px"></div>
-<div id="IM2" class="box" style="top:1935px;left:220px;width:196px;height:274px"></div>
+<div id="insurgent_momentum" style="position:absolute;top:1940px;left:19px;width:1250px;height:270px">
+<div id="mom_12" class="momentum card card_12 hide"></div>
+<div id="mom_17" class="momentum card card_17 hide"></div>
+<div id="mom_22" class="momentum card card_22 hide"></div>
+<div id="mom_27" class="momentum card card_27 hide"></div>
+<div id="mom_42" class="momentum card card_42 hide"></div>
+<div id="mom_67" class="momentum card card_67 hide"></div>
+</div>
-<div id="GOVT_CAPS" class="box" style="top:461px;left:224px;width:207px;height:149px"></div>
-<div id="GOVT_BASES" class="box" style="top:371px;left:287px;width:177px;height:55px"></div>
-<div id="CARTELS_SHIPMENTS" class="box" style="top:1728px;left:1391px;width:145px;height:125px"></div>
-<div id="CARTELS_BASES" class="box" style="top:2117px;left:1373px;width:183px;height:305px"></div>
-<div id="AUC_BASES" class="box" style="top:2386px;left:446px;width:360px;height:55px"></div>
-<div id="FARC_BASES" class="box" style="top:2295px;left:446px;width:543px;height:56px"></div>
+<div id="capabilities" style="position:absolute;top:508px;left:223px;width:208px;height:102px">
+<div id="cap_first_div" class="hide token shaded first_div"></div>
+<div id="cap_ospina" class="hide token shaded ospina"></div>
+<div id="cap_tapias" class="hide token shaded tapias"></div>
+<div id="cap_seventh_sf" class="hide token shaded seventh_sf"></div>
+<div id="cap_mtn_bns" class="hide token shaded mtn_bns"></div>
+<div id="cap_black_hawks" class="hide token shaded black_hawks"></div>
+<div id="cap_ndsc" class="hide token shaded ndsc"></div>
+<div id="cap_meteoro" class="hide token shaded meteoro"></div>
+</div>
</div>
diff --git a/play.js b/play.js
index 195cb6f..5685063 100644
--- a/play.js
+++ b/play.js
@@ -44,6 +44,25 @@ const SOP_C2 = 6
const SOP_PASS = 7
const INELIGIBLE = 8
+const capability_events = [ 1, 2, 3, 7, 9, 10, 11, 13 ]
+const momentum_events = [ 12, 17, 22, 27, 42, 67 ]
+
+const CAP_1ST_DIV = 1
+const CAP_OSPINA = 2
+const CAP_TAPIAS = 3
+const CAP_7TH_SF = 7
+const CAP_MTN_BNS = 9
+const CAP_BLACK_HAWKS = 10
+const CAP_NDSC = 11
+const CAP_METEORO = 13
+
+const MOM_PLAN_COLOMBIA = 12
+const MOM_MADRID_DONORS = 17
+const MOM_ALFONSO_CANO = 22
+const MOM_MISIL_ANTIAEREO = 27
+const MOM_SENADO_CAMARA = 42
+const MOM_MEXICAN_TRAFFICKERS = 67
+
let ui = {
favicon: document.getElementById("favicon"),
header: document.querySelector("header"),
@@ -56,6 +75,24 @@ let ui = {
document.getElementById("role_FARC_+_Cartels"),
document.getElementById("role_AUC_+_Cartels"),
],
+ capabilities: {
+ [CAP_1ST_DIV]: document.getElementById("cap_first_div"),
+ [CAP_OSPINA]: document.getElementById("cap_ospina"),
+ [CAP_TAPIAS]: document.getElementById("cap_tapias"),
+ [CAP_7TH_SF]: document.getElementById("cap_seventh_sf"),
+ [CAP_MTN_BNS]: document.getElementById("cap_mtn_bns"),
+ [CAP_BLACK_HAWKS]: document.getElementById("cap_black_hawks"),
+ [CAP_NDSC]: document.getElementById("cap_ndsc"),
+ [CAP_METEORO]: document.getElementById("cap_meteoro"),
+ },
+ momentum: {
+ [MOM_PLAN_COLOMBIA]: document.getElementById("mom_12"),
+ [MOM_MADRID_DONORS]: document.getElementById("mom_17"),
+ [MOM_ALFONSO_CANO]: document.getElementById("mom_22"),
+ [MOM_MISIL_ANTIAEREO]: document.getElementById("mom_27"),
+ [MOM_SENADO_CAMARA]: document.getElementById("mom_42"),
+ [MOM_MEXICAN_TRAFFICKERS]: document.getElementById("mom_67"),
+ },
spaces: [],
control: [],
support: [],
@@ -235,6 +272,11 @@ function init_ui() {
register_action(ui.resources[AUC], "resources", AUC)
register_action(ui.resources[CARTELS], "resources", CARTELS)
+ for (let c of momentum_events)
+ register_card_tip(ui.momentum[c], c)
+ for (let c of capability_events)
+ register_card_tip(ui.capabilities[c], c)
+
for (let i = 0; i < data.spaces.length; ++i) {
let id = data.spaces[i].id
let type = data.spaces[i].type
@@ -676,6 +718,23 @@ function on_update() {
ui.tokens.president.style.left = [ 0, "254px", "337px", "420px" ][view.president]
+ for (let cap of capability_events) {
+ let shaded = set_has(view.capabilities, -cap)
+ let unshaded = set_has(view.capabilities, cap)
+ console.log("CAP", cap, shaded, unshaded)
+ if (shaded || unshaded) {
+ ui.capabilities[cap].classList.toggle("shaded", shaded)
+ ui.capabilities[cap].classList.toggle("unshaded", unshaded)
+ ui.capabilities[cap].classList.toggle("hide", false)
+ } else {
+ ui.capabilities[cap].classList.toggle("hide", true)
+ }
+ }
+
+ for (let cap of momentum_events) {
+ ui.momentum[cap].classList.toggle("hide", !set_has(view.momentum, cap))
+ }
+
if (view.propaganda > 0) {
ui.tokens.propaganda.style.top = "744px"
ui.tokens.propaganda.style.left = (1124 + 75 * (view.propaganda - 1)) + "px"
@@ -854,6 +913,12 @@ function on_update() {
action_button("undo", "Undo")
}
+function register_card_tip(e, c) {
+ e.onmouseenter = () => //console.log("FOCUS ME", c)
+ on_focus_card_tip(c)
+ e.onmouseleave = on_blur_card_tip
+}
+
function on_focus_card_tip(c) {
document.getElementById("card_tip").className = "card card_" + c
}
diff --git a/rules.js b/rules.js
index 505d851..9b1bb33 100644
--- a/rules.js
+++ b/rules.js
@@ -22,6 +22,56 @@ const NAME_GOVT_AUC = "Government + AUC"
const NAME_FARC_CARTELS = "FARC + Cartels"
const NAME_AUC_CARTELS = "AUC + Cartels"
+const capability_events = [ 1, 2, 3, 7, 9, 10, 11, 13 ]
+const momentum_events = [ 12, 17, 22, 27, 42, 67 ]
+
+const CAP_1ST_DIV = 1
+const CAP_OSPINA = 2
+const CAP_TAPIAS = 3
+const CAP_7TH_SF = 7
+const CAP_MTN_BNS = 9
+const CAP_BLACK_HAWKS = 10
+const CAP_NDSC = 11
+const CAP_METEORO = 13
+
+const MOM_PLAN_COLOMBIA = 12
+const MOM_MADRID_DONORS = 17
+const MOM_ALFONSO_CANO = 22
+const MOM_MISIL_ANTIAEREO = 27
+const MOM_SENADO_CAMARA = 42
+const MOM_MEXICAN_TRAFFICKERS = 67
+
+// Events with no shaded/unshaded variants
+const single_events = [ 19, 36, 46, 53, 54, 63, 65, 69 ]
+
+const event_name_unshaded = {
+ [CAP_1ST_DIV]: "Jointness.",
+ [CAP_OSPINA]: "COIN experts take charge.",
+ [CAP_TAPIAS]: "CO tightens civil-military bonds.",
+ [CAP_7TH_SF]: "Infrastructure protection training.",
+ [CAP_MTN_BNS]: "Elites guard high-altitude corridors.",
+ [CAP_BLACK_HAWKS]: "US helos delivered.",
+ [CAP_NDSC]: "Military-police jointness.",
+ [CAP_METEORO]: "Transport protection units.",
+}
+
+const event_name_shaded = {
+ [CAP_1ST_DIV]: "Service parochialism.",
+ [CAP_OSPINA]: "COIN strategy eludes Army.",
+ [CAP_TAPIAS]: "Civil-military rivalries fester.",
+ [CAP_7TH_SF]: "US training ineffective.",
+ [CAP_MTN_BNS]: "Equipment not delivered.",
+ [CAP_BLACK_HAWKS]: "Delivery of US helos delayed.",
+ [CAP_NDSC]: "Military-police rivalry.",
+ [CAP_METEORO]: "Transport security deemphasized.",
+ [MOM_PLAN_COLOMBIA]: "US aid focuses on drug war.",
+ [MOM_MADRID_DONORS]: "EU aid focuses on reconstruction.",
+ [MOM_ALFONSO_CANO]: "Ideologue.",
+ [MOM_MISIL_ANTIAEREO]: "MANPADs feared.",
+ [MOM_SENADO_CAMARA]: "Insurgent sympathies.",
+ [MOM_MEXICAN_TRAFFICKERS]: "New routes to US market.",
+}
+
const faction_name = [ NAME_GOVT, NAME_FARC, NAME_AUC, NAME_CARTELS ]
// Factions
@@ -205,6 +255,8 @@ exports.setup = function (seed, scenario, options) {
farc_control: 0,
govt_control: 0,
support: Array(23).fill(NEUTRAL),
+ momentum: [],
+ capabilities: [], // positive = unshaded, negative = shaded
farc_zones: [],
terror: [],
sabotage: [],
@@ -947,8 +999,10 @@ states.limop_or_event = {
function goto_event() {
log_h2(faction_name[game.current] + " - Event")
- log("TODO: Event")
- resume_event_card()
+ if (set_has(single_events, this_card()))
+ execute_event()
+ else
+ game.state = "event"
}
function goto_op_only() {
@@ -984,10 +1038,6 @@ function end_operation() {
resume_event_card()
}
-function can_use_special_activity() {
- return game.sa === 1
-}
-
function action_remove() {
push_undo()
game.save_state = game.state
@@ -1020,7 +1070,7 @@ states.remove = {
},
done() {
game.state = game.save_state
- game.save_state = 0
+ delete game.save_state
},
}
@@ -1123,9 +1173,17 @@ function select_op_space(s, cost) {
// OPERATION: TRAIN
+function can_govt_train_base(s) {
+ return count_bases(s) < 2 && count_cubes(s) >= 3
+}
+
+function can_govt_train_place(s) {
+ return is_city(s) || has_piece(s, GOVT, BASE)
+}
+
states.train = {
prompt() {
- view.prompt = "Train: Select spaces to place cubes."
+ view.prompt = "Train: Select City or Department."
if (game.sa) {
view.actions.air_lift = 1
@@ -1135,24 +1193,25 @@ states.train = {
// Any Departments or Cities
if (can_select_op_space(3)) {
for (let s = first_space; s <= last_dept; ++s)
- if (is_city(s) || has_piece(s, GOVT, BASE))
+ if (can_govt_train_place(s))
if (!is_selected_op_space(s))
gen_action_space(s)
}
- if (game.op.spaces.length > 0)
- view.actions.next = 1
+ view.actions.next = 1
},
space(s) {
push_undo()
- logi(`S${s}.`)
+ log(`Train in S${s}.`)
select_op_space(s, 3)
- game.state = "train_place"
- game.op.where = s
- game.op.count = 6
+ if (is_city(s) || has_piece(s, GOVT, BASE)) {
+ game.state = "train_place"
+ game.op.where = s
+ game.op.count = 6
+ }
},
next() {
push_undo()
@@ -1173,11 +1232,10 @@ states.train_place = {
view.actions.next = 1
},
piece(p) {
- push_undo()
place_piece(p, game.op.where)
+ update_control()
if (--game.op.count == 0)
game.state = "train"
- update_control()
},
next() {
game.op.count = 0
@@ -1190,12 +1248,14 @@ states.train_base_or_civic = {
view.prompt = "Train: Build Base or buy Civic Action in one selected space?"
view.actions.base = 1
+
if (game.resources[game.current] >= 3)
view.actions.civic = 1
else
view.actions.civic = 0
- view.actions.end_operation = 1
+ if (game.op.spaces.length > 0)
+ view.actions.end_operation = 1
},
base() {
push_undo()
@@ -1214,14 +1274,14 @@ states.train_base = {
prompt() {
if (game.op.where < 0) {
view.prompt = `Train: Replace 3 cubes with a Base.`
- for (let s of game.op.spaces) {
- if (count_bases(s) < 2 && count_cubes(s) >= 3)
- gen_action_space(s)
- }
+ for (let s = first_space; s <= last_dept; ++s)
+ if (can_govt_train_base(s))
+ if (is_selected_op_space(s) || can_select_op_space(3))
+ gen_action_space(s)
} else {
if (game.op.count < 0) {
view.prompt = `Train: All done.`
- view.actions.done = 1
+ view.actions.end_operation = 1
} else if (game.op.count > 0) {
view.prompt = `Train: ${game.op.count} cubes with a Base.`
gen_piece_in_space(game.op.where, GOVT, POLICE)
@@ -1238,7 +1298,8 @@ states.train_base = {
game.op.count = 3
},
piece(p) {
- push_undo()
+ if (!is_selected_op_space(game.op.where))
+ select_op_space(game.op.where, 3)
if (game.op.count > 0)
remove_piece(p)
else
@@ -1255,16 +1316,15 @@ states.train_civic = {
if (game.op.where < 0) {
view.prompt = `Train: Buy Civic Action.`
if (res >= 3) {
- for (let s of game.op.spaces) {
+ for (let s = first_space; s <= last_dept; ++s)
if (can_govt_civic_action(s))
- gen_action_space(s)
- }
+ if (is_selected_op_space(s) || can_select_op_space(3))
+ gen_action_space(s)
}
} else {
view.prompt = `Train: Buy Civic Action in ${space_name[game.op.where]}.`
view.where = game.op.where
if (res >= 3 && can_govt_civic_action(game.op.where)) {
- //gen_action("resources", game.current)
gen_action_space(game.op.where)
} else {
view.prompt = `Train: All done.`
@@ -1745,6 +1805,9 @@ states.assault_space = {
game.op.targeted |= target_faction(p)
remove_piece(p)
update_control()
+
+ // TODO: 3.2.5 Drug Bust
+
if (--game.op.count === 0 || !can_assault_space(game.op.where))
game.state = "assault"
},
@@ -2199,6 +2262,9 @@ states.attack_remove = {
game.op.targeted |= target_faction(p)
remove_piece(p)
update_control()
+
+ // TODO: Captured Goods
+
if (--game.op.count === 0 || !has_exposed_enemy_piece(game.op.where, game.current))
game.state = "attack"
},
@@ -2718,6 +2784,8 @@ states.kidnap_space = {
let g = is_city_or_loc(s) && game.resources[GOVT] > 0
let c = has_piece(s, CARTELS, BASE) && game.resources[CARTELS] > 0
+ // TODO: Drug Ransom if targeting Cartels with Shipment
+
if (g)
gen_action_resources(GOVT)
if (c)
@@ -3046,6 +3114,8 @@ states.bribe_space = {
gen_piece_in_space(game.sa.where, AUC, GUERRILLA)
}
+ // TODO: Contraband
+
if (did_maximum_damage(game.sa.targeted))
view.actions.next = 1
else
@@ -3228,6 +3298,8 @@ exports.view = function (state, role) {
farc_control: game.farc_control,
support: game.support,
farc_zones: game.farc_zones,
+ capabilities: game.capabilities,
+ momentum: game.momentum,
terror: game.terror,
sabotage: game.sabotage,
}
@@ -3304,6 +3376,68 @@ exports.is_checkpoint = function (a, b) {
return a.turn !== b.turn
}
+// === EVENTS ===
+
+states.event = {
+ prompt() {
+ let c = this_card()
+ view.prompt = `${data.card_name[c]}: Choose effect.`
+ view.actions.shaded = 1
+ view.actions.unshaded = 1
+ },
+ shaded() {
+ execute_shaded_event()
+ },
+ unshaded() {
+ execute_unshaded_event()
+ },
+}
+
+function execute_event() {
+ let c = this_card()
+ log(`C${c} - Event`)
+
+ log("TODO")
+ resume_event_card()
+}
+
+function execute_unshaded_event() {
+ let c = this_card()
+ log(`C${c} - Unshaded`)
+
+ if (set_has(capability_events, c)) {
+ logi(event_name_unshaded[c])
+ set_add(game.capabilities, c)
+ resume_event_card()
+ return
+ }
+
+ log("TODO")
+ resume_event_card()
+}
+
+function execute_shaded_event() {
+ let c = this_card()
+ log(`C${c} - Shaded`)
+
+ if (set_has(capability_events, c)) {
+ logi(event_name_shaded[c])
+ set_add(game.capabilities, -c)
+ resume_event_card()
+ return
+ }
+
+ if (set_has(momentum_events, c)) {
+ logi(event_name_shaded[c])
+ set_add(game.momentum, c)
+ resume_event_card()
+ return
+ }
+
+ log("TODO")
+ resume_event_card()
+}
+
// === COMMON LIBRARY ===
function clear_undo() {