summaryrefslogtreecommitdiff
path: root/rules.js
diff options
context:
space:
mode:
Diffstat (limited to 'rules.js')
-rw-r--r--rules.js552
1 files changed, 466 insertions, 86 deletions
diff --git a/rules.js b/rules.js
index f88dc89..8dd00a4 100644
--- a/rules.js
+++ b/rules.js
@@ -1,5 +1,7 @@
"use strict"
+// TODO: Automatic "All done" message. (limited / no more resources / no more available options).
+
let states = {}
let game = null
let view = null
@@ -429,6 +431,30 @@ function has_underground_guerrilla(s, faction) {
return false
}
+function has_any_guerrilla(s) {
+ return (
+ has_piece(s, FARC, GUERRILLA) ||
+ has_piece(s, AUC, GUERRILLA) ||
+ has_piece(s, CARTELS, GUERRILLA)
+ )
+}
+
+function has_any_active_guerrilla(s) {
+ return (
+ has_active_guerrilla(s, FARC) ||
+ has_active_guerrilla(s, AUC) ||
+ has_active_guerrilla(s, CARTELS)
+ )
+}
+
+function has_any_underground_guerrilla(s) {
+ return (
+ has_underground_guerrilla(s, FARC) ||
+ has_underground_guerrilla(s, AUC) ||
+ has_underground_guerrilla(s, CARTELS)
+ )
+}
+
function count_bases(s) {
return (
count_pieces(s, GOVT, BASE) +
@@ -485,6 +511,14 @@ function is_pop(s) {
return s >= first_pop && s <= last_pop
}
+function is_mountain(s) {
+ return data.spaces[s].type === "mountain"
+}
+
+function is_forest(s) {
+ return data.spaces[s].type === "forest"
+}
+
function is_adjacent(a, b) {
return set_has(data.spaces[a].adjacent, b)
}
@@ -493,6 +527,10 @@ function is_active(p) {
return !is_underground(p)
}
+function is_farc_zone(s) {
+ return set_has(game.farc_zones, s)
+}
+
function is_guerrilla(p) {
for (let faction = 1; faction < 4; ++faction) {
let p0 = first_piece[faction][GUERRILLA]
@@ -592,7 +630,7 @@ function for_each_piece(faction, type, f) {
f(p)
}
-function gen_piece_in_space(faction, type, space) {
+function gen_piece_in_space(space, faction, type) {
for_each_piece(faction, type, p => {
if (game.pieces[p] === space)
gen_action_piece(p)
@@ -617,7 +655,7 @@ function gen_place_piece(space, faction, type) {
gen_action_piece(p)
}
-function gen_activate_guerrilla(s, faction) {
+function gen_underground_guerrillas(s, faction) {
for_each_piece(faction, GUERRILLA, p => {
if (game.pieces[p] === s)
if (is_underground(p))
@@ -625,16 +663,24 @@ function gen_activate_guerrilla(s, faction) {
})
}
+function gen_active_guerrillas(s, faction) {
+ for_each_piece(faction, GUERRILLA, p => {
+ if (game.pieces[p] === s)
+ if (is_active(p))
+ gen_action_piece(p)
+ })
+}
+
function gen_remove_piece(s, faction) {
if (faction === GOVT) {
- gen_piece_in_space(GOVT, TROOPS, game.op.where)
- gen_piece_in_space(GOVT, POLICE, game.op.where)
+ gen_piece_in_space(game.op.where, GOVT, TROOPS)
+ gen_piece_in_space(game.op.where, GOVT, POLICE)
if (!has_piece(game.op.where, GOVT, TROOPS) && !has_piece(game.op.where, GOVT, POLICE))
- gen_piece_in_space(GOVT, BASE, game.op.where)
+ gen_piece_in_space(game.op.where, GOVT, BASE)
} else {
- gen_piece_in_space(faction, GUERRILLA, game.op.where)
+ gen_piece_in_space(game.op.where, faction, GUERRILLA)
if (!has_piece(game.op.where, faction, GUERRILLA))
- gen_piece_in_space(faction, BASE, game.op.where)
+ gen_piece_in_space(game.op.where, faction, BASE)
}
}
@@ -1013,12 +1059,7 @@ function operation(state) {
states.train = operation({
prompt() {
- view.prompt = "Train: Place cubes first, then replace with Base or buy Civic Action."
-
- if (can_use_special_activity()) {
- view.actions.air_lift = 1
- view.actions.eradicate = 1
- }
+ view.prompt = "Train: Select spaces to place cubes."
// Any Departments or Cities
if (can_select_op_space(3)) {
@@ -1028,16 +1069,8 @@ states.train = operation({
gen_action_space(s)
}
- // place base
- view.actions.base = 1
-
- // buy civic action
- if (game.resources[game.current] >= 3)
- view.actions.civic = 1
- else
- view.actions.civic = 0
-
- gen_operation_common()
+ if (game.op.spaces.length > 0)
+ view.actions.next = 1
},
space(s) {
push_undo()
@@ -1050,22 +1083,11 @@ states.train = operation({
game.op.where = s
game.op.count = 6
},
- base() {
- push_undo()
- game.state = "train_base"
- game.op.where = -1
- },
- civic() {
+ next() {
push_undo()
- game.state = "train_civic"
- game.op.where = -1
- },
- done: end_operation,
+ game.state = "train_base_or_civic"
+ }
})
- },
- air_lift: goto_air_lift,
- eradicate: goto_eradicate,
-}
states.train_place = operation({
prompt() {
@@ -1092,14 +1114,36 @@ states.train_place = operation({
},
})
+states.train_base_or_civic = operation({
+ prompt() {
+ 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.done = 1
+ },
+ base() {
+ push_undo()
+ game.state = "train_base"
+ game.op.where = -1
+ },
+ civic() {
+ push_undo()
+ game.state = "train_civic"
+ game.op.where = -1
+ },
+ done: end_operation,
+})
+
states.train_base = operation({
prompt() {
if (game.op.where < 0) {
view.prompt = `Train: Replace 3 cubes with a Base.`
- for (let s = first_space; s <= last_dept; ++s) {
- if (can_select_op_space(3) || is_selected_op_space(s))
- if (count_bases(s) < 2 && count_cubes(s) >= 3)
- gen_action_space(s)
+ for (let s of game.op.spaces) {
+ if (count_bases(s) < 2 && count_cubes(s) >= 3)
+ gen_action_space(s)
}
} else {
if (game.op.count < 0) {
@@ -1107,8 +1151,8 @@ states.train_base = operation({
view.actions.done = 1
} else if (game.op.count > 0) {
view.prompt = `Train: ${game.op.count} cubes with a Base.`
- gen_piece_in_space(GOVT, POLICE, game.op.where)
- gen_piece_in_space(GOVT, TROOPS, game.op.where)
+ gen_piece_in_space(game.op.where, GOVT, POLICE)
+ gen_piece_in_space(game.op.where, GOVT, TROOPS)
} else {
view.prompt = `Train: Place Base.`
gen_place_piece(game.op.where, GOVT, BASE)
@@ -1117,8 +1161,6 @@ states.train_base = operation({
},
space(s) {
push_undo()
- if (!is_selected_op_space(s))
- select_op_space(s, 3)
game.op.where = s
game.op.count = 3
},
@@ -1140,26 +1182,25 @@ states.train_civic = operation({
if (game.op.where < 0) {
view.prompt = `Train: Buy Civic Action.`
if (res >= 3) {
- for (let s = first_space; s <= last_dept; ++s) {
- if (can_select_op_space(6) || is_selected_op_space(s))
- if (can_govt_civic_action(s))
- gen_action_space(s)
+ for (let s of game.op.spaces) {
+ if (can_govt_civic_action(s))
+ 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))
+ if (res >= 3 && can_govt_civic_action(game.op.where)) {
+ //gen_action("resources", game.current)
gen_action_space(game.op.where)
- else
+ } else {
view.prompt = `Train: All done.`
+ }
view.actions.done = 1
}
},
space(s) {
push_undo()
- if (!is_selected_op_space(s))
- select_op_space(s, 3)
game.op.where = s
game.resources[game.current] -= 3
game.support[game.op.where] += 1
@@ -1167,8 +1208,372 @@ states.train_civic = operation({
done: end_operation,
})
+// OPERATION: PATROL
+
+function goto_patrol() {
+ if (!game.op.free)
+ game.resources[game.current] -= 3
+ if (game.op.limited)
+ game.state = "limited_patrol"
+ else
+ game.state = "patrol"
+ game.op.who = -1
+ game.op.pieces = []
+}
+
+states.limited_patrol = operation({
+ prompt() {
+ view.prompt = "Patrol: ... TODO LimOp Patrol ..."
+ view.actions.next = 1
+ },
+ next: goto_patrol_activate,
+})
+
+function gen_patrol_move(start) {
+ let queue = [ start ]
+ view.actions.space = []
+ while (queue.length > 0) {
+ let here = queue.shift()
+ for (let next of data.adjacent_patrol[here]) {
+ // already seen
+ if (set_has(view.actions.space, next))
+ continue
+
+ set_add(view.actions.space, next)
+
+ // must stop if guerrilla
+ if (has_any_guerrilla(next))
+ continue
+
+ queue.push(next)
+ }
+ }
+}
+
+states.patrol = operation({
+ prompt() {
+ view.prompt = "Patrol: ..."
+ view.who = game.op.who
+
+ if (game.op.who < 0) {
+ for_each_piece(GOVT, TROOPS, p => {
+ if (game.pieces[p] !== AVAILABLE && !set_has(game.op.pieces, p))
+ gen_action_piece(p)
+ })
+ for_each_piece(GOVT, POLICE, p => {
+ if (game.pieces[p] !== AVAILABLE && !set_has(game.op.pieces, p))
+ gen_action_piece(p)
+ })
+
+ } else {
+ gen_action_piece(game.op.who)
+ gen_patrol_move(game.pieces[game.op.who])
+ let from = game.pieces[game.op.who]
+ for (let to of data.spaces[from].adjacent) {
+ if (is_city(to) || is_loc(to))
+ gen_action_space(to)
+ }
+ }
+
+ view.actions.next = 1
+ },
+ piece(p) {
+ if (game.op.who < 0) {
+ push_undo()
+ game.op.who = p
+ set_add(game.op.pieces, p)
+ } else {
+ pop_undo()
+ }
+ },
+ space(s) {
+ move_piece(game.op.who, s)
+ game.op.who = -1
+ update_control()
+ },
+ next: goto_patrol_activate,
+})
+
+function goto_patrol_activate() {
+ push_undo()
+ game.state = "patrol_activate"
+ game.op.count = []
+ for (let s = first_loc; s <= last_loc; ++s)
+ game.op.count[s - first_loc] = count_cubes(s)
+}
+
+states.patrol_activate = operation({
+ prompt() {
+ view.prompt = "Patrol: Activate 1 Guerrilla for each cube in LoCs."
+
+ // for each cube in each loc, activate 1 guerrilla
+ for (let s = first_loc; s <= last_loc; ++s) {
+ let n = game.op.count[s - first_loc]
+ if (n > 0) {
+ gen_underground_guerrillas(s, FARC)
+ gen_underground_guerrillas(s, AUC)
+ gen_underground_guerrillas(s, CARTELS)
+ }
+ }
+
+ view.actions.next = 1
+ },
+ piece(p) {
+ push_undo()
+ let s = game.pieces[p]
+ set_active(p)
+ game.op.count[s - first_loc]--
+ },
+ next() {
+ push_undo()
+ game.state = "patrol_assault"
+ },
+})
+
+states.patrol_assault = operation({
+ prompt() {
+ view.prompt = "Patrol: Assault in one LoC?"
+
+ if (game.op.limited) {
+ for (let s of game.op.spaces)
+ if (can_assault_space(s))
+ gen_action_space(s)
+ } else {
+ for (let s = first_loc; s <= last_loc; ++s)
+ if (can_assault_space(s))
+ gen_action_space(s)
+ }
+
+ view.actions.done = 1
+ },
+ space(s) {
+ push_undo()
+ log(`Assault S${s}.`)
+
+ game.state = "patrol_assault_space"
+ game.op.where = s
+ game.op.count = count_cubes(s)
+ },
+ done: end_operation
+})
+
+states.patrol_assault_space = operation({
+ prompt() {
+ view.prompt = `Patrol: Remove up to ${game.op.count} enemy pieces in ${space_name[game.op.where]}.`
+ view.where = game.op.where
+
+ if (game.op.count > 0) {
+ gen_assault_piece(game.op.where, FARC)
+ gen_assault_piece(game.op.where, AUC)
+ gen_assault_piece(game.op.where, CARTELS)
+ }
+
+ if (!view.actions.piece)
+ view.prompt = "Patrol: All done."
+
+ view.actions.done = 1
+ },
+ piece(p) {
+ push_undo()
+ remove_piece(p)
+ update_control()
+ },
+ done: end_operation,
+})
+
+// OPERATION: SWEEP
+
+function can_sweep_move(here) {
+ if (has_piece(here, GOVT, TROOPS) || has_piece(here, GOVT, POLICE))
+ return true
+ for (let next of data.spaces[here].adjacent) {
+ if (has_piece(next, GOVT, TROOPS))
+ return true
+ if (is_loc(next) && !has_any_guerrilla(next)) {
+ for (let nextnext of data.spaces[next].adjacent) {
+ if (nextnext !== here)
+ if (has_piece(nextnext, GOVT, TROOPS))
+ return true
+ }
+ }
+ }
+ return false
+}
+
+function gen_sweep_move(here) {
+ for (let next of data.spaces[here].adjacent) {
+ gen_piece_in_space(next, GOVT, TROOPS)
+ if (is_loc(next) && !has_any_guerrilla(next)) {
+ for (let nextnext of data.spaces[next].adjacent) {
+ if (nextnext !== here)
+ gen_piece_in_space(nextnext, GOVT, TROOPS)
+ }
+ }
+ }
+}
+
+states.sweep = operation({
+ prompt() {
+ view.prompt = "Sweep: Select Cities and Departments."
+
+ if (can_select_op_space(3)) {
+ for (let s = first_space; s <= last_dept; ++s) {
+ if (is_selected_op_space(s))
+ continue
+ if (is_farc_zone(s))
+ continue
+ if (can_sweep_move(s))
+ gen_action_space(s)
+ }
+ }
+
+ if (!view.actions.space)
+ view.prompt = "Sweep: All done."
+
+ gen_operation_common()
+ },
+ space(s) {
+ push_undo()
+
+ logi(`S${s}.`)
+
+ select_op_space(s, 3)
+
+ game.state = "sweep_move"
+ game.op.where = s
+ },
+ done: end_operation,
+})
+
+states.sweep_move = operation({
+ prompt() {
+ view.prompt = `Sweep: Move Troops into ${space_name[game.op.where]}.`
+ view.where = game.op.where
+ gen_sweep_move(game.op.where)
+ view.actions.next = 1
+ },
+ piece(p) {
+ push_undo()
+ place_piece(p, game.op.where)
+ update_control()
+ },
+ next() {
+ push_undo()
+ game.state = "sweep_activate"
+ game.op.count = count_pieces(game.op.where, GOVT, TROOPS)
+ if (is_forest(game.op.where))
+ game.op.count >>= 1
+ },
+})
+
+states.sweep_activate = operation({
+ prompt() {
+ view.prompt = `Sweep: Activate up to ${game.op.count} Guerrillas in ${space_name[game.op.where]}.`
+ view.where = game.op.where
+
+ gen_underground_guerrillas(game.op.where, FARC)
+ gen_underground_guerrillas(game.op.where, AUC)
+ gen_underground_guerrillas(game.op.where, CARTELS)
+
+ view.actions.next = 1
+ },
+ piece(p) {
+ push_undo()
+ set_active(p)
+ if (--game.op.count === 0 || !has_any_underground_guerrilla(game.op.where))
+ game.state = "sweep"
+ },
+ next() {
+ push_undo()
+ game.state = "sweep"
+ },
+})
+
+// OPERATION: ASSAULT
+
+function can_assault_space(s) {
+ for (let faction = 1; faction < 4; ++faction) {
+ if (has_piece(s, faction, GUERRILLA)) {
+ if (has_active_guerrilla(s, faction))
+ return true
+ } else {
+ if (has_piece(s, faction, BASE))
+ return true
+ }
+ }
+ return false
+}
+
+function gen_assault_piece(s, faction) {
+ if (has_piece(s, faction, GUERRILLA))
+ gen_active_guerrillas(s, faction)
+ else
+ gen_piece_in_space(s, faction, BASE)
}
+states.assault = operation({
+ prompt() {
+ view.prompt = "Assault: Select space to eliminate enemy forces."
+
+ if (can_select_op_space(3)) {
+ for (let s = first_space; s <= last_dept; ++s) {
+ if (is_selected_op_space(s))
+ continue
+ let n = count_pieces(s, GOVT, TROOPS)
+ if (n >= (is_mountain(s) ? 2 : 1)) {
+ if (can_assault_space(s))
+ gen_action_space(s)
+ }
+ }
+ }
+
+ if (!view.actions.space)
+ view.prompt = "Assault: All done."
+
+ gen_operation_common()
+ },
+ space(s) {
+ push_undo()
+
+ logi(`S${s}.`)
+
+ select_op_space(s, 3)
+
+ game.state = "assault_space"
+ game.op.where = s
+ game.op.count = count_pieces(s, GOVT, TROOPS)
+ if (is_city(s) || is_loc(s))
+ game.op.count += count_pieces(s, GOVT, POLICE)
+ if (is_mountain(s))
+ game.op.count >>= 1
+ },
+ done: end_operation,
+})
+
+states.assault_space = operation({
+ prompt() {
+ view.prompt = `Assault: Remove up to ${game.op.count} enemy pieces in ${space_name[game.op.where]}.`
+ view.where = game.op.where
+
+ gen_assault_piece(game.op.where, FARC)
+ gen_assault_piece(game.op.where, AUC)
+ gen_assault_piece(game.op.where, CARTELS)
+
+ view.actions.next = 1
+ },
+ piece(p) {
+ push_undo()
+ remove_piece(p)
+ update_control()
+ if (--game.op.count === 0 || !can_assault_space(game.op.where))
+ game.state = "assault"
+ },
+ next() {
+ push_undo()
+ game.state = "assault"
+ },
+})
+
// OPERATION: RALLY
function rally_count() {
@@ -1186,18 +1591,6 @@ states.rally = operation({
else
view.prompt = "Rally: Select City or Department."
- if (can_use_special_activity()) {
- if (game.current === FARC)
- view.actions.extort = 1
- if (game.current === AUC)
- view.actions.extort = 1
- if (game.current === CARTELS) {
- view.actions.cultivate = 1
- view.actions.process = 1
- view.actions.bribe = 1
- }
- }
-
// Departments or Cities
if (can_select_op_space(1)) {
for (let s = first_space; s <= last_dept; ++s) {
@@ -1230,18 +1623,14 @@ states.rally = operation({
select_op_space(s, 1)
- game.state = "rally_place"
+ game.state = "rally_space"
game.op.where = s
game.op.count = rally_count()
},
done: end_operation,
- extort: goto_extort,
- cultivate: goto_cultivate,
- process: goto_process,
- bribe: goto_bribe,
})
-states.rally_place = operation({
+states.rally_space = operation({
prompt() {
view.prompt = `Rally: Place up to ${game.op.count} Guerrillas.`
view.where = game.op.where
@@ -1291,7 +1680,7 @@ states.rally_base = operation({
prompt() {
if (game.op.count > 0) {
view.prompt = `Rally: Replace ${game.op.count} Guerrillas with a Base.`
- gen_piece_in_space(game.current, GUERRILLA, game.op.where)
+ gen_piece_in_space(game.op.where, game.current, GUERRILLA)
} else {
view.prompt = `Rally: Place a Base.`
gen_place_piece(game.op.where, game.current, BASE)
@@ -1354,18 +1743,6 @@ states.march = operation({
prompt() {
view.prompt = `March: Move Guerrillas to Cities/Departments/LoCs.`
- if (can_use_special_activity()) {
- if (game.current === FARC)
- view.actions.extort = 1
- if (game.current === AUC)
- view.actions.extort = 1
- if (game.current === CARTELS) {
- view.actions.cultivate = 1
- view.actions.process = 1
- view.actions.bribe = 1
- }
- }
-
if (can_select_op_space(1)) {
for (let s = first_space; s <= last_dept; ++s) {
if (is_selected_op_space(s))
@@ -1614,7 +1991,7 @@ function goto_ambush() {
states.ambush = {
prompt() {
view.prompt = `Ambush in ${space_name[game.op.where]}: Activate an Underground Guerrilla.`
- gen_activate_guerrilla(game.op.where, game.current)
+ gen_underground_guerrillas(game.op.where, game.current)
},
piece(p) {
set_active(p)
@@ -1649,6 +2026,9 @@ states.terror = operation({
}
}
+ if (!view.actions.space)
+ view.prompt = "Terror: All done."
+
gen_operation_common()
},
space(s) {
@@ -1670,7 +2050,7 @@ states.terror = operation({
states.terror_space = operation({
prompt() {
view.prompt = `Terror in ${space_name[game.op.where]}: Activate an Underground Guerrilla.`
- gen_activate_guerrilla(game.op.where, game.current)
+ gen_underground_guerrillas(game.op.where, game.current)
},
piece(p) {
set_active(p)