diff options
-rw-r--r-- | play.js | 1 | ||||
-rw-r--r-- | rules.js | 454 |
2 files changed, 310 insertions, 145 deletions
@@ -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") } @@ -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() |