From 73afac930d3f6671ee6d3d1d57cc889e0ed4dc23 Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Sun, 20 Oct 2024 21:26:12 +0200 Subject: Supply and paying TC for hussars. --- rules.js | 720 +++++++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 427 insertions(+), 293 deletions(-) (limited to 'rules.js') diff --git a/rules.js b/rules.js index f4491a5..c318f40 100644 --- a/rules.js +++ b/rules.js @@ -4,9 +4,6 @@ /* TODO -victory check -supply phase - hussar payment re-entering supply trains during movement (10.2) supply payment @@ -116,7 +113,6 @@ const RESERVE = 4 const IMPERIAL_ELECTION = 25 const ELIMINATED = data.cities.name.length -const REMOVED = ELIMINATED + 1 const TRIER = find_city("Trier") const MAINZ = find_city("Mainz") @@ -290,7 +286,9 @@ const all_powers_france_bavaria_prussia_saxony = [ P_FRANCE, P_BAVARIA, P_PRUSSI const all_powers_pragmatic_austria = [ P_PRAGMATIC, P_AUSTRIA ] const all_powers_none = [] +const all_powers_france_bavaria = [ P_FRANCE, P_BAVARIA ] const all_powers_bavaria = [ P_BAVARIA ] +const all_powers_prussia_saxony = [ P_PRUSSIA, P_SAXONY ] const all_powers_saxony = [ P_SAXONY ] const all_powers_bavaria_saxony = [ P_BAVARIA, P_SAXONY ] @@ -315,6 +313,33 @@ function all_friendly_minor_powers(pow) { } } +function all_coop_powers(pow) { + switch (pow) { + case P_FRANCE: + case P_BAVARIA: + return all_powers_france_bavaria + case P_PRUSSIA: + case P_SAXONY: + return all_powers_prussia_saxony + case P_PRAGMATIC: + case P_AUSTRIA: + return all_powers_pragmatic_austria + } +} + +function is_hostile_to_austria() { + switch (game.power) { + case P_FRANCE: + case P_BAVARIA: + case P_PRUSSIA: + case P_SAXONY: + return true + case P_PRAGMATIC: + case P_AUSTRIA: + return false + } +} + function all_enemy_powers(pow) { switch (pow) { case P_FRANCE: @@ -487,11 +512,6 @@ function format_selected() { } } -function log_move_to(to) { - let from = game.pos[game.selected] - log("@" + game.selected + ";" + from + "," + to) -} - function log_move_path() { if (game.move_path.length > 1) log("@" + game.selected + ";" + game.move_path.join(",")) @@ -531,6 +551,18 @@ function is_fortress(s) { return set_has(all_fortresses, s) } +function is_home_country(s) { + // TODO: Silesia + switch (game.power) { + case P_FRANCE: return set_has(data.country.France, s) + case P_BAVARIA: return set_has(data.country.Bavaria, s) + case P_PRUSSIA: return set_has(data.country.Prussia, s) + case P_SAXONY: return set_has(data.country.Saxony, s) + case P_AUSTRIA: return set_has(data.country.Austria, s) + case P_PRAGMATIC: return set_has(data.country.Netherlands, s) + } +} + function is_enemy_home_country(s) { for (let other of all_enemy_powers(game.power)) if (set_has(all_home_country_fortresses[other], s)) @@ -560,13 +592,12 @@ function is_enemy_controlled_fortress(s) { } function is_power_controlled_fortress(pow, s) { - if (map_get(game.elector, s, -1) === pow) - return true - if (map_get(game.victory, s, -1) === pow) - return true - if (set_has(all_home_country_fortresses[pow], s)) - return true - return false + let owner = map_get(game.elector, s, -1) + if (owner < 0) + owner = map_get(game.victory, s, -1) + if (owner < 0) + return set_has(all_home_country_fortresses[pow], s) + return owner === pow } function set_control_of_fortress(s, pow) { @@ -722,6 +753,13 @@ function has_any_piece(to) { return false } +function has_any_hussar(to) { + for (let p of all_hussars) + if (game.pos[p] === to) + return true + return false +} + function has_friendly_supply_train(to) { for (let p of all_allied_trains(game.power)) if (game.pos[p] === to) @@ -816,6 +854,13 @@ const POWER_FROM_ACTION_STEP = [ P_PRAGMATIC, // interleave with austria moves on flanders map ] +const title_from_action_step = [ + "=0 France and Bavaria", + "=2 Prussia and Saxony", + "=5 Austria", + "=4 Pragmatic Army", +] + function set_active_to_current_action_step() { set_active_to_power(POWER_FROM_ACTION_STEP[game.step]) } @@ -845,7 +890,7 @@ function goto_action_stage() { clear_undo() - log("=" + game.power) + log(title_from_action_step[game.step]) // TODO: minor powers controlled at the same time @@ -881,13 +926,14 @@ function end_place_hussars() { } states.place_hussars = { - inactive: "place Hussars", + inactive: "place hussars", prompt() { - prompt("Place the Hussars.") + prompt("Place the hussars.") for (let p of all_hussars) if (!set_has(game.moved, p)) gen_action_piece(p) - view.actions.next = 1 + if (!has_any_hussar(ELIMINATED)) + view.actions.next = 1 }, piece(p) { push_undo() @@ -901,9 +947,9 @@ states.place_hussars = { } states.place_hussars_where = { - inactive: "place Hussars", + inactive: "place hussars", prompt() { - prompt("Place the Hussar in a city.") + prompt("Place the hussar in a city.") view.selected = game.selected // bohemia @@ -935,7 +981,7 @@ function search_hussar_bfs(from) { continue if (!is_bohemia_space(next)) continue - if (!has_any_piece(next)) + if (!has_any_piece(next) && !has_any_hussar(next)) set_add(seen, next) if (dist < 4) queue.push((next << 4) | dist) @@ -1087,7 +1133,345 @@ function end_tactical_cards() { // TODO: draw pragmatic cards before austria moves - // MARIA TODO: supply! + goto_supply() +} + +/* PAYMENT */ + +function sum_card_values(list) { + let n = 0 + for (let c of list) + n += to_value(c) + return n +} + +function find_largest_card(list) { + for (let v = 10; v >= 2; --v) { + for (let c of list) + if (to_value(c) === v) + return c + } + throw "NO CARDS FOUND IN LIST" +} + +function spend_card_value(pool, used, amount) { + if (game.count > 0) { + if (amount < game.count) { + game.count -= amount + amount = 0 + } else { + amount -= game.count + game.count = 0 + } + } + while (amount > 0) { + let c = find_largest_card(pool) + let v = to_value(c) + set_delete(pool, c) + set_add(used, c) + if (v > amount) { + game.count = v - amount + amount = 0 + } else { + amount -= v + } + } +} + +/* SUPPLY */ + +function is_out_of_supply(p) { + return (game.oos & (1 << p)) !== 0 +} + +function set_out_of_supply(p) { + return game.oos |= (1 << p) +} + +function set_in_supply(p) { + return game.oos &= ~(1 << p) +} + +function search_supply_path_avoid_hussars(who) { + let from = game.pos[who] + let trains = all_power_trains[piece_power[who]] + + if (is_home_country(from)) + return 1 + + let seen = [ from ] + let queue = [ from << 4 ] + while (queue.length > 0) { + let item = queue.shift() + let here = item >> 4 + let dist = (item & 15) + 1 + for (let next of data.cities.adjacent[here]) { + for (let p of trains) + if (game.pos[p] === next) + return dist + if (has_any_hussar(next)) + continue + if (set_has(seen, next)) + continue + if (has_enemy_piece(next)) + continue + set_add(seen, next) + if (dist < 6) + queue.push((next << 4) | dist) + } + } + + return 0 +} + +function search_supply_path(who) { + let from = game.pos[who] + let trains = all_power_trains[piece_power[who]] + + if (is_home_country(from)) + return 1 + + let seen = [ from ] + let queue = [ from << 4 ] + while (queue.length > 0) { + let item = queue.shift() + let here = item >> 4 + let dist = (item & 15) + 1 + for (let next of data.cities.adjacent[here]) { + for (let p of trains) + if (game.pos[p] === next) + return dist + if (set_has(seen, next)) + continue + if (has_enemy_piece(next)) + continue + set_add(seen, next) + if (dist < 6) + queue.push((next << 4) | dist) + } + } + + return 0 +} + +function goto_supply() { + game.supply = { + hussars: [], + restore: [], + suffer: [], + } + + for (let p of all_controlled_generals(game.power)) { + if (!is_map_space(game.pos[p])) + continue + + if (is_hostile_to_austria()) { + let d = search_supply_path_avoid_hussars(p) + if (d > 0) { + if (is_out_of_supply(p)) + set_add(game.supply.restore, p) + } else { + d = search_supply_path(p) + if (d > 0) + map_set(game.supply.hussars, p, d) + else + set_add(game.supply.suffer, p) + } + } else { + let d = search_supply_path(p) + if (d > 0) { + if (is_out_of_supply(p)) + set_add(game.supply.restore, p) + } else { + set_add(game.supply.suffer, p) + } + } + } + + if (game.supply.hussars.length + game.supply.restore.length + game.supply.suffer.length === 0) + end_supply() + else + resume_supply() +} + +function resume_supply() { + set_active_to_current_action_step() + if (game.supply.hussars.length > 0) + goto_supply_hussars() + else if (game.supply.restore.length > 0) + goto_supply_restore() + else if (game.supply.suffer.length > 0) + goto_supply_suffer() + else + end_supply() + // TODO: pause + // game.state = "supply_done" +} + +function goto_supply_hussars() { + log_br() + log("Hussars") + set_active_to_power(piece_power[game.supply.hussars[0]]) + game.state = "supply_hussars" + game.supply.pool = [] + game.supply.used = [] + game.count = 0 +} + +function goto_supply_restore() { + log_br() + log("In supply") + game.state = "supply_restore" +} + +function goto_supply_suffer() { + log_br() + log("Out of supply") + game.state = "supply_suffer" +} + +states.supply_hussars = { + inactive: "supply", + prompt() { + let paid = game.count + sum_card_values(game.supply.pool) + + view.selected = [] + + let can_pay = false + let debt = 0 + map_for_each(game.supply.hussars, (p, d) => { + if (piece_power[p] === game.power) { + if (d <= paid) { + can_pay = true + gen_action_piece(p) + } else { + view.selected.push(p) + } + debt += d + } + }) + + let str + if (debt > 0) + str = `Pay ${debt} for tracing supply through hussars` + else + str = "Hussar payment done" + + if (paid > 1) + str += " \u2014 " + paid + " points." + else if (paid === 1) + str += " \u2014 1 point." + else + str += "." + + if (paid < debt) + for (let c of game.hand[game.power]) + gen_action_card(c) + + prompt(str) + + view.draw = game.supply.pool + + if (debt === 0 || (!can_pay && game.hand[game.power].length === 0)) + view.actions.next = 1 + }, + piece(p) { + push_undo() + + let cost = map_get(game.supply.hussars, p) + + spend_card_value(game.supply.pool, game.supply.used, cost) + + map_delete(game.supply.hussars, p) + if (is_out_of_supply(p)) + set_add(game.supply.restore, p) + + log(">P" + p + " paid " + cost) + }, + card(c) { + push_undo() + set_delete(game.hand[game.power], c) + set_add(game.supply.pool, c) + }, + next() { + push_undo() + + if (game.supply.used.length > 0) + log(">with " + game.supply.used.map(format_card).join(", ")) + else + log(">paid nothing") + + // move generals not paid for to out of supply list + map_for_each(game.supply.hussars, (p, _) => { + if (piece_power[p] === game.power) + set_add(game.supply.suffer, p) + }) + for (let p of game.supply.suffer) + map_delete(game.supply.hussars, p) + + // put back into hand unused cards + for (let c of game.supply.pool) + set_add(game.hand[game.power], c) + delete game.supply.pool + delete game.supply.used + + resume_supply() + }, +} + +states.supply_restore = { + inactive: "supply", + prompt() { + prompt("Restore supply to generals with a supply path.") + for (let p of game.supply.restore) + gen_action_piece(p) + }, + piece(p) { + log(`>P${p} at S${game.pos[p]}`) + set_delete(game.supply.restore, p) + set_in_supply(p) + if (game.supply.restore.length === 0) + resume_supply() + }, +} + +states.supply_suffer = { + inactive: "supply", + prompt() { + prompt("Flip and remove troops from generals without a supply path.") + for (let p of game.supply.suffer) + gen_action_piece(p) + }, + piece(p) { + set_delete(game.supply.suffer, p) + if (is_out_of_supply(p)) { + log(`>P${p} at S${game.pos[p]} -2 troops`) + game.troops[p] -= 2 + } else { + log(`>P${p} at S${game.pos[p]} -1 troop`) + set_out_of_supply(p) + game.troops[p] -= 1 + } + if (game.troops[p] <= 0) + eliminate_general(p, true) + if (game.supply.suffer.length === 0) + resume_supply() + }, +} + +states.supply_done = { + inactive: "supply", + prompt() { + prompt("Supply done.") + view.actions.end_supply = 1 + }, + end_supply() { + end_supply() + }, +} + +function end_supply() { + delete game.supply goto_movement() } @@ -1141,6 +1525,8 @@ function movement_range() { } function goto_movement() { + set_active_to_current_action_step() + game.state = "movement" set_clear(game.moved) @@ -1366,6 +1752,16 @@ states.move_supply_train = { if (!set_has(data.cities.main_roads[from], to)) game.main = 0 + // remove hussars + for (let p of all_hussars) { + if (game.pos[p] === to) { + if (!game.move_elim) + game.move_elim = [] + set_add(game.move_elim, p) + game.pos[p] = ELIMINATED + } + } + game.pos[who] = to if (++game.count === 2 + game.main) @@ -1566,8 +1962,12 @@ function end_move_piece() { log_move_path() if (game.move_elim) { - for (let p of game.move_elim) - log("P" + p + " eliminated.") + for (let p of game.move_elim) { + if (is_hussar(p)) + log("P" + p + " removed.") + else + log("P" + p + " eliminated.") + } delete game.move_elim } @@ -1628,45 +2028,8 @@ function troop_cost() { return 6 } -function sum_card_values(list) { - let n = 0 - for (let c of list) - n += to_value(c) - return n -} - -function find_largest_card(list) { - for (let v = 10; v >= 2; --v) { - for (let c of list) - if (to_value(c) === v) - return c - } - throw "NO CARDS FOUND IN LIST" -} - function spend_recruit_cost() { - let spend = troop_cost() - if (game.count > 0) { - if (spend < game.count) { - game.count -= spend - spend = 0 - } else { - spend -= game.count - game.count = 0 - } - } - while (spend > 0) { - let c = find_largest_card(game.recruit.pool) - let v = to_value(c) - set_delete(game.recruit.pool, c) - set_add(game.recruit.used, c) - if (v > spend) { - game.count = v - spend - spend = 0 - } else { - spend -= v - } - } + spend_card_value(game.recruit.pool, game.recruit.used, troop_cost()) } function has_available_depot() { @@ -2499,235 +2862,6 @@ function goto_retroactive_conquest() { end_action_stage() } -/* SUPPLY */ - -function search_supply_bfs(from, range) { - let seen = [ from ] - let queue = [ from << 4 ] - while (queue.length > 0) { - let item = queue.shift() - let here = item >> 4 - let dist = (item & 15) + 1 - for (let next of data.cities.adjacent[here]) { - if (set_has(seen, next)) - continue - if (has_enemy_piece(next)) - continue - set_add(seen, next) - if (dist < range) - queue.push((next << 4) | dist) - } - } - return seen -} - -function search_supply(range) { - for (let p of all_power_trains[game.power]) { - let here = game.pos[p] - if (here >= ELIMINATED) - continue - if (!game.supply) - game.supply = search_supply_bfs(here, range) - else - set_add_all(game.supply, search_supply_bfs(here, range)) - } -} - -function is_out_of_supply(p) { - return (game.oos & (1 << p)) !== 0 -} - -function set_out_of_supply(p) { - return game.oos |= (1 << p) -} - -function set_in_supply(p) { - return game.oos &= ~(1 << p) -} - -function has_supply_line(p) { - let s = game.pos[p] - if (set_has(all_home_or_depot_cities[game.power], s)) - return true - if (game.supply && set_has(game.supply, s)) - return true - return false -} - -function should_supply_restore() { - for (let p of all_power_generals[game.power]) { - if (game.pos[p] >= ELIMINATED) - continue - if (is_out_of_supply(p) && has_supply_line(p)) - return true - } - return false -} - -function should_supply_eliminate() { - for (let p of all_power_generals[game.power]) { - if (game.pos[p] >= ELIMINATED) - continue - if (is_out_of_supply(p) && !has_supply_line(p)) - return true - } - return false -} - -function should_supply_flip() { - for (let p of all_power_generals[game.power]) { - if (game.pos[p] >= ELIMINATED) - continue - if (!is_out_of_supply(p) && !has_supply_line(p)) - return true - } - return false -} - -function goto_supply() { - set_clear(game.moved) - search_supply(6) - - if (should_supply_restore()) - goto_supply_restore() - else if (should_supply_eliminate()) - goto_supply_eliminate() - else if (should_supply_flip()) - goto_supply_flip() - else - end_supply() -} - -function goto_supply_restore() { - log_br() - log("In supply") - resume_supply_restore() -} - -function goto_supply_eliminate() { - log_br() - log("Out of supply") - resume_supply_eliminate() -} - -function goto_supply_flip() { - log_br() - log("Out of supply") - resume_supply_flip() -} - -function resume_supply_restore() { - if (should_supply_restore()) - game.state = "supply_restore" - else if (should_supply_eliminate()) - goto_supply_eliminate() - else if (should_supply_flip()) - goto_supply_flip() - else - game.state = "supply_done" -} - -function resume_supply_eliminate() { - if (should_supply_eliminate()) - game.state = "supply_eliminate" - else if (should_supply_flip()) - goto_supply_flip() - else - game.state = "supply_done" -} - -function resume_supply_flip() { - if (should_supply_flip()) - game.state = "supply_flip" - else - game.state = "supply_done" -} - -states.supply_restore = { - inactive: "supply", - prompt() { - prompt("Restore supply to generals with a supply line.") - for (let p of all_power_generals[game.power]) { - if (game.pos[p] >= ELIMINATED) - continue - if (is_out_of_supply(p) && has_supply_line(p)) - gen_action_piece(game.pos[p]) - } - }, - piece(x) { - let s = game.pos[x] - for (let p of all_power_generals[game.power]) { - if (game.pos[p] === s) { - set_add(game.moved, p) - set_in_supply(p) - log(">P" + p + " at S" + s) - } - } - resume_supply_restore() - }, -} - -states.supply_eliminate = { - inactive: "supply", - prompt() { - prompt("Eliminate out of supply generals with no supply line.") - for (let p of all_power_generals[game.power]) { - if (game.pos[p] >= ELIMINATED) - continue - if (is_out_of_supply(p) && !has_supply_line(p)) - gen_action_piece(game.pos[p]) - } - }, - piece(x) { - let s = game.pos[x] - for (let p of all_power_generals[game.power]) - if (game.pos[p] === s) - eliminate_general(p, true) - - resume_supply_eliminate() - }, -} - -states.supply_flip = { - inactive: "supply", - prompt() { - prompt("Flip generals with no supply line.") - for (let p of all_power_generals[game.power]) { - if (game.pos[p] >= ELIMINATED) - continue - if (!is_out_of_supply(p) && !has_supply_line(p)) - gen_action_piece(game.pos[p]) - } - }, - piece(x) { - let s = game.pos[x] - for (let p of all_power_generals[game.power]) { - if (game.pos[p] === s) { - log(">P" + p + " at S" + s) - set_out_of_supply(p) - } - } - resume_supply_flip() - }, -} - -states.supply_done = { - inactive: "supply", - prompt() { - prompt("Supply done.") - view.actions.end_supply = 1 - }, - end_supply() { - end_supply() - }, -} - -function end_supply() { - delete game.supply - - end_action_stage() -} - /* SETUP */ const POWER_FROM_SETUP_STEP = [ -- cgit v1.2.3