rust_warrior/
engine.rs

1//! The game engine
2//!
3//! This module was formerly the home of a [specs][specs] implementation but is
4//! now home to a zero-dependency version of the engine that functions almost
5//! identically. It ended up being a little more straightforward when entities
6//! did not need to be queried.
7//!
8//! When `start` is called, a mutable `World` instance is created. This keeps
9//! track of things like the warrior's health and position, plus all enemy
10//! units' health and position as well.
11//!
12//! Within the game's loop, mutable references to the `World` are handed to
13//! various systems that live in the `systems` module and define a portion
14//! of the game's logic.
15//!
16//! [specs]: https://github.com/slide-rs/specs
17
18use std::{env, thread, time};
19
20use crate::{floor::Floor, unit::UnitType, Player};
21
22#[cfg(feature = "ncurses")]
23pub mod curses;
24pub mod systems;
25pub mod world;
26
27use systems::{player_system, shooter_system, sludge_system, ui_system};
28use world::World;
29
30const DEFAULT_GAME_LOOP_DELAY: u64 = 1000;
31
32/// The entry point for the engine, called by [`Game`](crate::game::Game)
33pub fn start(
34    player_name: String,
35    warrior_level: usize,
36    floor: Floor,
37    player_generator: fn() -> Box<dyn Player + Send + Sync>,
38) -> Result<(), String> {
39    let player = player_generator();
40
41    #[cfg(feature = "ncurses")]
42    let mut c = curses::Curses::new();
43
44    #[cfg(not(feature = "ncurses"))]
45    println!("{}", floor.draw());
46
47    let mut step = 0;
48
49    let mut warrior = None;
50    let mut other_units = Vec::new();
51    for unit in &floor.units {
52        match unit.unit_type {
53            UnitType::Warrior => {
54                warrior = Some(unit.clone());
55            }
56            _ => {
57                other_units.push(unit.clone());
58            }
59        }
60    }
61    let warrior = warrior.unwrap();
62
63    let mut world = World::new(
64        player_name,
65        warrior_level,
66        floor,
67        player,
68        warrior,
69        other_units,
70    );
71
72    let override_delay = env::var("GAME_LOOP_DELAY")
73        .ok()
74        .and_then(|s| s.parse::<u64>().ok());
75
76    loop {
77        step += 1;
78
79        if step > 100 {
80            return Err(format!(
81                "{} seems to have gotten lost...",
82                &world.player_name
83            ));
84        }
85
86        let (current, _) = world.warrior.hp;
87        if current == 0 {
88            return Err(format!("{} died!", &world.player_name));
89        }
90        if world.warrior.position == world.floor.stairs {
91            return Ok(());
92        }
93
94        let mut events = Vec::new();
95
96        let mut player_events = player_system(&mut world);
97        events.append(&mut player_events);
98
99        let mut sludge_events = sludge_system(&mut world);
100        events.append(&mut sludge_events);
101
102        let mut shooter_events = shooter_system(&mut world);
103        events.append(&mut shooter_events);
104
105        let num_events = events.len() as u64;
106
107        #[cfg(feature = "ncurses")]
108        ui_system(&world, events, &mut c);
109
110        #[cfg(not(feature = "ncurses"))]
111        ui_system(&world, events);
112
113        let delay = match override_delay {
114            Some(delay) => delay,
115            None => DEFAULT_GAME_LOOP_DELAY + num_events * 200,
116        };
117        thread::sleep(time::Duration::from_millis(delay));
118    }
119}