summaryrefslogtreecommitdiff
path: root/rules.js
diff options
context:
space:
mode:
Diffstat (limited to 'rules.js')
-rw-r--r--rules.js255
1 files changed, 237 insertions, 18 deletions
diff --git a/rules.js b/rules.js
index 4edfa17..48b62ee 100644
--- a/rules.js
+++ b/rules.js
@@ -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)