summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2023-12-13 15:08:05 +0100
committerTor Andersson <tor@ccxvii.net>2024-01-08 16:36:48 +0100
commitc5e067628fa60914c388164e72716e487862e68e (patch)
tree60e970a75ce27785eee35c0401605f3ba48215e0
parent82c6128f10dd19174b572e373e92810878b9f5d4 (diff)
downloadtable-battles-c5e067628fa60914c388164e72716e487862e68e.tar.gz
Breastworks!
-rw-r--r--data.js2
-rw-r--r--play.js6
-rw-r--r--rules.js147
-rw-r--r--tools/gendata.js6
4 files changed, 136 insertions, 25 deletions
diff --git a/data.js b/data.js
index 2191db8..8eb67c2 100644
--- a/data.js
+++ b/data.js
@@ -10989,7 +10989,7 @@ cards: [
"name": "Geary",
"wing": 3,
"morale": 1,
- "strength": null,
+ "special": 5,
"dice": "Straight 4/3",
"actions": [],
"rules": {
diff --git a/play.js b/play.js
index 906f571..9a13a14 100644
--- a/play.js
+++ b/play.js
@@ -155,6 +155,10 @@ function create_formation_card(id) {
append_div(e, "strength", "II")
else if (card.special === 3)
append_div(e, "strength", "III")
+ else if (card.special === 4)
+ append_div(e, "strength", "IV")
+ else if (card.special === 5)
+ append_div(e, "strength", "V")
else
append_div(e, "strength", card.strength)
@@ -241,6 +245,8 @@ function fill_card_row(top, parent, list) {
x = view.self
if (view.target === id)
x = view.hits
+ if (view.target2 === id)
+ x = view.hits2
for (let i = 0; i < x && i < n; ++i)
add_hit_stick(ui.slot_sticks[id])
for (let i = x; i < n; ++i)
diff --git a/rules.js b/rules.js
index aa246c5..eb381ea 100644
--- a/rules.js
+++ b/rules.js
@@ -19,16 +19,11 @@ Special card rules implemented:
rout_with
remove_with
wild
-
-TODO:
attack_reserve
attack_choose_target
take_from
-
-TODO: extra input steps
may_take_from
-
*/
// TODO: morale cube limit (cannot place on special if maxed)
@@ -133,6 +128,11 @@ exports.view = function (state, player) {
self: game.self,
}
+ if (game.target2 >= 0 && game.hits2 >= 0) {
+ view.target2 = game.target2
+ view.hits2 = game.hits2
+ }
+
if (game.state === "game_over") {
view.prompt = game.victory
} else if (player !== game.active) {
@@ -244,6 +244,10 @@ const S25_KELLY = find_card(25, "Kelly")
const S26_PEACH_ORCHARD = find_scenario(26)
const S26_FATAL_BLUNDER = find_card(26, "Fatal Blunder")
+const S28_CULPS_HILL = find_scenario(28)
+const S28_BREASTWORKS = find_card(28, "Breastworks")
+const S28_GEARY = find_card(28, "Geary")
+
// === SETUP ===
exports.setup = function (seed, scenario, options) {
@@ -284,6 +288,7 @@ exports.setup = function (seed, scenario, options) {
reserve: [ [], [] ],
// dice value placed on what card
+ rolled: 0,
placed: [],
// current action
@@ -292,6 +297,11 @@ exports.setup = function (seed, scenario, options) {
target: -1,
hits: 0,
self: 0,
+
+ // for breastworks etc
+ self2: 0,
+ target2: -1,
+ hits2: 0,
}
function setup_formation(front, reserve, c) {
@@ -570,6 +580,15 @@ function placed_any_dice_on_wing(w) {
return false
}
+function is_straight_4_or_3(c) {
+ if (game.scenario === S28_CULPS_HILL) {
+ if (game.rolled >= 5)
+ return 4
+ return 3
+ }
+ throw new Error("Missing rule for Straight 3/4 choice")
+}
+
const place_dice_once = {
"(1)": true,
"(2)": true,
@@ -595,6 +614,7 @@ const place_dice_once = {
const place_dice_check = {
"Full House": check_full_house,
+ "Straight 4/3": check_straight_4_or_3,
"Straight 3": check_straight_3,
"Straight 4": check_straight_4,
"Doubles": check_doubles,
@@ -647,6 +667,7 @@ const place_dice_check = {
const place_dice_gen = {
"Full House": gen_full_house,
+ "Straight 4/3": gen_straight_4_or_3,
"Straight 3": gen_straight_3,
"Straight 4": gen_straight_4,
"Doubles": gen_doubles,
@@ -700,6 +721,7 @@ const place_dice_gen = {
const place_dice_take = {
"Full House": take_full_house,
+ "Straight 4/3": take_straight_4_or_3,
"Straight 3": take_straight_3,
"Straight 4": take_straight_4,
"Doubles": take_doubles,
@@ -870,6 +892,13 @@ function check_all_4(c, x, y, z, w) {
return pool_has_single(x) && pool_has_single(y) && pool_has_single(z) && pool_has_single(w)
}
+function check_straight_4_or_3(c) {
+ if (is_straight_4_or_3(c) === 4)
+ check_straight_4(c)
+ else
+ check_straight_3(c)
+}
+
function check_straight_3(c) {
return (
check_all_3(c, 1, 2, 3) ||
@@ -939,6 +968,13 @@ function gen_range(c, lo, hi) {
gen_single(c, v)
}
+function gen_straight_4_or_3(c) {
+ if (is_straight_4_or_3(c) === 4)
+ gen_straight_4(c)
+ else
+ gen_straight_3(c)
+}
+
function gen_straight_3(c) {
if (check_all_3(c, 1, 2, 3))
gen_pool_die(1)
@@ -1026,6 +1062,13 @@ function take_full_house(c, d) {
}
}
+function take_straight_4_or_3(c, d) {
+ if (is_straight_4_or_3(c) === 4)
+ take_straight_4(c, d)
+ else
+ take_straight_3(c, d)
+}
+
function take_straight_3(c, d) {
let v = get_dice_value(d)
take_single(c, d)
@@ -1044,6 +1087,7 @@ function take_straight_4(c, d) {
function goto_roll_phase() {
game.selected = -1
game.target = -1
+ game.target2 = -1
game.action = 0
game.state = "roll"
@@ -1085,12 +1129,16 @@ states.roll = {
}
function roll_dice_in_pool() {
+ game.rolled = 0
if (game.reacted === player_index())
game.reacted = -1
let p = player_index()
- for (let i = 0; i < 6; ++i)
- if (get_player_dice_location(p, i) < 0)
+ for (let i = 0; i < 6; ++i) {
+ if (get_player_dice_location(p, i) < 0) {
set_player_dice_value(p, i, random(6) + 1)
+ game.rolled++
+ }
+ }
game.state = "place"
}
@@ -1468,9 +1516,13 @@ function goto_action_phase() {
}
function end_action_phase() {
- game.hits = game.self = 0
+ game.hits = 0
+ game.self = 0
+ game.hits2 = 0
+ game.self2 = 0
game.selected = -1
game.target = -1
+ game.target2 = -1
goto_routing()
}
@@ -1673,8 +1725,17 @@ function goto_attack(target) {
game.state = "attack"
game.target = target
+
+ update_attack1()
+ update_attack2()
+}
+
+// Update hits and self hits.
+function update_attack1() {
+ let a = current_action()
+
game.hits = get_attack_hits(game.selected, a)
- game.self = get_attack_self(game.selected, a)
+ game.self = get_attack_self(game.selected, a) + game.self2
if (game.scenario === S2_MARSTON_MOOR) {
if (is_card_in_play(S2_RUPERTS_LIFEGUARD)) {
@@ -1722,6 +1783,26 @@ function goto_attack(target) {
game.hits = Math.max(0, game.hits - 1)
}
+// Update hits and self hits for defensive abilities that redirect or steal hits.
+function update_attack2() {
+ if (game.scenario === S28_CULPS_HILL) {
+ if (is_card_in_play(S28_BREASTWORKS)) {
+ if (data.cards[game.target].wing === DKBLUE) {
+ if (game.hits > 0) {
+ game.target2 = S28_BREASTWORKS
+ if (game.hits > 1) {
+ game.hits2 = game.hits - 1
+ game.hits = 1
+ } else {
+ game.hits2 = 1
+ game.hits = 0
+ }
+ }
+ }
+ }
+ }
+}
+
states.attack = {
prompt() {
view.prompt = "Attack " + card_name(game.target) + "."
@@ -1737,6 +1818,12 @@ states.attack = {
gen_action_dice_on_card(from)
}
+ let may_take_from_extra = card_has_rule(game.selected, "may_take_from_extra_self")
+ if (may_take_from_extra) {
+ for (let from of may_take_from_extra)
+ gen_action_dice_on_card(from)
+ }
+
view.actions.attack = 1
},
attack() {
@@ -1761,15 +1848,27 @@ states.attack = {
let may_take_from = card_has_rule(game.selected, "may_take_from")
if (may_take_from) {
move_dice(get_dice_location(d), game.selected)
- goto_attack(game.target) // recompute hits
+ update_attack1()
+ update_attack2()
+ }
+ let may_take_from_extra = card_has_rule(game.selected, "may_take_from_extra_self")
+ if (may_take_from_extra) {
+ move_dice(get_dice_location(d), game.selected)
+ game.self2 = 1
+ update_attack1()
+ update_attack2()
}
}
}
function resume_attack() {
- apply_hits(game.hits)
- apply_self(game.self)
pay_for_action(game.selected)
+
+ remove_sticks(game.selected, game.self)
+ remove_sticks(game.target, game.hits)
+ if (game.target2 >= 0)
+ remove_sticks(game.target2, game.hits2)
+
end_action_phase()
}
@@ -1788,7 +1887,6 @@ function goto_command() {
game.state = "command"
}
-
states.command = {
prompt() {
let list = find_all_targets_of_command(current_action())
@@ -2001,6 +2099,8 @@ function goto_screen(c, a) {
game.target = c
+ update_attack1()
+
switch (a.effect)
{
default:
@@ -2018,6 +2118,8 @@ function goto_screen(c, a) {
break
}
+ update_attack2()
+
game.state = "screen"
}
@@ -2044,6 +2146,8 @@ function goto_absorb(c, a) {
game.target = c
+ update_attack1()
+
switch (a.effect)
{
default:
@@ -2060,6 +2164,8 @@ function goto_absorb(c, a) {
break
}
+ update_attack2()
+
game.state = "absorb"
}
@@ -2080,6 +2186,8 @@ states.absorb = {
function goto_counterattack(c, a) {
game.reacted = player_index()
+ update_attack1()
+
switch (a.effect)
{
default:
@@ -2112,6 +2220,8 @@ function goto_counterattack(c, a) {
break
}
+ update_attack2()
+
game.state = "counterattack"
}
@@ -2129,14 +2239,6 @@ states.counterattack = {
// === ATTACK EFFECTS ===
-function apply_self(n) {
- remove_sticks(game.selected, n)
-}
-
-function apply_hits(n) {
- remove_sticks(game.target, n)
-}
-
function get_attack_hits(c, a) {
switch (a.effect) {
default:
@@ -2329,8 +2431,6 @@ states.routing = {
}
function end_routing() {
- console.log("END ROUTING", game.routed)
-
// Normal morale loss and gain
if (game.morale[0] > 0 && game.morale[1] > 0) {
if ((game.routed[0] > 0 && !game.routed[1]) || (game.routed[1] > 0 && !game.routed[0])) {
@@ -2414,7 +2514,6 @@ function end_reserve() {
goto_roll_phase()
}
-
states.reserve = {
prompt() {
view.prompt = "Enter reserves!"
diff --git a/tools/gendata.js b/tools/gendata.js
index 0dce472..1b451c4 100644
--- a/tools/gendata.js
+++ b/tools/gendata.js
@@ -62,6 +62,12 @@ for (let c of card_records) {
else if (c.strength === "III") {
card.special = 3
}
+ else if (c.strength === "IV") {
+ card.special = 4
+ }
+ else if (c.strength === "V") {
+ card.special = 5
+ }
else if (c.strength.endsWith("*")) {
card.strength = parseInt(c.strength)
card.morale = 2