diff options
-rw-r--r-- | play.html | 2 | ||||
-rw-r--r-- | play.js | 1 | ||||
-rw-r--r-- | rules.js | 210 |
3 files changed, 165 insertions, 48 deletions
@@ -243,9 +243,9 @@ main[data-scenario="5"] { } .die.action { box-shadow: 0 0 0 1px #333, 0 0 0px 3px white; } +.card.action { box-shadow: 0 0 0 3px whitesmoke; } .card.selected { box-shadow: 0 0 0px 3px gold; } .card.target { box-shadow: 0 0 0px 3px black; } -.card.action { box-shadow: 0 0 0 3px whitesmoke; } .action_type.action { border-color: white; @@ -45,6 +45,7 @@ function remember_position(e) { } function animate_position(e) { + // TODO: fix double-animated dice when cards are removed if (e.parentElement) { if (e.my_parent) { let rect = e.getBoundingClientRect() @@ -154,6 +154,8 @@ exports.setup = function (seed, scenario, options) { active: P1, state: "roll", + reacted: 0, + // dice value and position dice: [ 0, POOL, 0, POOL, 0, POOL, 0, POOL, 0, POOL, 0, POOL, @@ -174,8 +176,10 @@ exports.setup = function (seed, scenario, options) { placed: [], // current action + routed: [ 0, 0 ], selected: -1, target: -1, + screen: -1, hits: 0, self: 0, } @@ -208,6 +212,20 @@ exports.setup = function (seed, scenario, options) { // === GAME STATE ACCESSORS === +function for_each_front_card(fn) { + for (let c of game.front[0]) + fn(c) + for (let c of game.front[1]) + fn(c) +} + +function for_each_reserve_card(fn) { + for (let c of game.reserve[0]) + fn(c) + for (let c of game.reserve[1]) + fn(c) +} + function card_number(c) { return data.cards[c].number } @@ -1010,10 +1028,7 @@ function goto_action_phase() { } function end_action_phase() { - check_routing() - check_pursuit() - check_reserve() - goto_roll_phase() + goto_routing() } states.action = { @@ -1165,8 +1180,8 @@ states.attack = { gen_action_card(game.target) view.actions.attack = 1 }, - card(c) { - log(card_name(game.selected) + " attacked " + card_name(c) + ".") + attack() { + log(card_name(game.selected) + " attacked " + card_name(game.target) + ".") if (can_opponent_react()) { clear_undo() set_opponent_active() @@ -1175,6 +1190,9 @@ states.attack = { resume_attack() } }, + card(_) { + this.attack() + }, } function resume_attack() { @@ -1185,6 +1203,7 @@ function resume_attack() { game.hits = game.self = 0 game.selected = -1 game.target = -1 + game.screen = -1 end_action_phase() } @@ -1306,9 +1325,11 @@ states.react = { view.actions.pass = 1 }, a1(c) { + push_undo() goto_take_reaction(c, 0) }, a2(c) { + push_undo() goto_take_reaction(c, 1) }, pass() { @@ -1332,31 +1353,49 @@ function goto_take_reaction(c, ix) { } } +function end_reaction() { + set_opponent_active() + resume_attack() +} + +// === SCREEN === + function goto_screen(c, a) { - log(card_name(c) + " screened.") game.reacted = 1 - pay_for_action(c) + + game.screen = c switch (a.effect) { default: throw new Error("invalid screen effect: " + a.effect) - case "": + case undefined: game.hits = 0 game.self = 0 break } - set_opponent_active() - resume_attack() + game.state = "screen" } +states.screen = { + prompt() { + view.prompt = "Screen attack from " + card_name(game.selected) + "." + view.actions.screen = 1 + }, + screen() { + log(card_name(game.screen) + " screened.") + pay_for_action(game.screen) + end_reaction() + }, +} + +// === ABSORB === + function goto_absorb(c, a) { - log(card_name(c) + " absorbed.") + game.reacted = 1 game.target = c - game.reacted = 1 - pay_for_action(c) switch (a.effect) { @@ -1372,14 +1411,25 @@ function goto_absorb(c, a) { break } - set_opponent_active() - resume_attack() + game.state = "absorb" } +states.absorb = { + prompt() { + view.prompt = "Absorb attack from " + card_name(game.selected) + "." + view.actions.absorb = 1 + }, + absorb() { + log(card_name(game.target) + " absorbed.") + pay_for_action(game.target) + end_reaction() + }, +} + +// === COUNTERATTACK === + function goto_counterattack(c, a) { - pay_for_action(c) game.reacted = 1 - log(card_name(c) + " counterattacked.") switch (a.effect) { @@ -1413,8 +1463,19 @@ function goto_counterattack(c, a) { break } - set_opponent_active() - resume_attack() + game.state = "counterattack" +} + +states.counterattack = { + prompt() { + view.prompt = "Counterattack " + card_name(game.selected) + "." + view.actions.counterattack = 1 + }, + counterattack() { + log(card_name(game.target) + " counterattacked.") + pay_for_action(game.target) + end_reaction() + }, } // === ATTACK EFFECTS === @@ -1463,47 +1524,97 @@ function get_attack_self(c, a) { // === ROUTING === -function check_routing() { - // Rout cards with no sticks. - let routed = [ 0, 0 ] - for (let p = 0; p <= 1; ++p) { - for (let i = 0; i < game.front[p].length; ++i) { - let c = game.front[p][i] - if (!data.cards[c].special) { - if (map_get(game.sticks, c, 0) === 0) { - log(card_name(c) + " routed.") - if (data.cards[c].star) - routed[p] = 2 - else - routed[p] = 1 - eliminate_card(c) - --i; - } - } - } - } +function should_remove_card(c) { + // TODO: remove after X routs special rules + return false +} + +function should_rout_card(c) { + // TODO: rout after X routs special rules + if (!data.cards[c].special) + if (map_get(game.sticks, c, 0) === 0) + return true + return false +} +function goto_routing() { + game.routed = [ 0, 0 ] + resume_routing() +} + +function resume_routing() { + game.state = "routing" + for (let p = 0; p <= 1; ++p) + for (let c of game.front[p]) + if (should_rout_card(c) || should_remove_card(c)) + return + end_routing() +} + +function end_routing() { // Morale loss - if ((routed[0] > 0 && !routed[1]) || (routed[1] > 0 && !routed[0])) { - if (routed[0]) { - routed[0] = Math.min(routed[0], game.morale[0]) - game.morale[0] -= routed[0] - game.morale[1] += routed[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] } else { - routed[1] = Math.min(routed[1], game.morale[1]) - game.morale[1] -= routed[1] - game.morale[0] += routed[1] + game.routed[1] = Math.min(game.routed[1], game.morale[1]) + game.morale[1] -= game.routed[1] + game.morale[0] += game.routed[1] } } + 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() +} + +function find_card_owner(c) { + if (set_has(game.front[0], c)) + return 0 + if (set_has(game.front[1], c)) + return 1 + throw new Error("card not found in any front") +} + +states.routing = { + prompt() { + view.prompt = "Rout cards with no remaining sticks!" + for (let p = 0; p <= 1; ++p) + for (let c of game.front[p]) + if (should_rout_card(c) || should_remove_card(c)) + gen_action_card(c) + }, + card(c) { + push_undo() // XXX + if (should_rout_card(c)) { + log(card_name(c) + " routed.") + let p = find_card_owner(c) + if (data.cards[c].star) + game.routed[p] = 2 + else + game.routed[p] = 1 + } else { + log(card_name(c) + " removed.") + } + eliminate_card(c) + resume_routing() + }, } // === PURSUIT === +function goto_pursuit() { + check_pursuit() + goto_reserve() +} + function check_pursuit() { // Remove pursuing cards. for (let p = 0; p <= 1; ++p) { @@ -1523,6 +1634,11 @@ function check_pursuit() { // === RESERVE === +function goto_reserve() { + check_reserve() + goto_roll_phase() +} + function check_reserve() { // Bring on reserves (on both sides). for (let p = 0; p <= 1; ++p) { |