summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--rules.js120
-rw-r--r--tools/gendata.js4
2 files changed, 93 insertions, 31 deletions
diff --git a/rules.js b/rules.js
index 84c6929..e1574a6 100644
--- a/rules.js
+++ b/rules.js
@@ -1,5 +1,19 @@
"use strict"
+/*
+
+Special scenario rules implemented:
+ zero morale - instant loss
+
+Special card rules implemented:
+
+ place_2_blue
+ place_2_red
+ remove_after_screen
+ suffer_1_less_1_max
+
+*/
+
// TODO: allow placing dice on full special formations?
// TODO: fizzle when action says to take cards from other dice?
@@ -165,7 +179,7 @@ exports.setup = function (seed, scenario, options) {
// cubes (map special formation -> count)
cubes: [],
- morale: [ info.players[0].morale || -1, info.players[1].morale || -1 ],
+ morale: [ info.players[0].morale, info.players[1].morale ],
front: [ [], [], ],
reserve: [ [], [] ],
@@ -176,7 +190,6 @@ exports.setup = function (seed, scenario, options) {
routed: [ 0, 0 ],
selected: -1,
target: -1,
- screen: -1,
hits: 0,
self: 0,
}
@@ -209,6 +222,13 @@ exports.setup = function (seed, scenario, options) {
// === GAME STATE ACCESSORS ===
+function card_has_rule(c, name) {
+ let rules = data.cards[c].rules
+ if (rules)
+ return rules[name]
+ return false
+}
+
function card_number(c) {
return data.cards[c].number
}
@@ -344,10 +364,6 @@ function check_impossible_to_attack_victory() {
return true
}
-function check_morale_loss(p) {
- return game.morale[p] === 0
-}
-
// === ROLL PHASE ===
function is_pool_die(i, v) {
@@ -551,10 +567,11 @@ function can_place_dice(c) {
n_wing ++
}
}
-
if (n_wing >= game.place_max[wing])
return false
+ // TODO: 91A Jackson - may only place dice if D.H. Hill has dice
+
if (place_dice_once[pattern]) {
if (map_has(game.placed, c))
return false
@@ -759,17 +776,10 @@ function goto_roll_phase() {
let p = player_index()
for (let c of game.front[p]) {
- let rules = data.cards[c].rules
- if (rules) {
- if (rules.place_2_blue)
- game.place_max[BLUE] = 2
- if (rules.place_2_red)
- game.place_max[RED] = 2
- if (rules.place_2_pink)
- game.place_max[PINK] = 2
- if (rules.place_2_dkblue)
- game.place_max[DKBLUE] = 2
- }
+ if (card_has_rule(c, "place_2_blue"))
+ game.place_max[BLUE] = 2
+ if (card_has_rule(c, "place_2_red"))
+ game.place_max[RED] = 2
}
}
@@ -944,6 +954,7 @@ function check_cube_requirement(c, req) {
switch (req) {
case "3 cubes":
return map_get(game.cubes, c, 0) >= 3
+ case "Voluntary":
case undefined:
return map_get(game.cubes, c, 0) >= 1
default:
@@ -990,6 +1001,10 @@ function can_take_action(c, a) {
return false
if (a.type === "Command" && find_target_of_command(a) < 0)
return false
+
+ // TODO: 38B Clinton (can only attack if other cards have dice)
+ // TODO: 91A Jackson - may only attack if D.H. Hill and one other formation have dice
+
if (a.type === "Bombard" || a.type === "Attack" || a.type === "Command") {
if (data.cards[c].special)
return check_cube_requirement(c, a.requirement)
@@ -1031,6 +1046,7 @@ function goto_action_phase() {
}
function end_action_phase() {
+ // TODO: 38A: the stanleys -- rout Northumberland
goto_routing()
}
@@ -1156,10 +1172,18 @@ states.bombard = {
function goto_attack() {
let a = current_action()
+
+ // TODO: 88B German Infantry - take dice from Saxon Infantry
+
game.state = "attack"
game.target = find_target_of_attack(a)
game.hits = get_attack_hits(game.selected, a)
game.self = get_attack_self(game.selected, a)
+
+ // TODO: 17A: rupert's lifeguard (one less hit)
+
+ if (card_has_rule("suffer_1_less_1_max"))
+ game.hits = Math.max(0, Math.min(1, game.hits - 1))
}
states.attack = {
@@ -1191,7 +1215,6 @@ function resume_attack() {
game.hits = game.self = 0
game.selected = -1
game.target = -1
- game.screen = -1
end_action_phase()
}
@@ -1262,6 +1285,9 @@ function can_take_reaction(c, a) {
break
}
+ // TODO: 21B The English Fleet (if cubes on card, duan juan jose cannot counterattack)
+ // TODO: 21B The English Fleet (if cubes on card, spanish right cav cannot screen)
+
if (data.cards[c].special)
return check_cube_requirement(c, a.requirement)
else
@@ -1341,7 +1367,7 @@ function end_reaction() {
function goto_screen(c, a) {
game.reacted = 1
- game.screen = c
+ game.target = c
switch (a.effect)
{
@@ -1362,8 +1388,12 @@ states.screen = {
view.actions.screen = 1
},
screen() {
- log(card_name(game.screen) + " screened.")
- pay_for_action(game.screen)
+ log(card_name(game.target) + " screened.")
+ pay_for_action(game.target)
+
+ if (card_has_rule(game.target, "remove_after_screen"))
+ eliminate_card(game.target)
+
end_reaction()
},
}
@@ -1517,6 +1547,9 @@ function should_rout_card(c) {
function goto_routing() {
game.routed = [ 0, 0 ]
+
+ // TODO: 17A: rupert's lifeguard (cancel rout)
+
resume_routing()
}
@@ -1530,28 +1563,53 @@ function resume_routing() {
}
function end_routing() {
- // Morale loss and gain (except for S3 - Plains of Abraham)
- if (game.morale[0] >= 0 && game.morale[1] >= 0) {
+ // 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])) {
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]
+ // do not gain for special scenarios
+ if (game.morale[1] > 0)
+ game.morale[1] += game.routed[0]
} else {
game.routed[1] = Math.min(game.routed[1], game.morale[1])
game.morale[1] -= game.routed[1]
- game.morale[0] += game.routed[1]
+ // do not gain for special scenarios
+ if (game.morale[0] > 0)
+ game.morale[0] += game.routed[1]
}
}
+ 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!")
+ } else {
+ // SPECIAL: S3 - Plains of Abraham
+ // SPECIAL: S34 - Tippermuir - Royalists
+ // SPECIAL: S35 - Auldearn - Royalists
+
+ // Instant loss if any card routs for side at 0 morale (S3, S34, S35).
+ if (game.morale[0] === 0 && game.routed[0])
+ return goto_game_over(P2, P1 + " card routed!")
+ if (game.morale[1] === 0 && game.routed[1])
+ return goto_game_over(P1, P2 + " card routed!")
+
+ // Remove instead of take cubes for side at 0 morale
+ if (game.routed[0]) {
+ game.morale[0] -= Math.min(game.routed[0], game.morale[0])
+ if (game.morale[0] === 0)
+ return goto_game_over(P2, P1 + " has run out of morale!")
+ }
+ if (game.routed[1]) {
+ game.morale[1] -= Math.min(game.routed[1], game.morale[1])
+ if (game.morale[1] === 0)
+ return goto_game_over(P1, P2 + " has run out of morale!")
+ }
}
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()
}
diff --git a/tools/gendata.js b/tools/gendata.js
index dce36b3..bb9c606 100644
--- a/tools/gendata.js
+++ b/tools/gendata.js
@@ -176,6 +176,8 @@ for (let c of card_records) {
card.pursuit = 1
else if (c.reserve === "Commanded")
card.reserve = []
+ else if (c.reserve === "See Above")
+ card.reserve = []
else
card.reserve = c.reserve.split(" or ")
@@ -247,6 +249,8 @@ for (let c of cards) {
a.target_list = find_friendly_cards(c.scenario, c.wing)
else if (a.target === "Any friendly Pink formation")
a.target_list = find_wing_cards(c.scenario, WING.pink)
+ else if (a.target === "Any other Pink formation")
+ a.target_list = find_wing_cards(c.scenario, WING.pink).filter(x => x !== c)
else if (a.target === "Any Red formation")
a.target_list = find_wing_cards(c.scenario, WING.red)
else if (a.target.startsWith("Any attack on "))