summaryrefslogtreecommitdiff
path: root/rules.js
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2023-12-08 13:31:38 +0100
committerTor Andersson <tor@ccxvii.net>2024-01-08 16:36:47 +0100
commitffd5446e54d50038da27fa7c2fb228e7b3e320ae (patch)
treee24364bbfa431e81a04e785cd4c43e541fc19ba4 /rules.js
parent2e0776498cd8b992c060bc578107d83e571fd45b (diff)
downloadtable-battles-ffd5446e54d50038da27fa7c2fb228e7b3e320ae.tar.gz
Show result of reactions. Manual routing.
Diffstat (limited to 'rules.js')
-rw-r--r--rules.js210
1 files changed, 163 insertions, 47 deletions
diff --git a/rules.js b/rules.js
index 9fbb6e8..95008f4 100644
--- a/rules.js
+++ b/rules.js
@@ -154,6 +154,8 @@ exports.setup = function (seed, scenario, options) {
active: P1,
state: "roll",
+ reacted: 0,
+
// dice value and position
dice: [
0, POOL, 0, POOL, 0, POOL, 0, POOL, 0, POOL, 0, POOL,
@@ -174,8 +176,10 @@ exports.setup = function (seed, scenario, options) {
placed: [],
// current action
+ routed: [ 0, 0 ],
selected: -1,
target: -1,
+ screen: -1,
hits: 0,
self: 0,
}
@@ -208,6 +212,20 @@ exports.setup = function (seed, scenario, options) {
// === GAME STATE ACCESSORS ===
+function for_each_front_card(fn) {
+ for (let c of game.front[0])
+ fn(c)
+ for (let c of game.front[1])
+ fn(c)
+}
+
+function for_each_reserve_card(fn) {
+ for (let c of game.reserve[0])
+ fn(c)
+ for (let c of game.reserve[1])
+ fn(c)
+}
+
function card_number(c) {
return data.cards[c].number
}
@@ -1010,10 +1028,7 @@ function goto_action_phase() {
}
function end_action_phase() {
- check_routing()
- check_pursuit()
- check_reserve()
- goto_roll_phase()
+ goto_routing()
}
states.action = {
@@ -1165,8 +1180,8 @@ states.attack = {
gen_action_card(game.target)
view.actions.attack = 1
},
- card(c) {
- log(card_name(game.selected) + " attacked " + card_name(c) + ".")
+ attack() {
+ log(card_name(game.selected) + " attacked " + card_name(game.target) + ".")
if (can_opponent_react()) {
clear_undo()
set_opponent_active()
@@ -1175,6 +1190,9 @@ states.attack = {
resume_attack()
}
},
+ card(_) {
+ this.attack()
+ },
}
function resume_attack() {
@@ -1185,6 +1203,7 @@ function resume_attack() {
game.hits = game.self = 0
game.selected = -1
game.target = -1
+ game.screen = -1
end_action_phase()
}
@@ -1306,9 +1325,11 @@ states.react = {
view.actions.pass = 1
},
a1(c) {
+ push_undo()
goto_take_reaction(c, 0)
},
a2(c) {
+ push_undo()
goto_take_reaction(c, 1)
},
pass() {
@@ -1332,31 +1353,49 @@ function goto_take_reaction(c, ix) {
}
}
+function end_reaction() {
+ set_opponent_active()
+ resume_attack()
+}
+
+// === SCREEN ===
+
function goto_screen(c, a) {
- log(card_name(c) + " screened.")
game.reacted = 1
- pay_for_action(c)
+
+ game.screen = c
switch (a.effect)
{
default:
throw new Error("invalid screen effect: " + a.effect)
- case "":
+ case undefined:
game.hits = 0
game.self = 0
break
}
- set_opponent_active()
- resume_attack()
+ game.state = "screen"
}
+states.screen = {
+ prompt() {
+ view.prompt = "Screen attack from " + card_name(game.selected) + "."
+ view.actions.screen = 1
+ },
+ screen() {
+ log(card_name(game.screen) + " screened.")
+ pay_for_action(game.screen)
+ end_reaction()
+ },
+}
+
+// === ABSORB ===
+
function goto_absorb(c, a) {
- log(card_name(c) + " absorbed.")
+ game.reacted = 1
game.target = c
- game.reacted = 1
- pay_for_action(c)
switch (a.effect)
{
@@ -1372,14 +1411,25 @@ function goto_absorb(c, a) {
break
}
- set_opponent_active()
- resume_attack()
+ game.state = "absorb"
}
+states.absorb = {
+ prompt() {
+ view.prompt = "Absorb attack from " + card_name(game.selected) + "."
+ view.actions.absorb = 1
+ },
+ absorb() {
+ log(card_name(game.target) + " absorbed.")
+ pay_for_action(game.target)
+ end_reaction()
+ },
+}
+
+// === COUNTERATTACK ===
+
function goto_counterattack(c, a) {
- pay_for_action(c)
game.reacted = 1
- log(card_name(c) + " counterattacked.")
switch (a.effect)
{
@@ -1413,8 +1463,19 @@ function goto_counterattack(c, a) {
break
}
- set_opponent_active()
- resume_attack()
+ game.state = "counterattack"
+}
+
+states.counterattack = {
+ prompt() {
+ view.prompt = "Counterattack " + card_name(game.selected) + "."
+ view.actions.counterattack = 1
+ },
+ counterattack() {
+ log(card_name(game.target) + " counterattacked.")
+ pay_for_action(game.target)
+ end_reaction()
+ },
}
// === ATTACK EFFECTS ===
@@ -1463,47 +1524,97 @@ function get_attack_self(c, a) {
// === ROUTING ===
-function check_routing() {
- // Rout cards with no sticks.
- let routed = [ 0, 0 ]
- for (let p = 0; p <= 1; ++p) {
- for (let i = 0; i < game.front[p].length; ++i) {
- let c = game.front[p][i]
- if (!data.cards[c].special) {
- if (map_get(game.sticks, c, 0) === 0) {
- log(card_name(c) + " routed.")
- if (data.cards[c].star)
- routed[p] = 2
- else
- routed[p] = 1
- eliminate_card(c)
- --i;
- }
- }
- }
- }
+function should_remove_card(c) {
+ // TODO: remove after X routs special rules
+ return false
+}
+
+function should_rout_card(c) {
+ // TODO: rout after X routs special rules
+ if (!data.cards[c].special)
+ if (map_get(game.sticks, c, 0) === 0)
+ return true
+ return false
+}
+function goto_routing() {
+ game.routed = [ 0, 0 ]
+ resume_routing()
+}
+
+function resume_routing() {
+ game.state = "routing"
+ for (let p = 0; p <= 1; ++p)
+ for (let c of game.front[p])
+ if (should_rout_card(c) || should_remove_card(c))
+ return
+ end_routing()
+}
+
+function end_routing() {
// Morale loss
- if ((routed[0] > 0 && !routed[1]) || (routed[1] > 0 && !routed[0])) {
- if (routed[0]) {
- routed[0] = Math.min(routed[0], game.morale[0])
- game.morale[0] -= routed[0]
- game.morale[1] += routed[0]
+ if ((game.routed[0] > 0 && !game.routed[1]) || (game.routed[1] > 0 && !game.routed[0])) {
+ if (game.routed[0]) {
+ game.routed[0] = Math.min(game.routed[0], game.morale[0])
+ game.morale[0] -= game.routed[0]
+ game.morale[1] += game.routed[0]
} else {
- routed[1] = Math.min(routed[1], game.morale[1])
- game.morale[1] -= routed[1]
- game.morale[0] += routed[1]
+ game.routed[1] = Math.min(game.routed[1], game.morale[1])
+ game.morale[1] -= game.routed[1]
+ game.morale[0] += game.routed[1]
}
}
+ game.routed = null
+
if (check_morale_loss(0))
return goto_game_over(P2, P1 + " has run out of morale!")
if (check_morale_loss(1))
return goto_game_over(P1, P2 + " has run out of morale!")
+
+ goto_pursuit()
+}
+
+function find_card_owner(c) {
+ if (set_has(game.front[0], c))
+ return 0
+ if (set_has(game.front[1], c))
+ return 1
+ throw new Error("card not found in any front")
+}
+
+states.routing = {
+ prompt() {
+ view.prompt = "Rout cards with no remaining sticks!"
+ for (let p = 0; p <= 1; ++p)
+ for (let c of game.front[p])
+ if (should_rout_card(c) || should_remove_card(c))
+ gen_action_card(c)
+ },
+ card(c) {
+ push_undo() // XXX
+ if (should_rout_card(c)) {
+ log(card_name(c) + " routed.")
+ let p = find_card_owner(c)
+ if (data.cards[c].star)
+ game.routed[p] = 2
+ else
+ game.routed[p] = 1
+ } else {
+ log(card_name(c) + " removed.")
+ }
+ eliminate_card(c)
+ resume_routing()
+ },
}
// === PURSUIT ===
+function goto_pursuit() {
+ check_pursuit()
+ goto_reserve()
+}
+
function check_pursuit() {
// Remove pursuing cards.
for (let p = 0; p <= 1; ++p) {
@@ -1523,6 +1634,11 @@ function check_pursuit() {
// === RESERVE ===
+function goto_reserve() {
+ check_reserve()
+ goto_roll_phase()
+}
+
function check_reserve() {
// Bring on reserves (on both sides).
for (let p = 0; p <= 1; ++p) {