summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2023-12-13 22:53:36 +0100
committerTor Andersson <tor@ccxvii.net>2024-01-08 16:36:48 +0100
commit40fd74d16cdd2749c512e38989783894b001d7b2 (patch)
treee044fa6bb8441fd3956283e7209c65116993c199
parentd6373f1887af64b092d55fa07d70ad383721405b (diff)
downloadtable-battles-40fd74d16cdd2749c512e38989783894b001d7b2.tar.gz
Tactical victory check.
-rw-r--r--play.html4
-rw-r--r--play.js11
-rw-r--r--rules.js130
3 files changed, 103 insertions, 42 deletions
diff --git a/play.html b/play.html
index 18bf4df..449fe99 100644
--- a/play.html
+++ b/play.html
@@ -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>
diff --git a/play.js b/play.js
index 6d8eca6..5b5566e 100644
--- a/play.js
+++ b/play.js
@@ -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)
diff --git a/rules.js b/rules.js
index 16f1e22..c45b788 100644
--- a/rules.js
+++ b/rules.js
@@ -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)) {