squadleader/lib.rs
1#![allow(unused)]
2
3//! _squadleader_ is a crate containing a type system and a rule engine that is
4//! based on the rules of the classic boardgame with the same name.
5//! Please note, there's no affiliation between this makers of this crate
6//! and the publishers of the boardgame.
7//!
8//! Further, this crate is not supposed to be a game. However, it can probably
9//! be used as a starting point, create one.
10//! The purpose of this crate is to model types and rules of the original game.
11//! You can think of it as a type system and rule engine modeling the game
12//! of chess, without being an actual playable chess game.
13//!
14//! References to the original 4th Edition Squad Leader rules are made
15//! in the source, and expressed in the following manner:
16//! `SLX.Y` refers to paragraph X.Y in the original 4th ed. rule book.
17//! All rules, that do not reference the original game, are labled RX.Y.
18
19// "Courage, above all things, is the first quality of a warrior."
20// –Carl von Clausewitz
21
22
23pub fn run_squadleader() {
24 println!("Place your SL logic here");
25}
26
27////////////////////////////////////////////////////////////////////////////////
28// Place all imports below:
29use std::collections::HashMap;
30use std::marker;
31// use scenario::*;
32
33////////////////////////////////////////////////////////////////////////////////
34/// SL1. Addmendums, labeled AX.Y, made to the original SL rule set.
35///
36/// A1.1
37/// A generic struct representing any target (building, unit, armour, etc).
38/// Every target has a location (in terms of longitude, latitude), and an
39/// elevation. Every target also has an armor value - a value that has to be
40/// super seeded for any destructive force to have a chance to inflict damage
41/// on the target. Finally, every target has a health value - how much damage it
42/// can withstand before it is destroyed.
43struct Target {
44 location: Hex,
45 elevation: u8,
46 armor: u8,
47 health: u8,
48}
49
50/// A struct reponsible for tracking relevant events on the battlefield.
51struct BattleManager {}
52
53/// The ScenarioManager is reponsible for tracking scenrio relevant events on
54/// the battlefield. TODO: give examples of scenerio events.
55struct ScenarioManager {}
56
57/// Marker States
58// A1.2
59// The state of a non-broken combat unit at the start of a new Game Turn.
60struct Unphased;
61// SL5.1
62// Any combat unit that moved during Phase::PrepFire of the current GameTurn.
63// TODO: Decide on naming of states: struct PrepFireMoved;
64struct PrepFired;
65// Asserts SL5.75
66// TODO: consider other solutions here.
67// TODO: what types of support weapons are there?
68struct FiredSupportWeapon;
69
70////////////////////////////////////////////////////////////////////////////////
71/// SL2. Combat Units
72///
73/// SL2.1 Unit: a unit represents any infantry squad, officer, or NCO
74/// (Non-Commissioned Officer), present on the battlefield.
75// TODO: consider weather to treat Armour crews and AT Gun crews as units.
76/// Unit is a Generic Type representing any unit (leader, squad, armour crew,
77/// Anti Tank Gun crew) on the battlefield.
78#[derive(Debug)]
79enum Unit {
80 // TODO: _consider: Are Armour and ATGun treated differently in terms of
81 // PrepFire and limitations to Movement?
82 Armour,
83 ATGun,
84 Leader,
85 Squad,
86}
87
88impl Unit {
89 fn has_moved(&self) -> bool {
90 todo!()
91 }
92
93 fn has_prepfired(&self) -> bool {
94 todo!()
95 }
96
97 fn broken(&self) -> bool {
98 todo!()
99 }
100
101 fn move_to_dest(&self) -> bool {
102 todo!()
103 }
104}
105
106/// SL2.2 Firepower: the basic strength that a unit can attack with in combat.
107/// SL2.3 Range: the distance, calculated as the least number of game hexes,
108/// from the firing unit to the target, that a unit can reach with its normal
109/// firepower factor.
110/// SL2.4 Morale: a rating of a unit's ability to withstand combat stress before
111/// 'breaking down psychologically and fleeing'. A broken unit will remain
112/// broken until it has received successful treatment by non-broken personel.
113use std::net::Ipv4Addr;
114#[derive(Debug)]
115struct Squad<State> {
116 // The `ars` is an identifying value, based on the United States Army
117 // Regimental System (USARS) - an organizational and classification system
118 // used by the United States Army. The `ars` provides each squad and leader
119 // on the battlefield with a unique identification, within a single
120 // regiment. A squad consists of 6-10 soldiers. To simplify
121 // identification on the battlefield, we have decided to treat the whole
122 // squad as one single entity (solider), with its unique ars.
123 //
124 // Let's say that we want to model the following army:
125 //
126 // + 1 Battalion: 100-1_000 soldiers, comprised of 3-5 companies
127 // + 3 Companies: each with 60-200 soldiers, comprised of 3-4 platoons
128 // + Platoon: 18-50 soldiers, comprised of 3-4 squads
129 // + Squad: comprised of 6-10 soldiers
130 //
131 // Example
132 // ```
133 // use std::net::Ipv4Addr;
134 //
135 // let squad_id = Ipv4Addr::new(Battalion, Company, Platoon, Squad);
136 // //1st Battalion, 3rd Company, 4th Platoon, 4th Squad becomes:
137 // let squad_id = Ipv4Addr::new(1, 3, 4, 4);
138 // ```
139 //
140 pub ars: Ipv4Addr,
141 firepower: u8,
142 range: u8,
143 morale: u8,
144 // Marker Type used to model illegal actions based on state.
145 // See for example SL5.1.
146 _state: marker::PhantomData<State>, // TODO: A way to model that a unit is either broken or non-broken.
147 // condition: Condition,
148}
149
150impl Squad<Unphased> {
151 fn has_moved(&self) -> bool {
152 todo!()
153 }
154 fn stacked_with_leader(&self) -> bool {
155 todo!()
156 }
157}
158
159// SL2.6 Leadership affects unit performance
160impl Squad<Unphased> {
161 // TODO: modifier when calculating `unit To Hit` or `unit save morale`.
162 fn get_leadership_modifier(&self) -> u8 {
163 todo!()
164 /*
165 match self.led_by()? {
166 None => 0,
167 Some => self.led_by().leadership(),
168 }
169 */
170 }
171}
172
173/// SL2.5 Identity: the name and rank of the leader unit.
174/// SL2.6 Leadership: a rating of a leader's ability to get the best performance
175/// out of the combat units under his command.
176/// The leadership number, usually negative, is added as a modifier to any
177/// morale or firepower test performed by a unit under the leader's command.
178///
179struct Leader {
180 identity: String,
181 pub leadership: u8,
182 // Morale: a rating of the leader's ability to withstand combat stress
183 // before breaking down psychologically, and fleeing'.
184 // A broken leader is non-operational until receiving treatment by
185 // non-broken personel.
186 //
187 // TODO: Using illegal states, make sure that leadership modifier to units
188 // is only applicable if the leader himself is non-broken.
189 morale: u8,
190 // TODO: _WIP Ideas for fields to add:
191 // Competence: a rating of a leader's tactical competence.
192 //
193 // TODO: A way to model that a leader is either broken or non-broken.
194 // condition: Condition,
195}
196
197// TODO: _code-structure: move to morale
198// TODO: perhaps improve naming of enum?
199// TODO: perhaps add Surrendered?
200/// A non-broken unit is fully able to operate under combat conditions.
201/// A broken unit is psychologically broken down and unable to follow orders.
202/// The unit's survivial instincts will trumph any army discipline until the
203/// unit is rallied into a non-broken state.
204///
205enum Condition {
206 Broken,
207 Composed,
208}
209
210/// SL2.7 Weapon Type: A short description of the weapon type.
211/// All suppport weapons must be operated by combat units.
212/// Enemy support weapons can be captured and used by either side.
213/// SL2.8 Penetration: a support weapon's ability to affect targets that are in
214/// line with the target's `Hex`. A machine gun with a penetration value of 3
215/// will have equal effect on the target hex, but also on the additional 2 hexes
216/// that are in a straight line, along the line of sight, behind the target hex.
217/// A penetration value of 1 will only affect the targeted hex.
218/// SL2.9 _Breakdown_: combat environments are harsh.
219/// To express this, support weapons have a number which determines if the
220/// weapon temporarily breaks down and malfunctions during operation.
221///
222enum WeaponType {
223 Rifle,
224 LMG,
225 MMG,
226 HMG,
227 Mortar,
228 Demolition,
229 Flamethrower,
230 APMine,
231 ATMine,
232 ATRifle,
233 ATGun,
234 TankGun,
235 Artillery,
236}
237
238struct SupportWeapon {
239 weapon: WeaponType,
240 firepower: u8,
241 penetration: u8,
242 range: u8,
243 breakdown: u8,
244}
245
246/// SL18.1
247/// All vehicles must be operated by crews.
248/// Enemy vechicles cannot be captured and used.
249struct Vechicle {
250 mf: u8,
251}
252
253////////////////////////////////////////////////////////////////////////////////
254/// SL3. Maps
255///
256/// Each hex represents a distance of meters on the battlefield.
257/// The default hexdistance is 40 meters, but that value can be changed.
258/// The distance must be chosen with care and with intetion to capture the flow
259/// of tactical small unit infantry combat.
260///
261// TODO: add functionality to change the HEXDISTANCE.
262static HEXDISTANCE: AtomicU8 = AtomicU8::new(40);
263
264/// SL3.2 A hexagon shaped grid is superimposed on the battlefield map.
265/// The default is for the hexagon grid to be six-sided and visible.
266///
267// TODO: Add functionality to toogle the visibility of the hexagon.
268// const mut CONST DRAWHEXAGONS:bool = true;
269///
270/// Every hex has a unique id which can be referenced against a map database.
271/// The map database contains information about what's in the hex, including
272/// elevation data, and the category of terrain that the hex belongs to.
273// TODO: Add map database connectivity functionlality.
274///
275/// SL3.6 Map designers can include aesthetic elements which have no terrain
276/// effect on movement. It's important to note that each hex on a map __can__
277/// belong to more than one terrain category. The effect of this on movement
278/// and combat resolution is __cumulative__.
279/// For example, a hex can contain both a TERRAIN::HEDGES and TERRAIN::WOODS.
280/// The cost of moving through that hex would be calculated by adding the cost
281/// of both terrain types.
282///
283// TODO: Implement terrain category in database.
284struct Hex {
285 id: u32,
286 elevation: u8,
287 // TODO: not super happy of type-setting terrain as a Vec<Terrain>, but how
288 // else can we handle the fact that a single hex could contain
289 // a house on a hill surrounded by a hedge?
290 terrain: Vec<Terrain>,
291}
292
293impl Hex {
294 fn broken(&self) -> bool {
295 todo!()
296 }
297}
298
299/// Returns the distance from hex A, to hex B in the number of inclusive hexes.
300fn get_distance(origin: Hex, target: Hex) -> u8 {
301 todo!()
302}
303
304/// Returns a boolean indicating if the unit located in origin hex can see
305/// the enemy located in the target hex.
306/// The requires a database hit due to the fact that all line of sight from
307/// to any other hex on the map has been precalculated in to a lookup table.
308/// Each hex has a center which is used to calculate Line of Sight (LOS) to other
309/// hexes, for targeting purposes. A unit (except for mortar units) can only fire
310/// on targets that they can see (that is in their LOS).
311fn get_los(origin: Hex, target: Hex) -> bool {
312 todo!()
313}
314
315/// SL3.1 Terrain will affect how fast units can move through a hex on the map.
316/// The Terrain Effects Chart (or TEC) is a database containing a description of
317/// all terrain types.
318// TODO: add TEC database and connectivity.
319///
320/// The Terrain Effect Function (TEF) calculates how a particular type of terrain
321/// affects movement. A hex can contain multiple terrain types.
322/// The cost of moving through such a hex is cumulative.
323fn get_terrain_effect_on_move(hex: Hex) -> u8 {
324 // types of terrain in the hex
325 // SL3.8 accumulate terrain types to calculate effect
326 todo!();
327}
328/// SL3.3 Each hex on the map has a center point. This point is used to
329/// calculating line of sight (LOS) between two different hexes on a map.
330/// These points are important because combat units are assumed to be firing
331/// from these center points towards center points in other hexes.
332
333/// The LOS calculations are persisted in the each map database and then
334/// used during simulation to determine if there's a clear LOS between two hexes.
335// TODO: write a function that can, if provided with a map data file, generate
336// LOS between two hexes on that map and persist it.
337
338/// SL3.4 Map designers should aim for an `isomorphic` quality in their maps
339/// making it possible to combine the edge of each map to any other map.
340// TODO: write functionality to determine if two maps can be combined, edge to
341// edge, and flag if the maps are in fact incompatible.
342/// SL3.7 The half hexes along the edge of the a map are all treated as full
343/// hexes in terms of terrain and line of sight.
344// TODO: EDGE CASE: edge of maps contain half hexes to enable isomorphic
345// combination of maps. Write functionality to handle this edge case.
346
347/// SL3.5 Each hex has a unique identifier which commanders can use to communicate
348/// unit movement and targeting.
349// TODO: consider how Hex of edge hexes is determined for combined maps.
350// The likely solution is to have the halves of each edge hex to create a new
351// hex, however the id of that hex is something that needs to be considered.
352/// The classic Hex format is:
353/// 1AB3 where <MAPID(1A)-ROWLETTER(B)-ROWHEXNUMBER(3)>
354fn get_grid_coordinate() -> Hex {
355 // Normally used when clicking on a map and reading the Hex of the hex.
356 todo!()
357}
358
359////////////////////////////////////////////////////////////////////////////////
360/// SL4. Sequence of Play
361///
362/// A game turn consists of two complete eight phase player turns. Meaning, each
363/// player will perform the 8 phases, for a combined total of 16 phases.
364/// The 16 phases represent one complete game turn.
365/// Each game turn represents 2 minutes of actual time.
366use std::sync::atomic::{AtomicU8, Ordering};
367// TODO: add functionality to change the length of a GAMETURN.
368static GAMETURN: AtomicU8 = AtomicU8::new(2);
369
370/// Reaction is slower than action, therefore it is important to distinguish
371/// between the first moving player and the second moving player.
372/// A player can either attack or defend, and just like two boxers, a first
373/// mover may want to preempt an attack by moving into a defensive position -
374/// an action that would improve his defensive posture.
375/// In each game turn, one player will be the defender, while the othe will be
376/// the attacker, they then switch and the defender becomes the attacker.
377/// It should be noted that it's the attacker who goes through the phases.
378enum Player {
379 Attacker,
380 Defender,
381}
382
383/// The player who moves first in a game turn is refered to as the
384/// Player::Attacker, while the other player is refered to as Player::Defender.
385enum Phase {
386 // SL4.1
387 // Both players can attempt to repair malfunctioning support weapons and
388 // attempt to rally broken units.
389 // TODO: add fn repair_malfunctioning_support_weapon()
390 // TODO: add fn rally_broken_unit().
391 Rally,
392 // SL4.2
393 // The Player::Attacker may order any of his units to fire on any
394 // enemy units that are within the unit's line of sight.
395 // TODO: Any unit that fires under this phase is indicated graphically by a
396 // Marker::PrepFire.
397 PrepFire,
398 // SL4.3
399 // Player::Attacker may order unbroken units that did not Phase::PrepFire
400 // to execute a movement order.
401 Movement,
402 // SL4.4
403 // Player::Defender may order any unbroken units to fire at any enemy units
404 // that are either: 1) in their current LOS, or, 2) moved through their LOS
405 // during Phase::Movement.
406 DefensiveFire,
407 // SL4.5
408 // Player::Attacker may now order any of his units who moved during
409 // Phase::Movement to fire at enemy units. The penalty for firing after
410 // movement is that firepower is halfed, rounded down.
411 // TODO: assert that firepower is halved (rounded down) for units that moved.
412 // Units that did neither Phase::PrepFire nor Phase::Movement can fire at their
413 // full firepower.
414 // At the end of the AdvancingFire phase all Marker::PreFire are removed.
415 // TODO: function for removing Marker::Prepfire.
416 AdvancingFire,
417 // SL4.6
418 // Both players, first Player::Attacker, then Player::Defender, __must __
419 // move their broken units into cover of terrain type either
420 // Terrain::Wood or Terrain::Building. A unit that is already in one of
421 // of these two terrain types, need not move, __unless__, it is adjacent
422 // to an enemy unit.
423 // TODO: fn to check if a broken unit has to move despite being located in a
424 // Wood hex or Building hex, due to being adjacent to an enemy unit.
425 Rout,
426 // SL4.7
427 // As a final push, Player::Attacker may now move any of his non-broken units
428 // one hex forward. The hex moved into is allowed to contain enemy units.
429 // This is the only phase in which combat troops are allowed to be moved into
430 // a hex occupied by enemy units.
431 // TODO: add fn to prohibit all movement of opposing units into the
432 // same hex, unless it's Player::Attacker doing so in Phase::Advance.
433 // TODO: consider exception 27, 53.4, 56, 57.
434 Advance,
435 // SL4.8
436 // All units, on both sides, who find themselves occupying the same hex must
437 // attack each other. Results are calculated using the Close Combat Table (CCT).
438 CloseCombat,
439}
440
441// SL4.9 Game Turn
442// After the end of SL4.8 Phase:CloseCombat the Player::Attacker and
443// Player::Defender switches.
444// Once both players have each reached Phase::CloseCombat, then the current
445// game turn is over, and the next one starts, or the game is over.
446//
447// A Game Turn is considered complete when both the attacking entity and the
448// defending entity have gone through steps SL4.1 to SL4.8.
449// TODO: The Battlemanger can now increment the gameturn_counter by 1.
450// TODO: If a scenario is being modeled then increment the Scenario Turn Record
451// Counter by 1.
452fn update_scenario_turn() {
453 // Phase.counter() mod 16 represents the number of complete Game Turns.
454 // Everytime the result is 0, increment the scenario game turn by one.
455 // Check if scenario game turn has reached limit and the game is over.
456 // TODO: implement Phase.counter() mod 16
457 // TODO: implement check if Phase.counter() > Scenario Turn Record Chart
458 // which would mean that the simulation/game is over.
459 todo!()
460}
461
462////////////////////////////////////////////////////////////////////////////////
463// SL5. Movement
464//
465// A trait describing anything on the battlefield that can be given an order.
466// An `Orderable` type can refuse to execute the order - usually due to being
467// in a morally broken condition, or having surrendered to enemy units.
468
469// For any type to have the Orderable trait, must implement `fn communicate.`
470// TODO: create a trait that can be used to identify units who can communicate.
471pub trait Orderable {
472 fn copythat() -> String;
473}
474
475/// A trait denotating any type that can carry equipment and portable weapons.
476pub trait Carrier {
477 fn carry() -> bool;
478 fn abandon() -> bool;
479}
480
481// Handles: Phase::Advance
482// Any unit that the player selects to move must pass the checks of not being
483fn advance_unit<T: Orderable>(unit: &T, dest: Hex) -> bool {
484 // if unit.condition != Condition::Broken {}
485 todo!()
486}
487
488// A broken unit in cover has to move if it finds itself adjacent to an enemy unit.
489// Handles: Phase::Rout
490fn broken_in_cover() {
491 // For every broken unit, check that no enemy unit is in an adjacent hex.
492 // If an enemy is adjacent then the unit has to be routed into another cover.
493 todo!("check unit is in a hex that is considered: TerrainType::Cover");
494 // TODO: todo!("check that the unit is not adjacent to enemy unit.")
495}
496
497// Fire Orders
498fn order_prep_fire() {
499 todo!("place graphical Marker::PrepFire on units which fired during Phase::PrepFire")
500}
501fn order_defensive_fire() {
502 todo!("Determine which units that are viable targets based on LOS")
503}
504
505fn order_moved_fire(unit: Unit, target: Unit) {
506 // The unit's firepower is halfed because it moved during Phase::Movement
507 // and thus did not have time to properly find targets and aim at them.
508 // unit.firepower / 2
509 todo!("Calculate units halved firepower, rounded up.")
510}
511
512fn order_aimed_fire(unit: Unit, target: Unit) {
513 // The unit neither prep fired nor moved. This enable the unit to fire at
514 // its full firepower.
515 // Unit's which Phase::PrepFire may not fire during the advancing fire phase.
516 if unit.has_moved() {
517 String::from("The unit moved and thus cannot fire during the advanced fire phase");
518 } else {
519 determine_fire_effect(unit, target);
520 }
521 todo!("Fire with full firepower at enemy unit.")
522}
523
524fn determine_fire_effect(attacker: Unit, defender: Unit) {
525 todo!()
526}
527
528// Player::Defender may order any unbroken units to fire at any enemy units
529// that are either: 1) in their current LOS, or, 2) moved through their LOS
530// during Phase::Movement. All targets that passed through the LOS are marked
531// as Marker::EnPassant, after the chess rule.
532fn get_enpassant_targets() -> Vec<Hex> {
533 // For each unit that moved during the movement phase
534 // calculate if it passed through any enemy unit's LOS.
535 // If a unit passed through the LOS of an enemy unit, a Marker::EnPassant
536 // is added to the enemy unit in question.
537 todo!(
538 "Calculate En passant targets and add them to list of viable targets."
539 )
540}
541
542/// OrderResponse
543/// Not every order by the commander can be executed, there can be many reasons why.
544enum OrderResponse {
545 CopyThat, // The unit has received the order and will attempt to execute it."
546 UnitBroken,
547 PrepFired,
548}
549
550fn response_to_order(response: OrderResponse) -> String {
551 match response {
552 OrderResponse::CopyThat => String::from("Copy that, Sir! Executing order."),
553 OrderResponse::UnitBroken => String::from("The unit is broken and unable to execute the order."),
554 OrderResponse::PrepFired => String::from("The unit fired during the prep fire phase and is therefore not eligable for this order."),
555 }
556}
557
558/// Orders
559fn order_move(unit: Unit) {
560 // Conditions to move
561 // 1. The unit is not broken.
562 // 2. The unit did not fire during Phase::PrepFire.
563
564 // is_broken(unit.id)
565 // has_prep_fired(unit.id)
566 if unit.broken() {
567 println!("{}", response_to_order(OrderResponse::UnitBroken));
568 } else if unit.has_prepfired() {
569 println!("{}", response_to_order(OrderResponse::PrepFired));
570 } else {
571 println!("{}", response_to_order(OrderResponse::CopyThat));
572 unit.move_to_dest();
573 }
574
575 // Instead of each game element keeping track of its own state, perhaps it's
576 // better to have an hashmap tracking all broken units
577 // and another hashmap tracking all units that prep fired.
578 todo!("Use hashmap solution instead of struct property.")
579}
580
581// Move a unit from its current location to the provided destination.
582// Terrain effects will affect if the unit can reach the destination.
583fn move_to_dest() {
584 todo!()
585}
586
587/// Markers are used to indicate state and help the player get a sense of what's
588/// going on in the battlefield.
589enum Marker {
590 PrepFire, // The unit fired during the PrepFire phase.
591 EnPassant, // During its movement phase, the unit passed through one or more enemy unit's LOS .
592 ProximityPanic, // The marker indicates that a broken unit has to be moved because of enemy proximity.
593}
594
595// Fire Phase
596/// Firing into or through a hex with multiple terrain types has a cumulative
597/// effect.
598fn terrain_effect_movement(hex: Hex) -> u8 {
599 todo!()
600}
601
602// Game Loop
603fn game_loop() {
604 todo!("Implement a state machine which steps throug the game phases.");
605}
606
607// Fire
608/// The penetration value is always adjusted to 1 when firing from one elevation
609/// to a different elevation.
610/// Returns the adjusted penetration value of the unit.
611fn sw_calc_penetration(origin: &Hex, target: &Hex) -> Option<u8> {
612 if origin.elevation == target.elevation {
613 Some(1)
614 } else {
615 None
616 }
617 // TODO: move this into the fire sequence of the support weapon?
618}
619
620/// Support weapons that can penetrate more than one hexes can select which hexes
621/// to target, and these hexes need not be adjacent to each other.
622/// For example: a light machine gun with a penetration of 3 can affect three
623/// hexes in total, the target hex and two more hexes along it's line of sight.
624/// In the following array, T is the target hex. The first and second P represent
625/// hexes that are not adjacent to eacher, but they can still be penetrated.
626/// However, there cannot be any obstacles (like buildings and woods) between
627/// the penetration hexes.
628/// -> [_,T,_,P,_,P,_,_]
629fn can_penetrate() -> bool {
630 todo!()
631}
632
633fn terrain_effect_combat(hex: Hex) -> u8 {
634 // types of terrain in the hex
635 // accumulate terrain types to calculate effect
636 todo!()
637}
638
639// Close Combat
640// Handles: Phase::CloseCombat
641fn close_combat() {
642 // For all opposing units in the same hex
643 // use the Close Combat Table to calculate results
644 // Update state (remove casulties).
645}
646
647////////////////////////////////////////////////////////////////////////////////
648// SL5. Movement
649//
650// TODO: look to create a global const that can be set.
651// TODO: check if this information is redundant.
652// Each hex in the game represents 40 meters (from hex border to hex border).
653//
654// SL5.1 Only units that _did not fire_ during the Phase::PrepFire may move
655// during the ensuing Phase::Movement.
656// TODO: mechanism for limiting condition to _ensuing_.
657// TODO: _consider using TypeState to assert rules.
658impl BattleManager {
659 fn unit_can_move(&self, u: &Unit, p: &Phase) {
660 todo!()
661 /*
662 // Check the HashMap, did the unit move during the prep fire phase?
663 match !self.Moved.contains_key(p, u) {
664 Ok() => return true,
665 Err(e) => return RuleBreak::E5100, //UNIT_TRYING_TO_MOVE_POST_PREP_FIRED
666 }
667 */
668 }
669}
670
671// SL5.2 Units can move inside the limits of their Movement Factors (MF).
672// The number of hexes that the unit can move is a function of its MF and
673// various factors, such as leaders, terrain, presence of enemy units, items
674// carried, enemy fire, etc.
675
676// SL5.3.1 Unites can move over a hex containing other units.
677// SL5.3.2 One hex can contain more than one unit but not more than ???
678// TODO: decide how many units that can be stacked in one hex.
679
680// SL5.4 All counters, except vehicles, have movement factors (MF) alloted to them:
681// Support Weapons must be carried by Squad, Leader, or Vehicle
682// TODO: rules to handle movement of support weapons by leader, squad, or vehicle.
683enum MF {
684 Squad = 4, // SL5.41
685 Leader = 6, // SL5.42
686 // Vehicles MF is a field of vechicle structs.
687 // TODO: implement Vehicle struct with MF.
688}
689
690// SL5.44 If a squad spends the entire Phase::Movement in the company with
691// a leader, then it will recive a MF bonus of 2.
692// TODO: handle led by Leader and stacked with Leader for entire movement phase.
693fn calcuate_mf_bonus(s: Squad<Unphased>) {
694 todo!()
695 /*
696 if s.stacked_with_leader() {
697 2
698 } else {
699 0
700 }
701 */
702}
703
704// SL5.5 Moving into a hex has a MF cost, depending on the type of terrain.
705// SL5.51 Crossing walls and hedges places a 1 MF penalty on the units MF.
706// SL5.52 A unit moving from one road hex to another will only pay 1 MF for every second
707// road hex it traverses.
708// TODO: read up on enums and data types.
709enum Terrain {
710 OpenGround,
711 Shellhole,
712 Wheatfield,
713 OnRoad,
714 OntoRoad,
715 Woods,
716 EnterBuilding,
717 WithinBuilding,
718 OverWall,
719}
720
721impl Terrain {
722 //TODO: based on unit type or armour.
723 //TODO: _consider: should these be pulled from db?
724 fn movement_cost(&self) -> u32 {
725 match self {
726 Terrain::OpenGround => 1,
727 Terrain::Shellhole => 1,
728 Terrain::Wheatfield => 1,
729 Terrain::OnRoad => 1,
730 Terrain::OntoRoad => 1,
731 Terrain::Woods => 2,
732 Terrain::EnterBuilding => 2,
733 Terrain::WithinBuilding => 2,
734 Terrain::OverWall => 1,
735 }
736 }
737}
738// SL5.53 A Squad moving upwards, for example from TerrainCost::OpenGround;
739// OnRoad, Building, and Woods, from one terrain level to a higher one, will
740// double its MF cost.
741//
742// Moving along same level carries no bonus or additional cost.
743// Nor is there a bonus for moving from one level to a lower level.
744// TODO: add functionality to double terrain cost due to higher ground.
745// TODO: verify the use of Type State below.
746impl Squad<Unphased> {
747 fn tfx_higher_ground(&self, orig: Hex, dest: Hex) -> bool {
748 dest.elevation > orig.elevation
749 }
750}
751
752// SL5.54 Terrain effects are cummulative: hexes containig more than one terrain
753// type have the MF of each type added together.
754// TODO: add fn to cummulate terrain fx.
755
756// SL5.6 Infantry units can move up to and around a hex containing enemy units
757// but may only move into a hex containing an enemy unit during Phase:Advance.
758// TODO: add fn which prohibts movements into enemy controlled hex.
759
760// SL5.70-Carrying support weapons and portage costs.
761//
762
763// SL5.73 Regardless of terrain and/or weapons portage, a squad or crew may
764// always carry up to 5 (TODO: turn into global CONST?) portage points, and
765// a leader carry 3 portage points, up to 1 hex during the `Phase::Advance`.
766fn carry_during_advance_phase(c: impl Carrier, p: Phase) {
767 todo!()
768 /*
769 if c::Squad && u.portage < 6 && p == Phase::Advance {
770 // TODO: instruct BattleManager / SceneManger to move unit one hex.
771 } else if u::Leader && u.portage < 4 && p::Advance {
772 // TODO: instruct BattleManager / SceneManger to move unit one hex.
773 } else {
774 // TODO: Improve mechanism for Informing General.
775 println!("Unit is too encoumbered to move.")
776 }
777 */
778}
779
780// SL5.74 A `Squad` carrying 4 or more portage points, or a `Leader` carrying 2
781// or more portage points (TODO: turn into global CONST), during the
782// `Phase::Movement` may _not_ fire a `SupportWeapon` during the _ensuing_
783// `Phase::AdvancedFire`.
784fn may_fire_support_weapon(u: Unit, s: SupportWeapon, p: Phase) {
785 todo!()
786 /*
787 if p::AdvanceFire && (u::Leader.portage < 2 || u::Squad.portage < 4) {
788 // May fire support weapon.
789 // TODO: issue to handler responsible for the firing of weapons.
790 } else {
791 // TODO: add "Inform General" functionality to inform when commands are
792 // break a rule. Also log to BattleManager?
793 //
794 println!("Unit {:?} may not fire the support weapon {:?} because the unit
795 exceeded the allowed portage points during the movement phase {:?}.", u, s, p);
796 }
797 */
798}
799
800// Handled by A1.2.
801// TODO: move this to fire section.
802// SL5.75 An infantry unit may only fire one _type_ of `SupportWeapon` in the
803// same Phase::FirePhase, Phase::PrepFire, or Phase::AdvanceFire.
804// WeaponType::{LMG, MMG, HMG} are all considred weapons of the same _type_.
805// TODO: enforce this how? Perhaps by each unit tracking which weapon type
806// it has fired in the same fire phase:
807//
808// ```
809// Unit.fire(w: WeaponType) {
810// if !Unit.weapon_types_fired.contains(w) {
811// w.fire();
812// Unit.weapon_types_fired.insert(w)
813// }
814// }
815// ```
816// ...or let a battlemanager track and decide if a unit is allowed to fire
817// a particular type of support weapon.
818
819// SL5.76 Portage costs are assumed to refer to `Squad` usage costs.
820// TODO: Consider how `Leader` should be handled with relation to portage cost.
821
822////////////////////////////////////////////////////////////////////////////////
823/// SL6. Stacking
824
825////////////////////////////////////////////////////////////////////////////////
826/// SL7. Line of Sight (LOS)
827// SL7.1 Line of sight - what a unit can "see" and fire upon, is measured from
828// the center dot of the hex (of the unit) and a straight line to the center dot
829// of the target hex.
830// If an obstacle on the map can be observed on both sides of this line then the
831// LOS is obstructed.
832fn los_obstructed(orig: Hex, dest: Hex) -> bool {
833 // TODO: setup a database to hold all hex data, including LOS
834 todo!("Look-table or database hit to get LOS for passed hexes.")
835}
836
837// SL7.2 LOS is only blocked if something in the hex is actually blocking it.
838// It's not enough that there's woods or a build in the hex, the LOS actually
839// has to be blocked.
840// TODO: fn to verify LOS obstructed by gfx in hex.
841// Weapon's fire may be traced through a hex containg units without affecting
842// them, if the firer prefers this.
843// TODO: option to fire through without affecting unit blocking LOs.
844// TODO: Exception: 17.6
845
846// SL7.3 LOS extends into woods and buildings but not through them.
847// TODO: verify that LOS is obstructed by second woods/building hex so that
848// fire is not able to pass through woods/buildings.
849
850// SL7.4
851
852////////////////////////////////////////////////////////////////////////////////
853// SL8
854// SL15 Leadership
855// SL16 Defensive Fire Principles
856// SL17 Machine Guns
857// SL18 Fate
858// SL19 Movement and Fire
859// SL20 Close Combat
860
861////////////////////////////////////////////////////////////////////////////////
862/// Tests
863#[cfg(test)]
864mod tests {
865 #[test]
866 fn this_is_a_test() {
867 let res = 2 + 2;
868 assert_eq!(res, 4);
869 }
870}