diff options
-rw-r--r-- | play.html | 4 | ||||
-rw-r--r-- | play.js | 11 | ||||
-rw-r--r-- | rules.js | 130 |
3 files changed, 103 insertions, 42 deletions
@@ -289,12 +289,12 @@ main { <div id="roles"> <div class="role" id="role_First"> <div class="role_name"><span id="role1">First</span></div> - <div class="role_info"></div> + <div class="role_stat" id="stat1"></div> <div class="role_user"></div> </div> <div class="role" id="role_Second"> <div class="role_name"><span id="role2">Second</span></div> - <div class="role_info"></div> + <div class="role_stat" id="stat2"></div> <div class="role_user"></div> </div> </div> @@ -8,6 +8,7 @@ const reactions = [ "Screen", "Counterattack", "Absorb" ] let ui = { main: document.querySelector("main"), role_name: [ document.getElementById("role1"), document.getElementById("role2") ], + role_stat: [ document.getElementById("stat1"), document.getElementById("stat2") ], name: [ document.getElementById("name1"), document.getElementById("name2") ], front: [ document.getElementById("front1"), document.getElementById("front2") ], morale: [ document.getElementById("morale1"), document.getElementById("morale2") ], @@ -315,6 +316,16 @@ function on_update() { ui.role_name[p1].textContent = data.scenarios[view.scenario].players[0].name ui.role_name[p2].textContent = data.scenarios[view.scenario].players[1].name + if (data.scenarios[view.scenario].players[1].tactical) + ui.role_stat[p1].textContent = `${view.morale[0]} (-${view.lost[0]})` + else + ui.role_stat[p1].textContent = view.morale[0] + + if (data.scenarios[view.scenario].players[0].tactical) + ui.role_stat[p2].textContent = `${view.morale[1]} (-${view.lost[1]})` + else + ui.role_stat[p2].textContent = view.morale[1] + for (let e of animation_registry) remember_position(e) @@ -31,6 +31,10 @@ Special card rules implemented: const data = require("./data.js") +function clamp(x, min, max) { + return Math.max(min, Math.min(max, x)) +} + function find_scenario(n) { let ix = data.scenarios.findIndex(s => s.number === n) if (ix < 0) @@ -120,6 +124,7 @@ exports.view = function (state, player) { sticks: game.sticks, cubes: game.cubes, morale: game.morale, + lost: game.lost, front: game.front, reserve: game.reserve, selected: game.selected, @@ -168,7 +173,7 @@ function goto_game_over(result, victory) { log("") log(result + " victory!") log(victory) - return false + return true } states.game_over = { @@ -321,6 +326,7 @@ exports.setup = function (seed, scenario, options) { cubes: [], morale: [ info.players[0].morale, info.players[1].morale ], + lost: [ 0, 0 ], front: [ [], [], ], reserve: [ [], [] ], @@ -353,7 +359,7 @@ exports.setup = function (seed, scenario, options) { else add_cubes(c, 1) } else { - add_sticks(c, card.strength) + set_sticks(c, card.strength) } } @@ -426,25 +432,35 @@ function player_index() { return 1 } +function get_cubes(c) { + return map_get(game.cubes, c, 0) +} + function add_cubes(c, n) { let limit = data.cards[c].special - let old = map_get(game.cubes, c, 0) + let old = get_cubes(c) map_set(game.cubes, c, Math.min(limit, old + n)) } function remove_cubes(c, n) { - let old = map_get(game.cubes, c, 0) + let old = get_cubes(c) map_set(game.cubes, c, Math.max(0, old - n)) } -function add_sticks(c, n) { - let old = map_get(game.sticks, c, 0) - map_set(game.sticks, c, old + n) +function get_sticks(c) { + return map_get(game.sticks, c, 0) +} + +function set_sticks(c, n) { + map_set(game.sticks, c, n) } function remove_sticks(c, n) { - let old = map_get(game.sticks, c, 0) - map_set(game.sticks, c, Math.max(0, old - n)) + let p = find_card_owner(c) + let old = get_sticks(c) + n = Math.min(n, old) + game.lost[p] += n + set_sticks(c, old - n) } function remove_dice(c) { @@ -494,6 +510,28 @@ function eliminate_card(c) { set_delete(game.reserve[1], c) } +function rout_card(c) { + let p = find_card_owner(c) + game.lost[p] += get_sticks(c) + log(c + " routed.") + eliminate_card(c) +} + +function pursue_card(c) { + log(c + " pursued.") + eliminate_card(c) +} + +function remove_card(c) { + log(c + " removed.") + eliminate_card(c) +} + +function retire_card(c) { + log(c + " retired.") + eliminate_card(c) +} + function pay_for_action(c) { if (data.cards[c].special) remove_cubes(c, 1) @@ -595,11 +633,23 @@ function check_impossible_to_attack_victory() { return false } -function check_morale_victory() { +function check_victory() { + let info = data.scenarios[game.scenario] + if (game.morale[0] === 0) return goto_game_over(P2, P1 + " has run out of morale!") if (game.morale[1] === 0) return goto_game_over(P1, P2 + " has run out of morale!") + + let tv0 = info.players[1].lost + let tv1 = info.players[0].lost + + if (info.players[0].tactical > 0 && tv0 >= info.players[0].tactical) + return goto_game_over(P2, P2 + " tactical victory!") + + if (info.players[1].tactical > 0 && tv1 >= info.players[1].tactical) + return goto_game_over(P1, P1 + " tactical victory!") + return false } @@ -847,7 +897,7 @@ function can_place_dice(c) { // At cube limit? if (data.cards[c].special) { // Max on card - if (map_get(game.cubes, c, 0) >= data.cards[c].special) + if (get_cubes(c) >= data.cards[c].special) return false // Max available @@ -1282,7 +1332,7 @@ function end_roll_phase() { for (let c of game.front[player_index()]) { let s = data.cards[c].special if (s && has_any_dice_on_card(c)) { - map_set(game.cubes, c, Math.min(s, map_get(game.cubes, c, 0) + 1)) + map_set(game.cubes, c, Math.min(s, get_cubes(c) + 1)) remove_dice(c) } } @@ -1303,7 +1353,7 @@ function end_roll_phase() { } if (game.scenario === S28_CULPS_HILL) { - if (map_get(game.cubes, S28_GEARY, 0) === 5) + if (get_cubes(S28_GEARY) === 5) return goto_game_over(P2, "Geary's Division arrived.") } @@ -1330,17 +1380,16 @@ states.s26_fatal_blunder = { card(c) { if (c === S26_FATAL_BLUNDER) { log("Fatal Blunder!") - eliminate_card(S26_FATAL_BLUNDER) + remove_card(S26_FATAL_BLUNDER) game.morale[0] ++ } else { - log("C" + c + " routed.") - eliminate_card(c) + rout_card(c) game.morale[0] -- game.morale[1] ++ } }, end_turn() { - if (check_morale_victory()) + if (check_victory()) return end_turn() } @@ -1379,7 +1428,7 @@ function has_any_dice_on_card(c) { } function has_any_cubes_on_card(c) { - return map_get(game.cubes, c, 0) >= 1 + return get_cubes(c) >= 1 } function count_dice_on_card(c) { @@ -1436,10 +1485,10 @@ function require_two_pairs(c) { function check_cube_requirement(c, req) { switch (req) { case "3 cubes": - return map_get(game.cubes, c, 0) >= 3 + return get_cubes(c) >= 3 case "Voluntary": case undefined: - return map_get(game.cubes, c, 0) >= 1 + return get_cubes(c) >= 1 default: throw new Error("invalid action requirement: " + req) } @@ -1592,11 +1641,10 @@ states.s25_stony_hill = { gen_action_card(S25_STONY_HILL) }, card(c) { - log("Stony Hill routed!") - eliminate_card(S25_STONY_HILL) + rout_card(S25_STONY_HILL) game.morale[0] -- game.morale[1] ++ - if (check_morale_victory()) + if (check_victory()) return goto_action_phase() }, @@ -1658,8 +1706,7 @@ states.action = { }, retire(c) { push_undo() - log(card_name(c) + " retired.") - eliminate_card(c) + retire_card(c) end_action_phase() }, a1(c) { @@ -1843,7 +1890,7 @@ function update_attack1() { if (game.scenario === S37_INKERMAN) { // Until the first Fog Cube is lifted. - if (map_get(game.cubes, S37_THE_FOG, 0) === 3) { + if (get_cubes(S37_THE_FOG) === 3) { game.hits -= 1 } } @@ -1908,7 +1955,7 @@ function update_attack1() { } if (card_has_rule(game.target, "suffer_1_less_1_max")) - game.hits = Math.max(0, Math.min(1, game.hits - 1)) + game.hits = clamp(game.hits - 1, 0, 1) if (card_has_rule(game.target, "suffer_1_less")) game.hits = Math.max(0, game.hits - 1) } @@ -2034,7 +2081,7 @@ states.command = { if (c === S4_THE_STANLEYS) { if (is_card_in_play_or_reserve(S4_NORTHUMBERLAND)) { log("The Stanleys rout Northumberland.") - map_set(game.sticks, S4_NORTHUMBERLAND, 0) + set_sticks(S4_NORTHUMBERLAND, 0) } } } @@ -2271,7 +2318,7 @@ states.screen = { pay_for_action(game.target) if (card_has_rule(game.target, "remove_after_screen")) - eliminate_card(game.target) + remove_card(game.target) end_reaction() }, @@ -2379,11 +2426,11 @@ function goto_counterattack(c, a) { break case "This unit suffers ONE less hit and never more than one.": game.self += 1 - game.hits = Math.max(0, Math.min(1, game.hits - 1)) + game.hits = clamp(game.hits - 1, 0, 1) break case "This unit suffers TWO less hits and never more than one.": game.self += 1 - game.hits = Math.max(0, Math.min(1, game.hits - 2)) + game.hits = clamp(game.hits - 2, 0, 1) break } @@ -2491,7 +2538,7 @@ function find_card_owner(c) { function should_rout_card(c) { if (!data.cards[c].special) { - if (map_get(game.sticks, c, 0) === 0) + if (get_sticks(c) === 0) return true } @@ -2551,13 +2598,13 @@ function goto_routing() { if (is_card_in_play(S2_RUPERTS_LIFEGUARD)) { if (should_rout_card(S2_NORTHERN_HORSE)) { log("Rupert's Lifeguard added to Northern Horse.") - map_set(game.sticks, S2_NORTHERN_HORSE, 1) - eliminate_card(S2_RUPERTS_LIFEGUARD) + set_sticks(S2_NORTHERN_HORSE, 1) + remove_card(S2_RUPERTS_LIFEGUARD) } if (should_rout_card(S2_BYRON)) { log("Rupert's Lifeguard added to Byron.") - map_set(game.sticks, S2_BYRON, 1) - eliminate_card(S2_RUPERTS_LIFEGUARD) + set_sticks(S2_BYRON, 1) + remove_card(S2_RUPERTS_LIFEGUARD) } } } @@ -2597,14 +2644,17 @@ states.routing = { log(card_name(c) + " routed.") let p = find_card_owner(c) game.routed[p] += data.cards[c].morale + rout_card(c) } else if (should_retire_card(c)) { log(card_name(c) + " retired.") + retire_card(c) } else if (should_pursue(c)) { log(card_name(c) + " pursued.") + pursue_card(c) } else { log(card_name(c) + " removed.") + remove_card(c) } - eliminate_card(c) resume_routing() }, } @@ -2627,7 +2677,7 @@ function end_routing() { game.morale[0] += game.routed[1] } } - if (check_morale_victory()) + if (check_victory()) return } else { // SPECIAL: S3 - Plains of Abraham @@ -2665,9 +2715,9 @@ function should_enter_reserve(c) { if (game.scenario === S37_INKERMAN) { if (c === S37_BRITISH_TROOPS) - return map_get(game.cubes, S37_THE_FOG, 0) === 1 + return get_cubes(S37_THE_FOG) === 1 if (c === S37_FRENCH_TROOPS) - return map_get(game.cubes, S37_THE_FOG, 0) === 0 + return get_cubes(S37_THE_FOG) === 0 } if (Array.isArray(reserve)) { |