summaryrefslogtreecommitdiff
path: root/rules.js
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2023-12-15 02:58:17 +0100
committerTor Andersson <tor@ccxvii.net>2024-01-08 16:36:48 +0100
commitb83115ce40d88c789fbbd5a891057e14d4cf553b (patch)
treefbccd0059aa41bc05ff7e97b2a5aaa3c2980aca1 /rules.js
parent4640a9affe7633628ce95de0bb5640e30dd71039 (diff)
downloadtable-battles-b83115ce40d88c789fbbd5a891057e14d4cf553b.tar.gz
stick shift special
Diffstat (limited to 'rules.js')
-rw-r--r--rules.js192
1 files changed, 182 insertions, 10 deletions
diff --git a/rules.js b/rules.js
index 121049f..c1f9f36 100644
--- a/rules.js
+++ b/rules.js
@@ -138,6 +138,9 @@ exports.view = function (state, player) {
view.hits2 = game.hits2
}
+ if (game.shift)
+ view.shift = game.shift
+
if (game.state === "game_over") {
view.prompt = game.victory
} else if (player !== game.active) {
@@ -316,19 +319,19 @@ const S43_VILLARS_LEFT = find_card(43, "Villars's Left")
const S43_BROGLIE = find_card(43, "Broglie")
const S43_PRINCE_DE_TINGRY = find_card(43, "Prince de Tingry")
-const S44_HOTHENFRIEDBERG = find_scenario(44)
+const S44_HOHENFRIEDBERG = find_scenario(44)
const S44_CHARLES = find_card(44, "Charles")
const S44_FREDERICK_II = find_card(44, "Frederick II")
const S44_BAYREUTH_DRAGOONS = find_card(44, "Bayreuth Dragoons")
const S44_LEOPOLDS_L = find_card(44, "Leopold's Left")
const S44_LEOPOLDS_C = find_card(44, "Leopold's Center")
const S44_LEOPOLDS_R = find_card(44, "Leopold's Right")
+const S44_DU_MOULIN = find_card(44, "Du Moulin")
+const S44_SAXON_HORSE = find_card(44, "Saxon Horse")
// === SETUP ===
exports.setup = function (seed, scenario, options) {
- // TODO: "Random"
-
scenario = parseInt(scenario)
scenario = data.scenarios.findIndex(s => s.number === scenario)
if (scenario < 0)
@@ -381,6 +384,10 @@ exports.setup = function (seed, scenario, options) {
hits2: 0,
}
+ // Charles Alexander of Lorraine -- shift special
+ if (info.number >= 44 && info.number <= 49)
+ game.shift = []
+
function setup_formation(front, reserve, c) {
let card = data.cards[c]
if (card.reserve)
@@ -509,6 +516,21 @@ function set_sticks(c, n) {
map_set(game.sticks, c, n)
}
+function get_shift_sticks(c) {
+ if (game.shift)
+ return map_get(game.shift, c, 0)
+ return 0
+}
+
+function set_shift_sticks(c, n) {
+ if (game.shift) {
+ if (n)
+ map_set(game.shift, c, n)
+ else
+ map_delete(game.shift, c)
+ }
+}
+
function remove_sticks(c, n) {
let p = find_card_owner(c)
let old = get_sticks(c)
@@ -570,22 +592,26 @@ function eliminate_card(c) {
function rout_card(c) {
let p = find_card_owner(c)
game.lost[p] += get_sticks(c)
+ game.lost[p] += get_shift_sticks(c)
log(c + " routed.")
eliminate_card(c)
}
function pursue_card(c) {
log(c + " pursued.")
+ game.lost[p] += get_shift_sticks(c) // TODO ?
eliminate_card(c)
}
function retire_card(c) {
log(c + " retired.")
+ game.lost[p] += get_shift_sticks(c) // TODO ?
eliminate_card(c)
}
function remove_card(c) {
log(c + " removed.")
+ game.lost[p] += get_shift_sticks(c) // TODO ?
eliminate_card(c)
}
@@ -1654,6 +1680,12 @@ function is_reaction(c, a) {
}
function is_mandatory_reaction(c, a) {
+
+ if (game.scenario === S44_HOHENFRIEDBERG) {
+ if (c === S44_DU_MOULIN && game.selected === S44_SAXON_HORSE)
+ return false
+ }
+
return (
a.requirement !== "Voluntary" &&
a.requirement !== "Pair, Voluntary"
@@ -1746,6 +1778,11 @@ function can_take_any_action() {
}
}
+ if (can_shift_any_infantry())
+ return true
+ if (can_shift_any_cavalry())
+ return true
+
return false
}
@@ -1767,12 +1804,25 @@ function count_cards_remaining_from_wing(w) {
}
function goto_start_turn() {
+ let p = player_index()
+
if (check_impossible_to_attack_victory())
return
+ // TODO: manual step to shift?
+ if (game.shift) {
+ for (let c of game.front[p]) {
+ let n = get_shift_sticks(c)
+ if (n > 0) {
+ set_sticks(c, get_sticks(c) + n)
+ set_shift_sticks(c, 0)
+ }
+ }
+ }
+
if (game.scenario === S25_WHEATFIELD) {
// Rout Stony Hill at start of Union turn if it is the only Blue card left.
- if (player_index() === 1) {
+ if (p === 1) {
if (is_card_in_play(S25_STONY_HILL)) {
if (count_cards_remaining_from_wing(BLUE) === 1) {
game.state = "s25_stony_hill"
@@ -1782,8 +1832,8 @@ function goto_start_turn() {
}
}
- if (game.scenario === S44_HOTHENFRIEDBERG) {
- if (player_index() === 1) {
+ if (game.scenario === S44_HOHENFRIEDBERG) {
+ if (p === 1) {
let have_inf_or_cav = false
for (let c of game.front[1])
if (is_infantry(c) || is_cavalry(c))
@@ -1865,6 +1915,9 @@ states.action = {
}
}
+ if (can_shift_any_infantry() || can_shift_any_cavalry())
+ view.actions.shift = 1
+
if (game.scenario === S40_CHIARI) {
if (player_index() === 1) {
if (s40_can_take_cassines_action(S40_CASSINES_I, S40_NIGRELLI, S40_KRIECHBAUM))
@@ -1915,6 +1968,10 @@ states.action = {
goto_roll_phase()
roll_dice_in_pool()
},
+ shift() {
+ push_undo()
+ game.state = "shift_from"
+ },
card(c) {
push_undo()
@@ -1941,6 +1998,87 @@ states.action = {
}
}
+function can_shift_any_infantry() {
+ if (game.shift) {
+ let n = 0, m = 0
+ for (let c of game.front[player_index()]) {
+ if (is_infantry(c)) {
+ if (get_sticks(c) > 1)
+ ++m
+ ++n
+ }
+ }
+ return n > 1 && m > 0
+ }
+ return false
+}
+
+function can_shift_any_cavalry() {
+ if (game.shift) {
+ let n = 0, m = 0
+ for (let c of game.front[player_index()]) {
+ if (is_cavalry(c)) {
+ if (get_sticks(c) > 1)
+ ++m
+ ++n
+ }
+ }
+ return n > 1 && m > 0
+ }
+ return false
+}
+
+states.shift_from = {
+ prompt() {
+ view.prompt = "Shift sticks from one Formation to another."
+ let p = player_index()
+ if (can_shift_any_infantry())
+ for (let c of game.front[p])
+ if (is_infantry(c) && get_sticks(c) > 1)
+ gen_action_card(c)
+ if (can_shift_any_cavalry())
+ for (let c of game.front[p])
+ if (is_cavalry(c) && get_sticks(c) > 1)
+ gen_action_card(c)
+ },
+ card(c) {
+ game.selected = c
+ game.target2 = -1
+ game.state = "shift_to"
+ },
+}
+
+states.shift_to = {
+ prompt() {
+ view.prompt = "Shift sticks from " + card_name(game.selected) + "."
+ let p = player_index()
+ if (game.target2 < 0) {
+ if (is_infantry(game.selected))
+ for (let c of game.front[p])
+ if (c !== game.selected && is_infantry(c))
+ gen_action_card(c)
+ if (is_cavalry(game.selected))
+ for (let c of game.front[p])
+ if (c !== game.selected && is_cavalry(c))
+ gen_action_card(c)
+ } else {
+ gen_action_card(game.target2)
+ view.actions.next = 1
+ }
+ },
+ card(c) {
+ game.target2 = c
+ set_sticks(game.selected, get_sticks(game.selected) - 1)
+ set_shift_sticks(game.target2, get_shift_sticks(game.target2) + 1)
+ if (get_sticks(game.selected) === 1)
+ this.next()
+ },
+ next() {
+ // TODO: skip action phase?
+ end_action_phase()
+ },
+}
+
states.s40_cassines = {
prompt() {
view.prompt = "Cassines: Move one unit stick to this card."
@@ -2066,7 +2204,7 @@ function find_first_target_of_command(c, a) {
return S37_THE_FOG
}
- if (game.scenario === S44_HOTHENFRIEDBERG) {
+ if (game.scenario === S44_HOHENFRIEDBERG) {
if (c === S44_CHARLES) {
if (game.reserve[1].length > 0)
return game.reserve[1]
@@ -2092,7 +2230,7 @@ function find_first_target_of_command(c, a) {
function find_all_targets_of_command(c, a) {
- if (game.scenario === S44_HOTHENFRIEDBERG) {
+ if (game.scenario === S44_HOHENFRIEDBERG) {
if (c === S44_CHARLES) {
return game.reserve[1].slice()
}
@@ -2268,7 +2406,7 @@ function update_attack1(direct) {
game.hits *= 2
}
- if (game.scenario === S44_HOTHENFRIEDBERG) {
+ 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)
game.self = 0
@@ -2406,6 +2544,13 @@ function resume_attack() {
if (game.target2 >= 0)
remove_sticks(game.target2, game.hits2)
+ if (game.scenario === S44_HOHENFRIEDBERG) {
+ // remove after first attack
+ if (game.selected === S44_BAYREUTH_DRAGOONS) {
+ remove_card(S44_BAYREUTH_DRAGOONS)
+ }
+ }
+
end_action_phase()
}
@@ -2465,7 +2610,7 @@ states.command = {
}
}
- if (game.scenario === S44_HOTHENFRIEDBERG) {
+ if (game.scenario === S44_HOHENFRIEDBERG) {
// one at a time
if (game.selected === S44_CHARLES || game.selected === S44_FREDERICK_II) {
pay_for_action(game.selected)
@@ -2872,6 +3017,8 @@ function get_attack_hits(c, a) {
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.":
@@ -2890,6 +3037,7 @@ function get_attack_self(c, a) {
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 "Two hits per die.":
case "2 hits.":
case "5 hits.":
return 0
@@ -3296,6 +3444,13 @@ function array_insert(array, index, item) {
array[index] = item
}
+function array_remove_pair(array, index) {
+ let n = array.length
+ for (let i = index + 2; i < n; ++i)
+ array[i - 2] = array[i]
+ array.length = n - 2
+}
+
function array_insert_pair(array, index, key, value) {
for (let i = array.length; i > index; i -= 2) {
array[i] = array[i-2]
@@ -3411,3 +3566,20 @@ function map_set(map, key, value) {
}
array_insert_pair(map, a<<1, key, value)
}
+
+function map_delete(map, item) {
+ let a = 0
+ let b = (map.length >> 1) - 1
+ while (a <= b) {
+ let m = (a + b) >> 1
+ let x = map[m<<1]
+ if (item < x)
+ b = m - 1
+ else if (item > x)
+ a = m + 1
+ else {
+ array_remove_pair(map, m<<1)
+ return
+ }
+ }
+}