diff options
-rw-r--r-- | play.js | 11 | ||||
-rw-r--r-- | rules.js | 399 |
2 files changed, 201 insertions, 209 deletions
@@ -84,6 +84,15 @@ const piece_power = [ P_AUSTRIA, P_AUSTRIA ] +const piece_rank = [ + 1, 2, 3, 4, 5, + 1, + 1, 2, 3, 4, + 1, + 1, 2, 3, + 1, 2, 3, 4, 5, 6, +] + const piece_abbr = [ "F1", "F2", "F3", "F4", "F5", "B1", @@ -688,7 +697,7 @@ function layout_general_offset(who, here) { return 1 if ((view.supreme & (1 << other)) || view.selected === other) return 0 - if (who < other) + if (piece_rank[who] < piece_rank[other]) return 1 return 0 } @@ -4,7 +4,11 @@ /* TODO -winter recruitment +renumber + major powers 0-4 + minor powers 5-6 + +winter scoring tc draw no TCs for minor power if major fortress enemy controlled @@ -141,13 +145,8 @@ const DRESDEN = find_city("Dresden") const ENGLAND = find_city("England") -const max_power_troops = [ 5*8, 1*8, 4*8, 1*8, 3*8, 6*8 ] - const all_powers = [ 0, 1, 2, 3, 4, 5 ] -const all_power_depots = [ -] - const all_power_generals = [ [ 0, 1, 2, 3, 4 ], [ 5 ], @@ -228,6 +227,7 @@ const piece_name = [ const all_pieces = [ ...all_power_generals.flat(), ...all_power_trains.flat() ] const all_trains = [ ...all_power_trains.flat() ] const all_generals = [ ...all_power_generals.flat() ] +const all_generals_by_rank = all_generals.slice().sort((a,b)=>piece_rank[a]-piece_rank[b]) const all_france_bavaria_generals = [ ...all_power_generals[P_FRANCE], @@ -724,8 +724,8 @@ function get_supreme_commander(s) { for (let p of all_generals) if ((game.supreme & (1<<p)) && game.pos[p] === s) return p - for (let p of all_generals) - if (!(game.supreme & (1<<p)) && game.pos[p] === s) + for (let p of all_generals_by_rank) + if (game.pos[p] === s) return p return -1 } @@ -755,22 +755,6 @@ function get_space_suit(s) { throw "IMPOSSIBLE" } -function count_eliminated_trains() { - let n = 0 - for (let p of all_power_trains[game.power]) - if (game.pos[p] === ELIMINATED) - ++n - return n -} - -function count_eliminated_generals() { - let n = 0 - for (let p of all_power_generals[game.power]) - if (game.pos[p] === ELIMINATED) - ++n - return n -} - function count_used_troops() { let current = 0 for (let p of all_power_generals[game.power]) @@ -778,14 +762,6 @@ function count_used_troops() { return current } -function count_unused_troops_on_map() { - let n = 0 - for (let p of all_power_generals[game.power]) - if (is_piece_on_map(p)) - n += 8 - game.troops[p] - return n -} - function has_any_piece(to) { for (let p = 0; p <= last_piece; ++p) if (game.pos[p] === to) @@ -865,15 +841,6 @@ function select_stack(s) { return list } -function add_one_troop(p) { - for (let x of all_power_generals[game.power]) { - if (game.pos[x] === game.pos[p] && game.troops[x] < 8) { - game.troops[x] ++ - break - } - } -} - function eliminate_general(p, indent) { if (indent) log(">P" + p + " eliminated") @@ -887,6 +854,26 @@ function eliminate_general(p, indent) { /* SEQUENCE OF PLAY */ +const TURN_NAME = [ + "Setup", + "Turn 1", + "Turn 2", + "Turn 3", + "Winter 1741", + "Turn 4", + "Turn 5", + "Turn 6", + "Winter 1742", + "Turn 7", + "Turn 8", + "Turn 9", + "Winter 1743", + "Turn 10", + "Turn 11", + "Turn 12", + "Winter 1744", +] + const POWER_FROM_ACTION_STAGE = [ P_FRANCE, // and bavaria P_PRUSSIA, // and saxony @@ -905,8 +892,29 @@ function set_active_to_current_action_stage() { set_active_to_power(POWER_FROM_ACTION_STAGE[game.stage]) } +const POWER_FROM_WINTER_STAGE = [ + P_FRANCE, + P_BAVARIA, + P_PRUSSIA, + P_SAXONY, + P_PRAGMATIC, + P_AUSTRIA, +] + +const title_from_winter_stage = [ + "=0", + "=1", + "=2", + "=3", + "=4", + "=5", +] + +function set_active_to_current_winter_stage() { + set_active_to_power(POWER_FROM_WINTER_STAGE[game.stage]) +} + function goto_end_turn() { - // TODO: winter goto_start_turn() } @@ -914,15 +922,18 @@ function goto_start_turn() { game.turn += 1 game.stage = 0 - log("# Turn " + game.turn) + log("# " + TURN_NAME[game.turn]) game.selected = -1 - delete game.ia_lost - // MARIA: politics - // MARIA: hussars + if (game.turn % 4 === 0) { + goto_winter_turn() + } else { + + // MARIA: politics - goto_place_hussars() + goto_place_hussars() + } } function goto_action_stage() { @@ -962,6 +973,9 @@ function goto_place_hussars() { function end_place_hussars() { set_clear(game.moved) + log("=" + P_AUSTRIA + " Hussars") + for (let p of all_hussars) + log("Hussar at S" + game.pos[p] + ".") goto_action_stage() } @@ -2073,16 +2087,12 @@ states.move_supreme = { /* RE-ENTER SUPPLY TRAIN */ -/* - TODO: move to end of movement (like friedrich recruitment)? -*/ - function goto_re_enter_train() { if (all_controlled_powers(game.power).length > 1) game.state = "re_enter_train_power" else game.state = "re_enter_train" - game.re_enter = { + game.recruit = { pool: [], used: [], pieces: [], @@ -2094,12 +2104,11 @@ function can_train_re_enter(p) { return ( (is_piece_on_map(p) || game.pos[p] === ELIMINATED) && !set_has(game.moved, p) && - train_has_re_entry_city(p) + has_re_entry_space_for_supply_train(piece_power[p]) ) } -function train_has_re_entry_city(p) { - let pow = piece_power[p] +function has_re_entry_space_for_supply_train(pow) { if (coop_minor_power(pow) !== pow) return can_re_enter_train_at_power_fortress(pow) || can_re_enter_train_at_power_fortress(coop_minor_power(pow)) else @@ -2147,7 +2156,7 @@ states.re_enter_train = { prompt() { let str - let paid = game.count + sum_card_values(game.re_enter.pool) + let paid = game.count + sum_card_values(game.recruit.pool) let av_trains = 0 for (let p of all_power_trains[game.power]) { @@ -2163,7 +2172,7 @@ states.re_enter_train = { gen_action_card(c) } - if (game.re_enter.used.length > 0) + if (game.recruit.used.length > 0) view.actions.next = 1 if (av_trains > 0) { @@ -2180,20 +2189,20 @@ states.re_enter_train = { prompt(str) - view.draw = game.re_enter.pool + view.draw = game.recruit.pool }, piece(p) { push_undo() - spend_card_value(game.re_enter.pool, game.re_enter.used, 4) + spend_card_value(game.recruit.pool, game.recruit.used, 4) set_add(game.moved, p) - map_set(game.re_enter.pieces, p, game.pos[p]) + map_set(game.recruit.pieces, p, game.pos[p]) game.state = "re_enter_train_where" game.selected = p }, card(c) { push_undo() set_delete(game.hand[game.power], c) - set_add(game.re_enter.pool, c) + set_add(game.recruit.pool, c) }, next() { push_undo() @@ -2207,24 +2216,30 @@ states.re_enter_train_where = { prompt("Re-enter supply train at a major fortress.") view.selected = game.selected - view.draw = game.re_enter.pool + view.draw = game.recruit.pool gen_re_enter_train_at_power_fortress(game.power) if (coop_minor_power(game.power) !== game.power) gen_re_enter_train_at_power_fortress(coop_minor_power(game.power)) }, - space(s) { - game.pos[game.selected] = s + space(to) { + game.pos[game.selected] = to game.selected = -1 game.state = "re_enter_train" + + // remove hussars + for (let p of all_hussars) { + log("P" + p + " removed.") + game.pos[p] = ELIMINATED + } }, } function end_re_enter_train() { - if (game.re_enter.used.length > 0) { + if (game.recruit.used.length > 0) { log_br() - log(POWER_NAME[game.power] + " spent " + game.re_enter.used.map(format_card).join(", ") + ".") - map_for_each(game.re_enter.pieces, (p, s) => { + log(POWER_NAME[game.power] + " spent " + game.recruit.used.map(format_card).join(", ") + ".") + map_for_each(game.recruit.pieces, (p, s) => { if (s !== ELIMINATED) log("Re-entered P" + p + " from S" + s + " at S" + game.pos[p] + ".") else @@ -2234,53 +2249,47 @@ function end_re_enter_train() { } // put back into hand unused cards - for (let c of game.re_enter.pool) + for (let c of game.recruit.pool) set_add(game.hand[game.power], c) - delete game.re_enter + delete game.recruit set_active_to_current_action_stage() game.state = "movement" } -/* RECRUITMENT */ +/* WINTER RECRUITMENT */ -function troop_cost() { - if (is_map_space(game.recruit.re_enter)) - return 8 - return 6 -} +function goto_winter_turn() { -function spend_recruit_cost() { - spend_card_value(game.recruit.pool, game.recruit.used, troop_cost()) -} + // TODO: winter scoring -function has_available_depot() { - for (let s of all_power_depots[game.power]) - // TODO: also allied other player's pieces? - if (!has_enemy_piece(s)) - return true - return false + goto_winter_stage() } -function can_re_enter_general(to) { - if (has_friendly_supply_train(to)) - return false - if (has_non_cooperative_general(to)) - return false - if (1 + count_generals(to) > 3) - return false - return true +function goto_winter_stage() { + set_active_to_current_winter_stage() + + clear_undo() + + log(title_from_winter_stage[game.stage]) + + goto_recruit() } -function can_re_enter_supply_train(s) { - return !has_any_piece(s) +function end_winter_stage() { + clear_undo() + + if (++game.stage === 6) + goto_end_turn() + else + goto_winter_stage() } function goto_recruit() { game.count = 0 - if (!can_recruit_anything_in_theory()) { + if (!can_recruit_anything()) { end_recruit() return } @@ -2289,80 +2298,62 @@ function goto_recruit() { pool: [], used: [], pieces: [], - re_enter: ELIMINATED, troops: 0, } - // if all depots have enemy pieces, choose ONE city in given sector and COST is 8 - if (has_available_depot()) - game.state = "recruit" - else - game.state = "re_enter_choose_city" + game.state = "recruit" } -states.re_enter_choose_city = { - inactive: "recruit", - prompt() { - prompt("Choose city to re-enter troops.") - for (let s of all_power_re_entry_cities[game.power]) - if (!has_enemy_piece(s)) - gen_action_space(s) - }, - space(s) { - push_undo() - game.recruit.re_enter = s - game.state = "recruit" - }, -} - -function has_re_entry_space(p) { - let can_re_enter_at = is_general(p) ? can_re_enter_general : can_re_enter_supply_train - if (is_map_space(game.recruit.re_enter)) - return can_re_enter_at(game.recruit.re_enter) - for (let s of all_power_depots[game.power]) - if (can_re_enter_at(s)) +function has_re_entry_space_for_general() { + for (let s of all_home_country_major_fortresses[game.power]) + if (can_re_enter_general_at_city(s)) return true return false } -function can_recruit_anything_in_theory() { - let unused_everywhere = max_power_troops(game.power) - count_used_troops() - return unused_everywhere > 0 || count_eliminated_trains() > 0 +function can_re_enter_general_at_city(from, to) { + if (is_enemy_controlled_fortress(to)) + return false + if (has_friendly_supply_train(to)) + return false + if (has_non_cooperative_general(to)) + return false + if (count_generals(to) >= 2) + return false + return true } function can_recruit_anything() { - let unused_everywhere = max_power_troops(game.power) - count_used_troops() - let elim_trains = count_eliminated_trains() - let elim_generals = count_eliminated_generals() - let unused_on_map = count_unused_troops_on_map() - // can reinforce on-map generals - if (unused_everywhere > 0 && unused_on_map > 0) - return true - // can re-enter eliminated generals - if (unused_everywhere > 0 && elim_generals > 0 && has_re_entry_space()) - return true - // can re-enter eliminated supply trains - if (elim_trains > 0 && has_re_entry_space()) - return true + for (let p of all_power_generals[game.power]) { + // can re-enter generals + if (game.pos[p] === ELIMINATED && has_re_entry_space_for_general()) + return true + // can recruit troops? + if (is_piece_on_map(p) && game.troops[p] < 8) + return true + } return false } states.recruit = { inactive: "recruit", prompt() { - let cost = troop_cost() - let n_troops = count_used_troops() - let av_troops = max_power_troops(game.power) - n_troops - let av_trains = count_eliminated_trains() - let possible = can_recruit_anything() + let av_general = 0 + let av_troops = 0 + for (let p of all_power_generals[game.power]) { + if (is_piece_on_map(p)) + av_troops += 8 - game.troops[p] + else if (game.pos[p] === ELIMINATED && has_re_entry_space_for_general()) { + av_general += 1 + av_troops += 8 + } + } let str - if (av_trains > 0 && av_troops > 0) - str = `Recruit supply trains and up to ${av_troops} troops for ${cost} each` + if (av_general > 0 && av_troops > 0) + str = `Re-enter generals and recruit up to ${av_troops} troops for 4 each` else if (av_troops > 0) - str = `Recruit up to ${av_troops} troops for ${cost} each` - else if (av_trains > 0) - str = `Recruit supply trains for ${cost} each` + str = `Recruit up to ${av_troops} troops for 4 each` else str = "Nothing to recruit" @@ -2378,31 +2369,23 @@ states.recruit = { view.draw = game.recruit.pool - if (possible && paid / cost < av_troops + av_trains) { - for (let c of game.hand[game.power]) - gen_action_card(c) - } + if (av_troops > 0) { + if (paid / 4 < av_troops) { + for (let c of game.hand[game.power]) + gen_action_card(c) + } - if (paid >= cost) { - if (av_troops > 0) { + if (paid >= 4) { for (let p of all_power_generals[game.power]) { - if (game.troops[p] > 0 && game.troops[p] < 8) { - let s = game.pos[p] - gen_action_piece(s) - } - else if (game.pos[p] === ELIMINATED && has_re_entry_space(p)) + if (game.troops[p] > 0 && game.troops[p] < 8) gen_action_piece(p) - } - } - if (av_trains > 0) { - for (let p of all_power_trains[game.power]) { - if (game.pos[p] === ELIMINATED && has_re_entry_space(p)) + else if (game.pos[p] === ELIMINATED && has_re_entry_space_for_general()) gen_action_piece(p) } } } - if (paid < cost || !possible) + if (paid < 4 || av_troops === 0) view.actions.end_recruit = 1 }, card(c) { @@ -2413,14 +2396,14 @@ states.recruit = { piece(p) { push_undo() - spend_recruit_cost() + spend_card_value(game.recruit.pool, game.recruit.used, 4) if (game.pos[p] === ELIMINATED) { game.selected = p - game.state = "re_enter" + game.state = "re_enter_general_where" } else { game.recruit.troops += 1 - add_one_troop(p) + game.troops[p] += 1 } }, end_recruit() { @@ -2429,63 +2412,63 @@ states.recruit = { }, } -function end_recruit() { - if (game.recruit) { - if (game.recruit.used.length > 0) { - log_br() - if (game.recruit.troops > 0) - log("Recruited " + game.recruit.troops + " troops with " + game.recruit.used.map(format_card).join(", ") + ".") - else - log("Recruited with " + game.recruit.used.map(format_card).join(", ") + ".") - map_for_each(game.recruit.pieces, (p,s) => { - log("Re-entered P" + p + " to S" + s + ".") - }) - if (game.recruit.troops) - log(">" + game.recruit.troops + " troops") - } - - // put back into hand unused cards - for (let c of game.recruit.pool) - set_add(game.hand[game.power], c) - - delete game.recruit - } - - // MARIA: NOT goto_combat() - // goto_combat() -} - -states.re_enter = { +states.re_enter_general_where = { inactive: "recruit", prompt() { prompt("Re-enter " + format_selected() + ".") view.selected = game.selected - - let p = game.selected - let can_re_enter_at = is_general(p) ? can_re_enter_general : can_re_enter_supply_train - - if (is_map_space(game.recruit.re_enter)) { - if (can_re_enter_at(game.recruit.re_enter)) - gen_action_space(game.recruit.re_enter) - } else { - for (let s of all_power_depots[game.power]) - if (can_re_enter_at(s)) - gen_action_space(s) - } + for (let s of all_home_country_major_fortresses[game.power]) + if (can_re_enter_general_at_city(s)) + gen_action_space(s) }, space(s) { let p = game.selected game.pos[p] = s - map_set(game.recruit.pieces, p, s) + set_add(game.recruit.pieces, p) if (is_general(p)) { game.recruit.troops += 1 game.troops[p] = 1 } game.selected = -1 game.state = "recruit" + + // remove hussars + for (let p of all_hussars) { + log("P" + p + " removed.") + game.pos[p] = ELIMINATED + } + + // remove enemy supply trains + for (let p of all_enemy_trains(game.power)) { + log("P" + p + " eliminated.") + game.pos[p] = ELIMINATED + } }, } +function end_recruit() { + if (game.recruit) { + if (game.recruit.used.length > 0) { + log_br() + log("Recruited " + game.recruit.troops + " troops with " + game.recruit.used.map(format_card).join(", ") + ".") + for (let p of game.recruit.pieces) + log("Re-entered P" + p + " at S" + game.pos[p] + ".") + } else { + log("Recruited nothing.") + } + + // put back into hand unused cards + for (let c of game.recruit.pool) + set_add(game.hand[game.power], c) + + delete game.recruit + } else { + log("Recruited nothing.") + } + + end_winter_stage() +} + /* COMBAT (CHOOSE TARGETS) */ function goto_combat() { |