summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--play.js1
-rw-r--r--rules.js454
2 files changed, 310 insertions, 145 deletions
diff --git a/play.js b/play.js
index 09e9217..e89ca36 100644
--- a/play.js
+++ b/play.js
@@ -1126,6 +1126,7 @@ function on_update() {
action_button("end_actions", "End Actions")
+ action_button("pass", "Pass")
action_button("done", "Done")
action_button("undo", "Undo")
}
diff --git a/rules.js b/rules.js
index d131ceb..bbc4923 100644
--- a/rules.js
+++ b/rules.js
@@ -6,6 +6,8 @@
TODO
----
+game.battle -> game.battle / game.count + game.where
+
-- castra/quaestor - directly or extra select target step?
[x] crisis ira deorum
@@ -532,9 +534,11 @@ function set_placed_governor(province) { game.placed |= (1 << province) }
function has_general_battled(id) { return game.battled & (1 << id) }
function set_general_battled(id) { game.battled |= (1 << id) }
+function clear_general_battled(id) { game.battled &= ~(1 << id) }
function has_militia_battled(province) { return game.mbattled & (1 << province) }
function set_militia_battled(province) { game.mbattled |= (1 << province) }
+function clear_militia_battled(province) { game.mbattled &= ~(1 << province) }
// === COMPOUND STATE ===
@@ -925,6 +929,19 @@ function flip_discard_to_available() {
game.discard[game.current] = []
}
+function assign_hit_to_legion(id) {
+ let army = get_legion_location(id) - ARMY
+ if (is_legion_reduced(id)) {
+ set_legion_location(id, AVAILABLE)
+ if (count_legions_in_army(army) === 0) {
+ set_general_location(army, AVAILABLE)
+ clear_general_battled(army)
+ }
+ } else {
+ set_legion_reduced(id)
+ }
+}
+
// === SETUP ===
states.setup_province = {
@@ -1513,7 +1530,8 @@ states.take_actions = {
push_undo()
if (game.selected_governor >= 0) {
spend_ip(SENATE, 1)
- game.misc = { spend: 1, where: where }
+ game.count = 1
+ game.where = where
game.state = "place_governor"
}
if (game.selected_general >= 0) {
@@ -1602,6 +1620,7 @@ function remove_governor(where) {
remove_all_mobs(where)
remove_militia(where)
+ clear_militia_battled(where)
remove_militia_castra(where)
remove_quaestor(where)
@@ -1621,7 +1640,9 @@ function remove_governor(where) {
function place_governor(where, new_governor) {
remove_all_mobs(where)
remove_militia(where)
+ clear_militia_battled(where)
remove_militia_castra(where)
+ remove_quaestor(where)
let old_governor = get_province_governor(where)
if (old_governor >= 0) {
@@ -1666,12 +1687,12 @@ function calc_needed_votes(where, pg) {
states.place_governor = {
prompt() {
let [ mip, sip, pip ] = game.ip
- let need = calc_needed_votes(game.misc.where, false)
- let votes = game.misc.spend
- if (game.misc.where === ITALIA)
+ let need = calc_needed_votes(game.where, false)
+ let votes = game.count
+ if (game.where === ITALIA)
votes += count_own_basilicas()
view.color = SENATE
- view.selected_region = game.misc.where
+ view.selected_region = game.where
prompt(`Place Governor: ${sip} Senate. Rolling ${votes} dice. ${need} votes needed.`)
if (sip >= 1)
view.actions.spend_senate = 1
@@ -1682,7 +1703,7 @@ states.place_governor = {
spend_senate() {
push_undo()
spend_ip(SENATE, 1)
- game.misc.spend += 1
+ game.count += 1
},
roll() {
roll_to_place_governor()
@@ -1692,12 +1713,12 @@ states.place_governor = {
states.praetorian_guard = {
prompt() {
let [ mip, sip, pip ] = game.ip
- let need = calc_needed_votes(game.misc.where, true)
- let votes = game.misc.spend
- if (game.misc.where === ITALIA)
+ let need = calc_needed_votes(game.where, true)
+ let votes = game.count
+ if (game.where === ITALIA)
votes += count_own_basilicas()
view.color = MILITARY
- view.selected_region = game.misc.where
+ view.selected_region = game.where
prompt(`Praetorian Guard: ${mip} Military. Rolling ${votes} dice. ${need} votes needed.`)
if (mip >= 1)
view.actions.spend_military = 1
@@ -1708,7 +1729,7 @@ states.praetorian_guard = {
spend_military() {
push_undo()
spend_ip(MILITARY, 1)
- game.misc.spend += 1
+ game.count += 1
},
roll() {
roll_to_place_governor()
@@ -1716,34 +1737,33 @@ states.praetorian_guard = {
}
function roll_to_place_governor(pg) {
- let need = calc_needed_votes(game.misc.where, pg)
+ let need = calc_needed_votes(game.where, pg)
let have = 0
- set_placed_governor(game.misc.where)
+ set_placed_governor(game.where)
- if (game.misc.where === ITALIA)
- game.misc.spend += count_own_basilicas()
+ if (game.where === ITALIA)
+ game.count += count_own_basilicas()
if (pg)
- log("Praetorian Guard in S" + game.misc.where)
+ log("Praetorian Guard in S" + game.where)
else
- log("Place Governor in S" + game.misc.where)
+ log("Place Governor in S" + game.where)
- if (is_neutral_province(game.misc.where))
- have = roll_dice(game.misc.spend, 1)
- else if (!pg && has_quaestor(game.misc.where))
- have = roll_dice(game.misc.spend, 3)
+ if (is_neutral_province(game.where))
+ have = roll_dice(game.count, 1)
+ else if (!pg && has_quaestor(game.where))
+ have = roll_dice(game.count, 3)
else
- have = roll_dice(game.misc.spend, 2)
+ have = roll_dice(game.count, 2)
if (have >= need) {
logi("Success!")
- place_governor(game.misc.where, game.selected_governor)
+ place_governor(game.where, game.selected_governor)
} else {
logi("Failed!")
}
- game.misc = null
game.state = "take_actions"
}
@@ -1773,7 +1793,8 @@ function can_play_praetorian_guard() {
function play_praetorian_guard() {
spend_ip(MILITARY, 1)
- game.misc = { spend: 1, where: ITALIA }
+ game.count = 0
+ game.where = ITALIA
game.state = "praetorian_guard"
}
@@ -2043,7 +2064,8 @@ function goto_battle_vs_militia(where, attacker) {
function goto_battle(type, where, attacker, target) {
spend_ip(MILITARY, 1)
- game.misc = { type, where, attacker, target }
+ game.where = where
+ game.battle = { type, attacker, target }
game.state = "battle"
if (attacker >= 0) {
remove_general_castra(attacker)
@@ -2086,29 +2108,24 @@ states.battle = {
roll() {
// clear_undo()
- game.misc.dhits = roll_attacker_dice()
- game.misc.dtaken = 0
+ game.battle.dhits = roll_attacker_dice()
+ game.battle.dtaken = 0
- if (game.misc.type === "militia" && has_militia_castra(game.misc.where)) {
+ if (game.battle.type === "militia" && has_militia_castra(game.where)) {
log("Castra reduces 1 hit")
- if (game.misc.dhits > 0)
- game.misc.dhits -= 1
+ if (game.battle.dhits > 0)
+ game.battle.dhits -= 1
}
- if (game.misc.type === "general" && has_general_castra(game.misc.target)) {
+ if (game.battle.type === "general" && has_general_castra(game.battle.target)) {
log("Castra reduces 1 hit")
- if (game.misc.dhits > 0)
- game.misc.dhits -= 1
+ if (game.battle.dhits > 0)
+ game.battle.dhits -= 1
}
- game.misc.ahits = roll_defender_dice()
- game.misc.ataken = 0
+ game.battle.ahits = roll_defender_dice()
+ game.battle.ataken = 0
- if (game.misc.ahits > 0)
- game.state = "assign_hits_on_attacker"
- else if (game.misc.dhits > 0)
- game.state = "assign_hits_on_defender"
- else
- end_battle()
+ goto_assign_hits()
},
}
@@ -2118,7 +2135,7 @@ function roll_general_dice(general) {
log(GENERAL_NAME[general])
- if (is_general_inside_capital(general) && has_militia(game.misc.where)) {
+ if (is_general_inside_capital(general) && has_militia(game.where)) {
log("Militia")
n += roll_dice(1, 5)
}
@@ -2166,10 +2183,10 @@ function roll_rival_emperor_dice(rival_emperor) {
function roll_barbarian_dice(tribe) {
log(BARBARIAN_NAME[tribe])
- let prov = is_province(game.misc.where)
+ let prov = is_province(game.where)
let n = 0
for (let id = first_barbarian[tribe]; id <= last_barbarian[tribe]; ++id)
- if (get_barbarian_location(id) === game.misc.where)
+ if (get_barbarian_location(id) === game.where)
if (prov || is_barbarian_active(id))
n += 1
return roll_dice(n, 4)
@@ -2177,167 +2194,313 @@ function roll_barbarian_dice(tribe) {
function roll_attacker_dice() {
log_h3("ATTACKER")
- if (game.misc.attacker < 0)
+ if (game.battle.attacker < 0)
return roll_militia_dice()
else
- return roll_general_dice(game.misc.attacker)
+ return roll_general_dice(game.battle.attacker)
}
function roll_defender_dice() {
log_h3("DEFENDER")
- switch (game.misc.type) {
+ switch (game.battle.type) {
case "militia":
return roll_militia_dice()
case "rival_emperor":
- return roll_rival_emperor_dice(game.misc.target)
+ return roll_rival_emperor_dice(game.battle.target)
case "barbarians":
- return roll_barbarian_dice(game.misc.target)
+ return roll_barbarian_dice(game.battle.target)
case "general":
- return roll_general_dice(game.misc.target)
+ return roll_general_dice(game.battle.target)
}
return 0
}
+// COMBAT: ASSIGN HITS
+
+function has_hits_militia() {
+ return has_militia(game.where)
+}
+
function gen_hits_militia() {
- if (has_militia(game.misc.where)) {
- gen_action_militia(game.misc.where)
- return false
- }
- return true
+ if (has_militia(game.where))
+ gen_action_militia(game.where)
+}
+
+function has_hits_barbarians(tribe) {
+ let prov = is_province(game.where)
+ for (let id = first_barbarian[tribe]; id <= last_barbarian[tribe]; ++id)
+ if (get_barbarian_location(id) === game.where)
+ if (prov || is_barbarian_active(id))
+ return true
+ return false
}
function gen_hits_barbarians(tribe) {
- let prov = is_province(game.misc.where)
+ let prov = is_province(game.where)
for (let id = first_barbarian[tribe]; id <= last_barbarian[tribe]; ++id)
- if (get_barbarian_location(id) === game.misc.where)
+ if (get_barbarian_location(id) === game.where)
if (prov || is_barbarian_active(id))
gen_action_barbarian(id)
}
-// TODO: auto-end if all possible hits assigned
+function has_hits_general(general) {
+ let army = ARMY + general
+ if (is_general_inside_capital(general) && has_militia(game.where))
+ return true
+ for (let id = 0; id < game.barbarians.length; ++id)
+ if (get_barbarian_location(id) === army)
+ return true
+ for (let id = 0; id < 33; ++id)
+ if (get_legion_location(id) === army)
+ return true
+ return false
+}
function gen_hits_general(general) {
let army = ARMY + general
- // TODO: castra
+ let done = false
- if (is_general_inside_capital(general) && has_militia(game.misc.where)) {
- gen_action_militia(game.misc.where)
- return false
+ if (is_general_inside_capital(general) && has_militia(game.where)) {
+ gen_action_militia(game.where)
+ done = true
}
- for (let id = 0; id < game.barbarians.length; ++id) {
- if (get_barbarian_location(id) === army) {
- gen_action_barbarian(id)
- return false
+ if (!done) {
+ for (let id = 0; id < game.barbarians.length; ++id) {
+ if (get_barbarian_location(id) === army) {
+ gen_action_barbarian(id)
+ done = true
+ }
}
}
// NOTE: reduce all legions before eliminating any
- for (let id = 0; id < 33; ++id) {
- if (get_legion_location(id) === army && !is_legion_reduced(id)) {
- gen_action_legion(id)
- return false
+ if (!done) {
+ for (let id = 0; id < 33; ++id) {
+ if (get_legion_location(id) === army && !is_legion_reduced(id)) {
+ gen_action_legion(id)
+ done = true
+ }
}
}
- for (let id = 0; id < 33; ++id) {
- if (get_legion_location(id) === army && is_legion_reduced(id)) {
- gen_action_legion(id)
- return false
+ if (!done) {
+ for (let id = 0; id < 33; ++id) {
+ if (get_legion_location(id) === army && is_legion_reduced(id)) {
+ gen_action_legion(id)
+ done = true
+ }
}
}
+}
- return true
+function has_hits_on_attacker() {
+ if (game.battle.ataken < game.battle.ahits) {
+ if (game.battle.attacker < 0)
+ return has_hits_militia()
+ else
+ return has_hits_general(game.battle.attacker)
+ }
+ return false
}
-states.assign_hits_on_attacker = {
- prompt() {
- prompt("Assign " + (game.misc.ahits - game.misc.ataken) + " hits!")
+function has_hits_on_defender() {
+ if (game.battle.dtaken < game.battle.dhits) {
+ switch (game.battle.type) {
+ case "militia":
+ return has_hits_militia()
+ break
+ case "rival_emperor":
+ return has_hits_rival_emperor(game.battle.target)
+ break
+ case "barbarians":
+ return has_hits_barbarians(game.battle.target)
+ break
+ case "general":
+ return has_hits_general(game.battle.target)
+ break
+ }
+ }
+ return false
+}
- let done = true
+function goto_assign_hits() {
+ // TODO: flanking maneuver
+ goto_assign_hits_on_attacker()
+}
- if (game.misc.ataken < game.misc.ahits) {
- if (game.misc.attacker < 0)
- done = gen_hits_militia()
- else
- done = gen_hits_general(game.misc.attacker)
- }
+function goto_assign_hits_on_attacker() {
+ if (has_hits_on_attacker())
+ game.state = "assign_hits_on_attacker"
+ else
+ goto_assign_hits_on_defender()
+}
- if (done)
- view.actions.done = 1
- },
- done() {
- if (game.misc.dhits > 0)
- game.state = "assign_hits_on_defender"
+function goto_assign_hits_on_defender() {
+ if (has_hits_on_defender())
+ game.state = "assign_hits_on_defender"
+ else
+ goto_combat_victory()
+}
+
+states.assign_hits_on_attacker = {
+ prompt() {
+ prompt("Assign " + (game.battle.ahits - game.battle.ataken) + " hits to attacker!")
+ if (game.battle.attacker < 0)
+ gen_hits_militia()
else
- end_battle()
+ gen_hits_general(game.battle.attacker)
},
militia(where) {
- game.misc.ataken += 1
+ game.battle.ataken += 1
remove_militia(where)
+ clear_militia_battled(where)
+ goto_assign_hits_on_attacker()
},
legion(id) {
- game.misc.ataken += 1
- if (is_legion_reduced(id))
- set_legion_location(id, AVAILABLE)
- else
- set_legion_reduced(id)
+ game.battle.ataken += 1
+ assign_hit_to_legion(id)
+ goto_assign_hits_on_attacker()
},
barbarian(id) {
- game.misc.ataken += 1
+ game.battle.ataken += 1
eliminate_barbarian(id)
+ goto_assign_hits_on_attacker()
},
}
states.assign_hits_on_defender = {
prompt() {
- prompt("Assign " + (game.misc.dhits - game.misc.dtaken) + " hits!")
-
- let done = true
-
- if (game.misc.dtaken < game.misc.dhits) {
- switch (game.misc.type) {
- case "militia":
- done = gen_hits_militia()
- break
- case "rival_emperor":
- done = gen_hits_rival_emperor(game.misc.target)
- break
- case "barbarians":
- done = gen_hits_barbarians(game.misc.target)
- break
- case "general":
- done = gen_hits_general(game.misc.target)
- break
- }
+ prompt("Assign " + (game.battle.dhits - game.battle.dtaken) + " hits to defender!")
+ switch (game.battle.type) {
+ case "militia":
+ gen_hits_militia()
+ break
+ case "rival_emperor":
+ gen_hits_rival_emperor(game.battle.target)
+ break
+ case "barbarians":
+ gen_hits_barbarians(game.battle.target)
+ break
+ case "general":
+ gen_hits_general(game.battle.target)
+ break
}
-
- if (done)
- view.actions.done = 1
- },
- done() {
- end_battle()
},
militia(where) {
- game.misc.dtaken += 1
+ game.battle.dtaken += 1
remove_militia(where)
+ clear_militia_battled(where)
+ goto_assign_hits_on_defender()
},
legion(id) {
- game.misc.dtaken += 1
- if (is_legion_reduced(id))
- set_legion_location(id, AVAILABLE)
- else
- set_legion_reduced(id)
+ game.battle.dtaken += 1
+ assign_hit_to_legion(id)
+ goto_assign_hits_on_defender()
},
barbarian(id) {
- game.misc.dtaken += 1
+ game.battle.dtaken += 1
eliminate_barbarian(id)
+ goto_assign_hits_on_defender()
+ },
+}
+
+function is_attacker_eliminated() {
+ if (game.selected_general < 0)
+ return !has_militia(game.where)
+ else
+ return get_general_location(game.selected_general) === AVAILABLE
+}
+
+function is_defender_eliminated() {
+ switch (game.battle.type) {
+ case "militia":
+ return !has_militia(game.where)
+ case "rival_emperor":
+ return get_rival_emperor_location(game.battle.target) === AVAILABLE
+ case "barbarians":
+ if (is_province(game.where))
+ return find_barbarian_of_tribe(game.where, game.battle.target) < 0
+ else
+ return find_active_barbarian_of_tribe(game.where, game.battle.target) < 0
+ case "general":
+ return get_general_location(game.battle.target) === AVAILABLE
+ }
+ return false
+}
+
+function goto_combat_victory() {
+ let de = is_defender_eliminated()
+ let ae = is_attacker_eliminated()
+ console.log("goto_combat_victory", de, ae)
+ if (de && ae)
+ end_battle()
+ else if (de || game.battle.dtaken > game.battle.ataken)
+ goto_combat_victory_attacker()
+ else
+ goto_combat_victory_defender()
+}
+
+function award_legacy(p, n) {
+ log(PLAYER_NAMES[p] + " gained " + n + " VP.")
+ game.legacy[p] += n
+}
+
+function goto_combat_victory_defender() {
+ if (game.battle.type === "general")
+ award_legacy(game.battle.target / 6 | 0, 2)
+ if (game.battle.type === "militia")
+ award_legacy(get_province_governor(game.where) / 6 | 0, 2)
+ end_battle()
+}
+
+function goto_combat_victory_attacker() {
+ award_legacy(game.current, 2)
+
+ if (game.battle.type === "barbarians") {
+ award_legacy(game.current, game.battle.dtaken)
+
+ // Surviving Barbarians go home (to active)
+ let tribe = get_barbarian_tribe(game.battle.target)
+ for (let id = first_barbarian[tribe]; id <= last_barbarian[tribe]; ++id)
+ if (get_barbarian_location(id) === game.battle)
+ set_barbarian_location(id, BARBARIAN_HOMELAND[tribe])
+ }
+
+ // Defending Romans must retreat into province
+ if (game.battle.type === "general") {
+ set_general_outside_capital(game.battle.target)
+ remove_general_castra(game.battle.target)
+ }
+
+ // TODO: barbarian leader / rival emperor bonus
+
+ if (can_enter_capital(game.where))
+ game.state = "advance_after_combat"
+ else
+ end_battle()
+}
+
+states.advance_after_combat = {
+ prompt() {
+ prompt("Battle: You may advance into provincial capital.")
+ gen_action_capital(game.where)
+ view.actions.pass = 1
+ },
+ capital(where) {
+ push_undo()
+ end_battle()
+ enter_capital()
+ },
+ pass() {
+ push_undo()
+ end_battle()
},
}
function end_battle() {
- // TODO: retreat / advance into capital
+ game.battle = null
game.state = "take_actions"
}
@@ -2473,7 +2636,7 @@ function goto_buy_trash_cards() {
for (let c of game.played)
set_add(discard, c)
game.played.length = 0
- game.misc = {
+ game.battle = {
count: 0,
pp: count_political_points(),
}
@@ -2510,9 +2673,9 @@ function find_market_with_card(c) {
states.buy_trash = {
prompt() {
- prompt("Buy/Trash cards: " + game.misc.pp + "PP left.")
+ prompt("Buy/Trash cards: " + game.battle.pp + "PP left.")
let nprov = count_own_provinces()
- if (game.misc.pp >= 3) {
+ if (game.battle.pp >= 3) {
for (let c of current_discard())
gen_action_card(c)
}
@@ -2522,8 +2685,8 @@ states.buy_trash = {
let cost = card_cost(c)
if (cost > nprov)
cost *= 2
- cost += game.misc.count
- if (game.misc.pp >= cost)
+ cost += game.battle.count
+ if (game.battle.pp >= cost)
gen_action_card(c)
}
}
@@ -2534,7 +2697,7 @@ states.buy_trash = {
if (set_has(current_discard(), c)) {
log("Trashed " + card_name(c))
set_delete(current_discard(), c)
- game.misc.pp -= 3
+ game.battle.pp -= 3
} else {
log("Bought " + card_name(c))
set_add(current_discard(), c)
@@ -2542,9 +2705,9 @@ states.buy_trash = {
let cost = card_cost(c)
if (cost > count_own_provinces())
cost *= 2
- cost += game.misc.count
- game.misc.pp -= cost
- game.misc.count += 1
+ cost += game.battle.count
+ game.battle.pp -= cost
+ game.battle.count += 1
}
},
done() {
@@ -2724,9 +2887,9 @@ exports.setup = function (seed, scenario, options) {
used: [],
placed: 0,
battled: 0,
-
- // grab bag of temporary data for the current procedure
- misc: null,
+ count: 0,
+ where: 0,
+ battle: null,
support: new Array(player_count * 3).fill(1),
mobs: new Array(player_count * 3).fill(0),
@@ -2800,6 +2963,7 @@ exports.setup = function (seed, scenario, options) {
game.hand[player] = []
game.draw[player] = setup_player_deck(player)
game.discard[player] = []
+ game.draw[player].push(game.market[2].pop())
}
update_neutral_italia()