summaryrefslogtreecommitdiff
path: root/rules.js
diff options
context:
space:
mode:
Diffstat (limited to 'rules.js')
-rw-r--r--rules.js373
1 files changed, 180 insertions, 193 deletions
diff --git a/rules.js b/rules.js
index 9f1a92b..d3e49aa 100644
--- a/rules.js
+++ b/rules.js
@@ -2,10 +2,6 @@
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)
@@ -20,9 +16,9 @@ function find_card(s, n) {
return ix
}
-// for (let c of data.cards) for (let a of c.actions) console.log(a.type, a.effect)
+for (let c of data.cards) for (let a of c.actions) console.log(a.type + ":", a.effect)
// for (let c of data.cards) console.log(c.dice)
-//for (let c of data.cards) for (let a of c.actions) { if (a.type === "Counterattack") console.log(c.number, a.type, a.sequence, a.target) }
+// for (let c of data.cards) for (let a of c.actions) { if (a.type === "Counterattack") console.log(c.number, a.type, a.sequence, a.target) }
function check_attack_res(c, a) {
if (a.choice)
@@ -53,7 +49,7 @@ function check_attack_res(c, a) {
}
}
-for (let c of data.cards) for (let a of c.actions) { if (a.type === "Attack") check_attack_res(c, a) }
+//for (let c of data.cards) for (let a of c.actions) { if (a.type === "Attack") check_attack_res(c, a) }
const P1 = "First"
const P2 = "Second"
@@ -217,18 +213,8 @@ const S8_CLINTON = find_card(8, "Clinton")
const S8_GRANT = find_card(8, "Grant")
const S8_HESSIANS = find_card(8, "Hessians")
-const S36_PHARSALUS = find_scenario(36)
-const S36_SULLA = find_card(36, "Sulla")
const S36_FOURTH_LINE = find_card(36, "The Fourth Line")
-const S37_INKERMAN = find_scenario(37)
-const S37_PAULOFFS_LEFT = find_card(37, "Pauloff's Left")
-const S37_PAULOFFS_RIGHT = find_card(37, "Pauloff's Right")
-const S37_BRITISH_TROOPS = find_card(37, "British Troops")
-const S37_FRENCH_TROOPS = find_card(37, "French Troops")
-const S37_SOIMONOFF = find_card(37, "Soimonoff")
-const S37_THE_FOG = find_card(37, "The Fog")
-
const S3201_GAINES_MILL = find_scenario(3201)
const S3201_JACKSON = find_card(3201, "Jackson")
const S3201_DH_HILL = find_card(3201, "D.H. Hill")
@@ -240,7 +226,6 @@ const S9_HENRY_VI = find_card(9, "Henry VI")
const S9_SHROPSHIRE_LANE = find_card(9, "Shropshire Lane")
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)
@@ -253,7 +238,6 @@ const S15_WENLOCK = find_card(15, "Wenlock")
const S14_BARNET = find_scenario(14)
const S14_TREASON = find_card(14, "\"Treason!\"")
-const S14_OXFORD = find_card(14, "Oxford")
const S16_STOKE_FIELD = find_scenario(16)
@@ -289,28 +273,32 @@ const S30_STAPLETON = find_card(30, "Stapleton")
const S30_RUPERT = find_card(30, "Rupert of the Rhine")
const S30_WILMOT = find_card(30, "Wilmot")
const S30_ESSEX = find_card(30, "Charles Essex")
-const S30_GERARD = find_card(30, "Gerard")
const S31_NEWBURY_1ST = find_scenario(31)
const S31_BYRON = find_card(31, "Byron")
const S31_SKIPPON = find_card(31, "Skippon")
const S31_WENTWORTH = find_card(31, "Wentworth")
-const S31_ROYALIST_GUNS = find_card(31, "Royalist Guns")
const S31_GERARD = find_card(31, "Gerard")
-const S31_STAPLETON = find_card(31, "Stapleton")
-const S31_LONDON_TRAINED_BANDS = find_card(31, "London Trained Bands")
+
+const S34_TULLIBARDINE = find_card(34, "Tullibardine")
const S35_AULDEARN = find_scenario(35)
const S35_MONTROSE = find_card(35, "Montrose")
const S35_GORDON = find_card(35, "Gordon")
+const S37_INKERMAN = find_scenario(37)
+const S37_PAULOFFS_LEFT = find_card(37, "Pauloff's Left")
+const S37_BRITISH_TROOPS = find_card(37, "British Troops")
+const S37_FRENCH_TROOPS = find_card(37, "French Troops")
+const S37_SOIMONOFF = find_card(37, "Soimonoff")
+const S37_THE_FOG = find_card(37, "The Fog")
+
const S38_FLEURUS = find_scenario(38)
const S38_WALDECK = find_card(38, "Waldeck")
const S38_RETREAT_TO_NIVELLES = find_card(38, "Retreat to Nivelles")
const S38_LUXEMBOURGS_HORSE = find_card(38, "Luxembourg's Horse")
const S38_GOURNAYS_HORSE = find_card(38, "Gournay's Horse")
const S38_DUTCH_LEFT_FOOT = find_card(38, "Dutch Left Foot")
-const S38_FRENCH_RIGHT = find_card(38, "French Right")
const S38_DUTCH_HORSE = find_card(38, "Dutch Horse")
const S39_MARSAGLIA = find_scenario(39)
@@ -331,7 +319,6 @@ const S40_GUTTENSTEIN = find_card(40, "Guttenstein")
const S41_BLENHEIM_SCENARIO = find_scenario(41)
const S41_CLERAMBAULT = find_card(41, "Clerambault")
-const S41_CUTTS_COLUMN = find_card(41, "Cutt's Column")
const S41_BLENHEIM_CARD = find_card(41, "Blenheim")
const S41_PRINCE_EUGENE = find_card(41, "Prince Eugene")
@@ -375,7 +362,6 @@ const S48_BEVERN = find_card(48, "Bevern")
const S48_GRENZERS = find_card(48, "Grenzers")
const S49_LEUTHEN = find_scenario(49)
-const S49_LUCCHESI = find_card(49, "Lucchesi")
const S49_RETZOW = find_card(49, "Retzow")
const S49_COLLOREDO = find_card(49, "Colloredo")
const S49_NADASDY = find_card(49, "Nadasdy")
@@ -760,6 +746,12 @@ function is_removed_from_play(c) {
}
function card_has_active_link(c) {
+ // Ignores Link.
+ if (game.scenario === S48_BRESLAU) {
+ if (game.selected === S48_GRENZERS)
+ return false
+ }
+
let link = data.cards[c].link
if (link) {
for (let t of link)
@@ -2644,8 +2636,141 @@ function goto_attack(target) {
function update_attack1(direct) {
let a = current_action()
- game.hits = get_attack_hits(game.selected, a)
- game.self = get_attack_self(game.selected, a) + game.self2
+ let n = count_dice_on_card(game.selected)
+
+ switch (a.effect) {
+ default:
+ throw new Error("invalid attack effect: " + a.effect)
+
+ case "1 hit per die.":
+ game.self = 0
+ game.hits = n
+ break
+
+ case "1 hit per die. 1 self.":
+ game.self = 1
+ game.hits = n
+ break
+
+ case "1 hit per die. 1 self. If reduced to one stick, no self hits.":
+ if (get_sticks(game.selected) === 1)
+ game.self = 0
+ else
+ game.self = 1
+ game.hits = n
+ break
+
+ case "1 hit per pair.":
+ game.self = 0
+ game.hits = n / 2
+ break
+
+ case "1 hit per pair. 1 self.":
+ game.self = 1
+ game.hits = n / 2
+ break
+
+ case "1 hit plus 1 hit per die.":
+ game.self = 0
+ game.hits = 1 + n
+ break
+ case "1 hit plus 1 hit per die. 1 self.":
+ game.self = 1
+ game.hits = 1 + n
+ break
+
+ case "1 hit. 1 self.":
+ game.self = 1
+ game.hits = 1
+ break
+
+ case "2 hits per die.":
+ game.self = 0
+ game.hits = 2 * n
+ break
+
+ case "2 hits plus 1 hit per die. 1 self.":
+ game.self = 1
+ game.hits = 2 + n
+ break
+
+ case "1 hit.":
+ game.self = 0
+ game.hits = 1
+ break
+
+ case "2 hits.":
+ game.self = 0
+ game.hits = 2
+ break
+
+ case "5 hits.":
+ game.self = 0
+ game.hits = 5
+ break
+
+ case "1 hit per die (2 hits per die versus Blenheim). 1 self.":
+ game.self = 1
+ game.hits = n
+ if (direct && game.target === S41_BLENHEIM_CARD)
+ game.hits = 2 * n
+ break
+
+ case "1 hit per die (2 hits per die versus Villars's Left). 1 self.":
+ game.self = 1
+ game.hits = n
+ if (direct && game.target === S43_VILLARS_LEFT)
+ game.hits = 2 * n
+ break
+
+ case "1 hit per die versus Driesen. 2 hits per die versus Retzow.":
+ case "1 hit per die (2 hits per die versus Retzow).":
+ game.self = 0
+ game.hits = n
+ if (direct && game.target === S49_RETZOW)
+ game.hits = 2 * n
+ break
+
+ case "1 hit per die. 1 extra hit if Fourth Line is in play.":
+ game.self = 0
+ game.hits = n
+ if (is_card_in_play(S36_FOURTH_LINE))
+ game.hits += 1
+ break
+
+ case "1 hit per die. 1 self. 1 extra hit if Dutch Horse routed.":
+ game.self = 1
+ game.hits = n
+ if (is_removed_from_play(S38_DUTCH_HORSE))
+ game.hits += 1
+ break
+
+ case "1 hit per die. 1 self. 1 extra vs Dutch Left Foot.":
+ game.self = 1
+ game.hits = n
+ if (direct && game.target === S38_DUTCH_LEFT_FOOT)
+ game.hits += 1
+ break
+
+ case "1 hit per die. 1 self. 1 extra vs Essex.":
+ game.self = 1
+ game.hits = n
+ if (game.target === S30_ESSEX)
+ game.hits += 1
+ break
+
+ case "1 hit per die. 1 self. 1 extra vs Tullibardine.":
+ game.self = 1
+ game.hits = n
+ if (direct && game.target === S34_TULLIBARDINE)
+ game.hits += 1
+ break
+
+ case "Oxford immediately routs. This attack cannot be screened.":
+ game.self = 0
+ game.hits = 8
+ break
+ }
if (game.scenario === S2_MARSTON_MOOR) {
if (is_card_in_play(S2_RUPERTS_LIFEGUARD)) {
@@ -2656,20 +2781,6 @@ function update_attack1(direct) {
}
}
- if (game.scenario === S36_PHARSALUS) {
- if (game.selected === S36_SULLA) {
- if (is_card_in_play(S36_FOURTH_LINE))
- game.hits += 1
- }
- }
-
- if (game.scenario === S37_INKERMAN) {
- // Until the first Fog Cube is lifted.
- if (get_cubes(S37_THE_FOG) === 3) {
- game.hits -= 1
- }
- }
-
if (game.scenario === S9_ST_ALBANS) {
// Defensive Works (negated by Archers)
if (game.target === S9_SHROPSHIRE_LANE || game.target === S9_SOPWELL_LANE) {
@@ -2694,11 +2805,6 @@ function update_attack1(direct) {
}
}
- if (game.scenario === S30_EDGEHILL) {
- if (game.selected === S30_GERARD && game.target === S30_ESSEX)
- game.hits += 1
- }
-
if (game.scenario === S31_NEWBURY_1ST) {
if (game.selected === S31_WENTWORTH) {
if (has_any_dice_on_card(S31_BYRON) && is_card_in_play(S31_SKIPPON)) {
@@ -2712,12 +2818,10 @@ function update_attack1(direct) {
}
}
- if (game.scenario === S38_FLEURUS) {
- if (game.selected === S38_LUXEMBOURGS_HORSE && game.target === S38_DUTCH_LEFT_FOOT) {
- game.hits += 1
- }
- if (game.selected === S38_FRENCH_RIGHT && is_removed_from_play(S38_DUTCH_HORSE)) {
- game.hits += 1
+ if (game.scenario === S37_INKERMAN) {
+ // Until the first Fog Cube is lifted.
+ if (game.target === S37_SOIMONOFF && get_cubes(S37_THE_FOG) === 3) {
+ game.hits -= 1
}
}
@@ -2732,18 +2836,6 @@ function update_attack1(direct) {
}
}
- if (game.scenario === S41_BLENHEIM_SCENARIO) {
- if (direct)
- if (game.selected === S41_CUTTS_COLUMN && game.target === S41_BLENHEIM_CARD)
- game.hits *= 2
- }
-
- if (game.scenario === S43_DENAIN) {
- if (direct)
- if (game.selected === S43_DUTCH_HORSE && game.target === S43_VILLARS_LEFT)
- game.hits *= 2
- }
-
if (game.scenario === S44_HOHENFRIEDBERG) {
if (game.target === S44_CHARLES) {
if (game.selected === S44_LEOPOLDS_L || game.selected === S44_LEOPOLDS_C || game.selected === S44_LEOPOLDS_R)
@@ -2751,12 +2843,6 @@ function update_attack1(direct) {
}
}
- if (game.scenario === S49_LEUTHEN) {
- if (direct)
- if (game.selected === S49_LUCCHESI && game.target === S49_RETZOW)
- game.hits *= 2
- }
-
// Oblique Attack (CAL expansion rule)
if (is_infantry(game.selected)) {
if (get_sticks(game.selected) >= get_sticks(game.target) + 3)
@@ -2775,7 +2861,7 @@ function update_attack1(direct) {
game.hits = Math.max(0, game.hits - 1)
if (card_has_rule(game.target, "suffer_1_less_1_max"))
- game.hits = clamp(game.hits - 1, 0, 1)
+ game.hits = Math.max(0, Math.min(1, game.hits - 1))
}
// Update hits and self hits for defensive abilities that redirect or steal hits.
@@ -3148,6 +3234,7 @@ function can_take_reaction(c, a, wild) {
if (game.scenario === S31_NEWBURY_1ST) {
if (c === S31_GERARD) {
+ // London Trained Bands enter play when Skippon routs
if (is_removed_from_play(S31_SKIPPON))
return false
}
@@ -3268,7 +3355,7 @@ function goto_screen(c, a) {
game.hits = 0
game.self = 0
break
- case "If either Chariot formation is screened, it suffers one Hit!":
+ case "If either Chariot formation is screened, it suffers one hit!":
game.hits = 0
if (card_has_rule(game.selected, "is_chariot"))
game.self = 1
@@ -3324,18 +3411,15 @@ function goto_absorb(c, a) {
{
default:
throw new Error("invalid absorb effect: " + a.effect)
- case "When target suffers Hits, this card suffers them instead.":
- case "When target suffers Hits, this unit suffers them instead.":
+ case "Suffers hits.":
break
- case "When target suffers Hits, this card suffers 1 hit ONLY instead.":
- case "When target suffers Hits, this unit suffers 1 hit ONLY instead.":
+ case "Suffers 1 hit only.":
game.hits = 1
break
- case "When target suffers Hits, this card suffers one less Hit instead.":
+ case "Suffers 1 less hit.":
game.hits = Math.max(0, game.hits - 1)
break
- case "When target suffers Hits, this card suffers 1 less hit per die.":
- case "When target suffers Hits, this unit suffers 1 less hit per die.":
+ case "Suffers 1 less hit per die.":
game.hits = Math.max(0, game.hits - count_dice_on_card(c))
break
}
@@ -3347,7 +3431,7 @@ function goto_absorb(c, a) {
states.s29_meade = {
prompt() {
- view.prompt = "Choosy any friendly Formation except Little Round Top to absorb the hits instead."
+ view.prompt = "Choose any friendly Formation except Little Round Top to absorb the hits instead."
let p = player_index()
for (let c of game.front[p]) {
if (c !== S29_MEADE && c !== S29_LITTLE_ROUND_TOP && c !== game.target)
@@ -3392,31 +3476,34 @@ function goto_counterattack(c, a) {
{
default:
throw new Error("invalid counterattack effect: " + a.effect)
- case "1 hit per die.":
- game.self += count_dice_on_card(c)
- break
case "1 hit.":
game.self += 1
break
- case "1 hit. Additionally, this unit only suffers one hit.":
+ case "1 hit per die.":
+ game.self += count_dice_on_card(c)
+ break
+ case "1 hit. Suffers 1 hit only.":
game.self += 1
game.hits = 1
break
- case "1 hit. Additionally, this unit suffers one less hit per die.":
+ case "Suffers 1 less hit.":
+ game.hits = Math.max(0, game.hits - 1)
+ break
+ case "1 hit. Suffers 1 less hit.":
game.self += 1
- game.hits = Math.max(0, game.hits - count_dice_on_card(c))
+ game.hits = Math.max(0, game.hits - 1)
break
- case "1 hit. Additionally, this unit suffers one less hit.":
+ case "1 hit. Suffers 1 less hit per die.":
game.self += 1
- game.hits -= 1
+ game.hits = Math.max(0, game.hits - count_dice_on_card(c))
break
- case "This unit suffers ONE less hit and never more than one.":
+ case "1 hit. Suffers 1 less hit and never more than 1.":
game.self += 1
- game.hits = clamp(game.hits - 1, 0, 1)
+ game.hits = Math.max(0, Math.min(1, game.hits - 1))
break
- case "This unit suffers TWO less hits and never more than one.":
+ case "1 hit. Suffers 2 less hits and never more than 1.":
game.self += 1
- game.hits = clamp(game.hits - 2, 0, 1)
+ game.hits = Math.max(0, Math.min(1, game.hits - 2))
break
}
@@ -3450,106 +3537,6 @@ states.counterattack = {
},
}
-// === ATTACK EFFECTS ===
-
-function get_attack_hits(c, a) {
- switch (a.effect) {
- default:
- throw new Error("invalid attack effect: " + a.effect)
- case "1 hit.":
- case "1 hit. Warwick Retires upon completing this Attack Action.":
- case "1 hit. You CHOOSE the target.":
- case "1 hit. 1 self per action.":
- case "1 hit per action. 1 self per action.":
- return 1
- case "1 hit per die.":
- case "1 hit per die. 1 self per action.":
- case "1 hit per die. Ignore first target until it comes out of Reserve.":
- case "1 hit per die (but see below). 1 self per action.":
- case "1 hit per die (plus dice from E. Phalanx).":
- case "1 hit per die. 1 self per action. (But see Sharpshooters.)":
- case "1 hit per die. 1 self per action. (But see 4th Alabama.)":
- case "1 hit per die. 1 self per action. (But see Semmes.)":
- case "1 hit per die (also take dice from 141st Pennsylvania). 1 self per action.":
- case "1 hit per die (also take dice from 68th Pennsylvania). 1 self per action.":
- case "1 hit per die. 1 self per action. (But see William Fielding.)":
- case "1 hit per die (1 extra vs Essex). 1 self per action. (See W. Fielding.)":
- case "1 hit per die. 1 self per action (but see Cannons).":
- case "1 hit per die. 1 self per action (but see Bayonets!).":
- case "1 hit per die (2 hits per die vs. Blenheim). 1 self per action.":
- case "1 hit per die (two per die vs. Villars's Left). 1 self per action.":
- case "1 hit per die versus Driesen. Two hits per die versus Retzow.":
- case "1 hit per die. 1 self per action. Does 1 extra hit versus Dutch Left Foot.":
- case "1 hit per die. 1 self per action. Does 1 extra hit if Dutch Horse Routed.":
- case "1 hit per die. 1 self per action. If reduced to one stick, no self hits.":
- case "1 hit per die. 1 self per action. You CHOOSE the target.":
- return count_dice_on_card(c)
- case "1 hit per pair.":
- case "1 hit per pair. 1 self per action.":
- return count_dice_on_card(c) >> 1
- case "1 hit plus 1 hit per die.":
- case "1 hit, PLUS 1 hit per die. 1 self per action.":
- case "1 hit, PLUS 1 hit per die. 1 self per action. Fightin' Irish!":
- return 1 + count_dice_on_card(c)
- case "2 hits, PLUS 1 hit per die. 1 self per action.":
- return 2 + count_dice_on_card(c)
- case "Two hits per die.":
- return 2 * count_dice_on_card(c)
- case "2 hits.":
- return 2
- case "5 hits.":
- return 5
- case "Oxford immediately Routs. This cannot be Screened.":
- return 5
- }
-}
-
-function get_attack_self(c, a) {
- switch (a.effect) {
- default:
- throw new Error("invalid attack effect: " + a.effect)
- case "1 hit.":
- case "1 hit. Warwick Retires upon completing this Attack Action.":
- case "1 hit. You CHOOSE the target.":
- case "1 hit per die.":
- case "1 hit per die. Ignore first target until it comes out of Reserve.":
- case "1 hit per die (plus dice from E. Phalanx).":
- case "1 hit per pair.":
- case "1 hit plus 1 hit per die.":
- case "Two hits per die.":
- case "2 hits.":
- case "5 hits.":
- case "Oxford immediately Routs. This cannot be Screened.":
- return 0
- case "1 hit. 1 self per action.":
- case "1 hit per action. 1 self per action.":
- case "1 hit per die. 1 self per action.":
- case "1 hit per die (but see below). 1 self per action.":
- case "1 hit per die. 1 self per action. (But see Sharpshooters.)":
- case "1 hit per die. 1 self per action. (But see 4th Alabama.)":
- case "1 hit per die. 1 self per action. (But see Semmes.)":
- case "1 hit per die (also take dice from 141st Pennsylvania). 1 self per action.":
- case "1 hit per die (also take dice from 68th Pennsylvania). 1 self per action.":
- case "1 hit per die. 1 self per action. (But see William Fielding.)":
- case "1 hit per die (1 extra vs Essex). 1 self per action. (See W. Fielding.)":
- case "1 hit per die. 1 self per action (but see Cannons).":
- case "1 hit per die. 1 self per action (but see Bayonets!).":
- case "1 hit per die (2 hits per die vs. Blenheim). 1 self per action.":
- case "1 hit per die (two per die vs. Villars's Left). 1 self per action.":
- case "1 hit per die versus Driesen. Two hits per die versus Retzow.":
- case "1 hit per die. 1 self per action. Does 1 extra hit versus Dutch Left Foot.":
- case "1 hit per die. 1 self per action. Does 1 extra hit if Dutch Horse Routed.":
- case "1 hit per die. 1 self per action. You CHOOSE the target.":
- case "1 hit per pair. 1 self per action.":
- case "1 hit, PLUS 1 hit per die. 1 self per action.":
- case "1 hit, PLUS 1 hit per die. 1 self per action. Fightin' Irish!":
- case "2 hits, PLUS 1 hit per die. 1 self per action.":
- return 1
- case "1 hit per die. 1 self per action. If reduced to one stick, no self hits.":
- return (get_sticks(c) > 1) ? 1 : 0
- }
-}
-
// === ROUTING/PURSUIT/REMOVE/FORCE-RETIRE ===
function find_card_owner(c) {