diff options
Diffstat (limited to 'rules.js')
-rw-r--r-- | rules.js | 255 |
1 files changed, 237 insertions, 18 deletions
@@ -53,6 +53,27 @@ const sidecount = hexcount * 3 const AXIS = 'Axis' const ALLIED = 'Allied' +const firepower_name = [ "0", "1", "2", "3", "TF", "DF", "SF" ] + +const SF = 6 +const DF = 5 +const TF = 4 + +const class_name = [ "armor", "infantry", "anti-tank", "artillery" ] + +const ARMOR = 0 +const INFANTRY = 1 +const ANTITANK = 2 +const ARTILLERY = 3 + +const FIREPOWER_MATRIX = [ + [ SF, DF, SF, TF ], + [ SF, SF, SF, TF ], + [ DF, SF, SF, TF ], + [ SF, DF, DF, SF ], +] + + const CLEAR = 2 const PASS = 1 const ROUGH = 0 @@ -159,20 +180,24 @@ function unit_speed(u) { return units[u].speed } +function unit_class(u) { + return units[u].class +} + function is_artillery_unit(u) { - return units[u].class === 'artillery' + return units[u].class === ARTILLERY } function is_armor_unit(u) { - return units[u].class === 'armor' + return units[u].class === ARMOR } function is_infantry_unit(u) { - return units[u].class === 'infantry' + return units[u].class === INFANTRY } function is_antitank_unit(u) { - return units[u].class === 'antitank' + return units[u].class === ANTITANK } function unit_hex(u) { @@ -191,6 +216,21 @@ function set_unit_lost_steps(u, n) { game.units[u] = (game.units[u] & ~3) | n } +function eliminate_unit(u) { + set_unit_hex(u, 0) + set_unit_lost_steps(u, 0) +} + +function reduce_unit(u) { + let s = unit_steps(u) + let hp = unit_hp_per_step(u) + if (s === 1) + eliminate_unit(u) + else + set_unit_steps(u, s - 1) + return hp +} + function is_unit_supplied(u) { return (game.units[u] & 4) === 4 } @@ -239,6 +279,25 @@ function unit_steps(u) { return units[u].steps - unit_lost_steps(u) } +function is_unit_elite(u) { + return units[u].elite +} + +function unit_cv(u) { + if (is_unit_elite(u)) + return unit_steps(u) * 2 + return unit_steps(u) +} + +function unit_hp_per_step(u) { + // TODO: double defense, minefields, etc + return is_unit_elite(u) ? 2 : 1 +} + +function unit_hp(u) { + return unit_steps(u) * unit_hp_per_step(u) +} + function set_unit_steps(u, n) { set_unit_lost_steps(u, units[u].steps - n) } @@ -395,7 +454,7 @@ function release_hex_control(a) { function is_new_battle_hex(a) { if (is_battle_hex(a)) - return !set_has(game.axis_hexes) && !set_has(game.allied_hexes) + return !set_has(game.axis_hexes, a) && !set_has(game.allied_hexes, a) return false } @@ -1172,7 +1231,9 @@ states.move_who = { game.move_road = 4 }, end_move() { - clear_supply_networks() + game.side_limit = {} + game.rommel = 0 + game.from1 = game.from2 = game.to1 = game.to2 = 0 // TODO goto_combat_phase() } @@ -1455,21 +1516,26 @@ states.select_battle = { gen_action('end_combat') }, hex(x) { - push_undo() + clear_undo() game.battle = x goto_defensive_fire() }, + end_combat() { + end_combat_phase() + } } function goto_defensive_fire() { set_passive_player() game.fired = [] + game.hits = [ 0, 0, 0, 0 ] game.state = 'defensive_fire' } function goto_offensive_fire() { set_active_player() game.fired = [] + game.hits = [ 0, 0, 0, 0 ] game.state = 'offensive_fire' } @@ -1496,27 +1562,179 @@ const xxx_fire = { } }, unit(who) { - clear_undo() - set_unit_fired(who) + game.selected = [ who ] + game.state = game.state + '_target' + }, +} - let done = true +const xxx_fire_target = { + prompt() { + view.prompt = `Select a target class.` + + let hp = [ 0, 0, 0, 0 ] for (let u = 0; u < units.length; ++u) - if (is_friendly_unit(u) && !is_unit_fired(u) && unit_hex(u) === game.battle) - done = false + if (is_enemy_unit(u) && unit_hex(u) === game.battle) + hp[unit_class(u)] += unit_hp(u) + for (let i = 0; i < 4; ++i) + hp[i] -= game.hits[i] + + let who = game.selected[0] + let fc = unit_class(who) + + gen_action_unit(who) // deselect + + // armor must target armor if possible + if (fc === ARMOR && hp[ARMOR] > 0) { + gen_action('armor') + return + } + + // infantry must target infantry if possible + if (fc === INFANTRY && hp[INFANTRY] > 0) { + gen_action('infantry') + return + } + + if (hp[ARMOR] > 0) + gen_action('armor') + if (hp[INFANTRY] > 0) + gen_action('infantry') + if (hp[ANTITANK] > 0) + gen_action('antitank') + + // only artillery may target artillery if other units are alive + if (hp[ARTILLERY] > 0) { + if (fc === ARTILLERY || + (hp[ARTILLERY] <= 0 && hp[INFANTRY] <= 0 && hp[ANTITANK] <= 0)) + gen_action('artillery') + } + }, + unit(u) { + game.selected = [] + resume_fire() + }, + armor() { + let who = game.selected[0] + game.selected = [] + fire_at(who, ARMOR) + }, + infantry() { + let who = game.selected[0] + game.selected = [] + fire_at(who, INFANTRY) + }, + antitank() { + let who = game.selected[0] + game.selected = [] + fire_at(who, ANTITANK) + }, + artillery() { + let who = game.selected[0] + game.selected = [] + fire_at(who, ARTILLERY) + }, +} + +const xxx_fire_hits = { + prompt() { + view.prompt = `Apply hits.` + let done = true + for (let u = 0; u < units.length; ++u) { + if (is_friendly_unit(u) && unit_hex(u) === game.battle) { + if (game.hits[unit_class(u)] > 0) { + gen_action_unit(u) + done = false + } + } + } if (done) - end_fire() + gen_action_next() + }, + unit(who) { + push_undo() + let c = unit_class(who) + game.hits[c] -= reduce_unit(who) + }, + next() { + clear_undo() + if (game.state === 'defensive_fire_hits') { + goto_offensive_fire() + } else { + end_battle() + } }, } -function end_fire() { - if (game.state === 'defensive_fire') - goto_offensive_fire() - else - end_combat_phase() +function roll_fire(who, fp, tc) { + let roll = random(6) + 1 + log(`${who} fired ${firepower_name[fp]} ${roll} at ${class_name[tc]}`) + if (roll >= fp) + return 1 + return 0 +} + +function fire_at(firing, tc) { + let fp = FIREPOWER_MATRIX[unit_class(firing)][tc] + let cv = unit_cv(firing) + + set_unit_fired(firing) + + for (let i = 0; i < cv; ++i) + game.hits[tc] += roll_fire(firing, fp, tc) + + resume_fire() +} + +function resume_fire() { + game.state = game.state.replace("_target", "") + let done = true + for (let u = 0; u < units.length; ++u) + if (is_friendly_unit(u) && !is_unit_fired(u) && unit_hex(u) === game.battle) + done = false + if (done) + goto_fire_hits() +} + +function goto_fire_hits() { + // clamp number of hits to available hp + let hp = [ 0, 0, 0, 0 ] + for (let u = 0; u < units.length; ++u) + if (is_enemy_unit(u) && unit_hex(u) === game.battle) + hp[unit_class(u)] += unit_hp(u) + for (let i = 0; i < 4; ++i) + game.hits[i] = min(game.hits[i], hp[i]) + + if (game.state === 'defensive_fire') { + set_active_player() + game.state = 'defensive_fire_hits' + } else { + set_passive_player() + game.state = 'offensive_fire_hits' + } +} + +function end_battle() { + clear_undo() + set_active_player() + set_delete(game.active_battles, game.battle) + set_delete(game.assault_battles, game.battle) + game.state = 'select_battle' + game.battle = 0 } states.defensive_fire = xxx_fire states.offensive_fire = xxx_fire +states.defensive_fire_target = xxx_fire_target +states.offensive_fire_target = xxx_fire_target +states.defensive_fire_hits = xxx_fire_hits +states.offensive_fire_hits = xxx_fire_hits + +function end_combat_phase() { + // TODO: blitz + // TODO: final supply check + // TODO: supply cards revealed + end_player_turn() +} // === DEPLOYMENT === @@ -2120,6 +2338,7 @@ exports.view = function(state, current) { if (game.battle) view.battle = game.battle if (game.fired) view.fired = game.fired + if (game.hits) view.hits = game.hits if (game.flash) view.flash = game.flash return common_view(current) |