summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2023-12-12 17:25:54 +0100
committerTor Andersson <tor@ccxvii.net>2024-01-08 16:36:48 +0100
commit6195f24fc7abb0453356464a1f923a699e89da00 (patch)
tree9c18acf4b60cb077bb1a6131643926b218237f96
parentcc45c704c8338d92dca9e37c3fe448f58c0e4ad5 (diff)
downloadtable-battles-6195f24fc7abb0453356464a1f923a699e89da00.tar.gz
Wild die.
-rw-r--r--rules.js115
1 files changed, 90 insertions, 25 deletions
diff --git a/rules.js b/rules.js
index 2bd3dcd..c104d37 100644
--- a/rules.js
+++ b/rules.js
@@ -14,11 +14,11 @@ Special card rules implemented:
suffer_1_less
start_with_no_cubes
take_from
+ wild
TODO:
rout_with
remove_with
- wild
attack_reserve
no_morale
@@ -217,6 +217,7 @@ const S9_SOPWELL_LANE = find_card(9, "Sopwell Lane")
const S9_ARCHERS = find_card(9, "Archers")
const S9_WARWICK = find_card(9, "Warwick")
+const S11_MORTIMERS_CROSS = find_scenario(11)
const S12_TOWTON = find_scenario(12)
const S13_EDGECOTE_MOOR = find_scenario(13)
@@ -225,6 +226,8 @@ const S15_A_PLUMP_OF_SPEARS = find_card(15, "A Plump of Spears")
const S15_SOMERSET = find_card(15, "Somerset")
const S15_WENLOCK = find_card(15, "Wenlock")
+const S16_STOKE_FIELD = find_scenario(16)
+
// === SETUP ===
exports.setup = function (seed, scenario, options) {
@@ -389,6 +392,16 @@ function move_dice(from, to) {
}
}
+function take_wild_die(from, to) {
+ for (let i = 0; i < 12; ++i) {
+ if (get_dice_location(i) === from) {
+ set_dice_location(i, to)
+ set_dice_value(i, 0)
+ to = POOL
+ }
+ }
+}
+
function eliminate_card(c) {
remove_dice(c)
remove_cubes(c, 3)
@@ -1076,6 +1089,19 @@ function end_roll_phase() {
// === ACTION PHASE ===
+function side_get_wild_die_card(p) {
+ if (game.scenario === S11_MORTIMERS_CROSS || game.scenario === S12_TOWTON || game.scenario === S16_STOKE_FIELD) {
+ for (let c of game.front[p])
+ if (card_has_rule(c, "wild") && has_any_dice_on_card(c))
+ return c
+ }
+ return -1
+}
+
+function side_has_wild_die(p) {
+ return side_get_wild_die_card(p) >= 0
+}
+
function has_any_dice_on_card(c) {
for (let i = 0; i < 12; ++i)
if (get_dice_location(i) === c)
@@ -1150,12 +1176,15 @@ function check_cube_requirement(c, req) {
}
}
-function check_dice_requirement(c, req) {
+function check_dice_requirement(c, req, wild) {
switch (req) {
case "Full House":
return require_full_house(c)
case "Pair":
case "Pair, Voluntary":
+ // NOTE: Only requirement needed for Wild die scenarios.
+ if (wild)
+ return has_any_dice_on_card(c)
return require_pair(c)
case "Triplet":
return require_triplet(c)
@@ -1217,7 +1246,7 @@ function can_take_action(c, a) {
if (data.cards[c].special)
return check_cube_requirement(c, a.requirement)
else
- return check_dice_requirement(c, a.requirement)
+ return check_dice_requirement(c, a.requirement, false)
}
return false
}
@@ -1454,6 +1483,11 @@ states.attack = {
prompt() {
view.prompt = "Attack " + card_name(game.target) + "."
gen_action_card(game.target)
+
+ let w = side_get_wild_die_card(player_index())
+ if (w >= 0)
+ gen_action_dice_on_card(w)
+
view.actions.attack = 1
},
attack() {
@@ -1469,6 +1503,13 @@ states.attack = {
card(_) {
this.attack()
},
+ die(d) {
+ let w = side_get_wild_die_card(player_index())
+ if (w === get_dice_location(d)) {
+ log("Wild die from C" + w + ".")
+ take_wild_die(w, game.selected)
+ }
+ }
}
function resume_attack() {
@@ -1546,29 +1587,30 @@ states.command = {
function can_opponent_react() {
let p = 1 - player_index()
+ let wild = side_has_wild_die(p)
for (let c of game.front[p])
- if (can_card_react(c))
+ if (can_card_react(c, wild))
return true
return false
}
-function can_card_react(c) {
+function can_card_react(c, wild) {
let has_dice = has_any_dice_on_card(c)
let has_cube = has_any_cubes_on_card(c)
if (has_dice || has_cube) {
if (data.cards[c].actions.length >= 1)
if (is_reaction(c, data.cards[c].actions[0]))
- if (can_take_reaction(c, data.cards[c].actions[0]))
+ if (can_take_reaction(c, data.cards[c].actions[0], wild))
return true
if (data.cards[c].actions.length >= 2)
if (is_reaction(c, data.cards[c].actions[1]))
- if (can_take_reaction(c, data.cards[c].actions[1]))
+ if (can_take_reaction(c, data.cards[c].actions[1], wild))
return true
}
return false
}
-function can_take_reaction(c, a) {
+function can_take_reaction(c, a, wild) {
switch (a.type) {
default:
throw new Error("invalid reaction: " + a.type)
@@ -1612,49 +1654,63 @@ function can_take_reaction(c, a) {
if (data.cards[c].special)
return check_cube_requirement(c, a.requirement)
else
- return check_dice_requirement(c, a.requirement)
+ return check_dice_requirement(c, a.requirement, wild)
+}
+
+function take_wild_die_if_needed_for_reaction(c, ix) {
+ let w = side_get_wild_die_card(player_index())
+ if (w >= 0) {
+ let a = data.cards[c].actions[ix]
+ if (!can_take_reaction(c, a, false)) {
+ log("Wild die from C" + w + ".")
+ take_wild_die(w, c)
+ }
+ }
}
states.react = {
prompt() {
view.prompt = card_name(game.selected) + " attacks " + card_name(game.target) + "!"
-
let voluntary = true
let p = player_index()
+ let wild = side_has_wild_die(p)
for (let c of game.front[p]) {
let has_dice = has_any_dice_on_card(c)
let has_cube = has_any_cubes_on_card(c)
if (has_dice || has_cube) {
- if (data.cards[c].actions.length >= 1) {
- if (is_reaction(c, data.cards[c].actions[0])) {
- if (can_take_reaction(c, data.cards[c].actions[0])) {
- if (is_mandatory_reaction(c, data.cards[c].actions[0]))
- voluntary = false
- gen_action_action1(c)
+ for (let i = 0; i < data.cards[c].actions.length; ++i) {
+ let a = data.cards[c].actions[i]
+ if (is_reaction(c, a)) {
+ let must = false
+ let may = false
+ if (is_mandatory_reaction(c, a)) {
+ must = can_take_reaction(c, a, false)
+ if (!must && wild && can_take_reaction(c, a, true))
+ may = true
+ } else {
+ may = can_take_reaction(c, a, wild)
}
- }
- }
- if (data.cards[c].actions.length >= 2) {
- if (is_reaction(c, data.cards[c].actions[1])) {
- if (can_take_reaction(c, data.cards[c].actions[1])) {
- if (is_mandatory_reaction(c, data.cards[c].actions[1]))
- voluntary = false
- gen_action_action2(c)
+ if (must)
+ voluntary = false
+ if (must || may) {
+ if (i === 0) gen_action_action1(c)
+ if (i === 1) gen_action_action2(c)
}
}
}
}
}
-
if (voluntary)
view.actions.pass = 1
},
a1(c) {
push_undo()
+ take_wild_die_if_needed_for_reaction(c, 0)
goto_take_reaction(c, 0)
},
a2(c) {
push_undo()
+ take_wild_die_if_needed_for_reaction(c, 1)
goto_take_reaction(c, 1)
},
pass() {
@@ -2124,6 +2180,15 @@ function gen_action(action, argument) {
set_add(view.actions[action], argument)
}
+function gen_action_dice_on_card(c) {
+ for (let d = 0; d < 12; ++d) {
+ if (get_dice_location(d) === c) {
+ gen_action_die(d)
+ return
+ }
+ }
+}
+
function gen_action_card(c) {
gen_action("card", c)
}