From 5605104117cb0eb8b8630e18a6c89b743fd9dca4 Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Fri, 8 Dec 2023 16:07:42 +0100 Subject: Add notes about missing special rules. --- rules.js | 120 +++++++++++++++++++++++++++++++++++++++++-------------- tools/gendata.js | 4 ++ 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 ")) -- cgit v1.2.3