babalcore 0.5.1

Babal core logic library, low-level things which are game-engine agnostic.
Documentation
use std::collections::VecDeque;

use super::consts::*;
use super::level_query::*;
use super::row::*;
use super::row_generator::*;
use super::skill::*;
use super::slab_kind::*;

/// Contains the level definition, where are slabs, and of what type.
pub struct Level {
    seed: u64,
    rows: VecDeque<Row>,
    row0: isize,
    w: usize,
    skl: Skill,
    now_msec: i64,
}

impl Level {
    /// Create a new level, with a given size
    ///
    /// # Examples
    ///
    /// ```
    /// use babalcore::*;
    ///
    /// let _level = Level::new(0, Skill::Easy);
    /// ```
    pub fn new(seed: u64, skill: Skill) -> Level {
        let width = skill.width();
        let w = if width > WIDTH_MAX as usize {
            WIDTH_MAX
        } else {
            width
        };
        Level {
            seed,
            rows: VecDeque::with_capacity(2 * SLIDING_LENGTH_MAX),
            row0: 0,
            w,
            skl: skill,
            now_msec: 0,
        }
    }

    /// Push a row at the end of the level.
    ///
    /// # Examples
    ///
    /// ```
    /// use babalcore::*;
    ///
    /// let mut level = Level::new(0, Skill::Easy);
    /// level.push(Row::new(SlabDef::Floor));
    /// assert_eq!(1, level.len());
    /// ```
    pub fn push(&mut self, row: Row) {
        if self.rows.len() >= ABSOLUTE_LENGTH_MAX {
            return;
        }
        self.rows.push_back(row);
        while self.rows.len() > SLIDING_LENGTH_MAX {
            self.row0 += 1;
            self.rows.pop_front();
        }
    }

    /// Generate new rows and append them to the level.
    ///
    /// # Examples
    ///
    /// ```
    /// use babalcore::*;
    ///
    /// let mut level = Level::new(0, Skill::Easy);
    /// level.generate(2);
    /// assert_eq!(2, level.len());
    /// level.generate(3);
    /// assert_eq!(5, level.len());
    /// ```
    pub fn generate(&mut self, n: usize) {
        if self.rows.len() >= ABSOLUTE_LENGTH_MAX {
            return;
        }
        for _ in 0..n {
            let j = self.rows.len();
            let row = if j == 0 {
                generate_first_row(self.seed, self.w, self.skl)
            } else {
                generate_next_row(self.seed, j, &self.rows[j - 1], self.skl)
            };
            self.push(row);
        }
    }

    /// Get a level content, at a given time.
    ///
    /// # Examples
    ///
    /// ```
    /// use babalcore::*;
    ///
    /// let mut level = Level::new(0, Skill::Easy);
    /// level.generate(2);
    /// assert_eq!(SlabKind::Floor, level.slab_kind(5, 1, false));
    /// ```
    pub fn slab_kind(&self, col: isize, row: isize, boost: bool) -> SlabKind {
        let real_row = row + self.row0;
        if col < 0 || col >= WIDTH_MAX as isize {
            return SlabKind::Void;
        }
        if real_row < 0 || real_row >= self.len() as isize {
            return SlabKind::Void;
        }
        match self.rows[real_row as usize].slab_kind(col, self.now_msec) {
            SlabKind::Boost => {
                if boost {
                    SlabKind::Overdrive
                } else {
                    SlabKind::Boost
                }
            }
            v => v,
        }
    }

    pub fn set_now_msec(&mut self, now_msec: i64) {
        self.now_msec = now_msec;
    }

    pub fn set_now_sec(&mut self, now_sec: f64) {
        self.now_msec = (now_sec * 1000.0) as i64;
    }
}

impl LevelQuery for Level {
    /// Get the level width. It is fixed for a given game session.
    ///
    /// # Examples
    ///
    /// ```
    /// use babalcore::*;
    ///
    /// let level = Level::new(0, Skill::Easy);
    /// assert_eq!(9, level.width());
    /// ```
    fn width(&self) -> usize {
        self.w
    }

    /// Get the level length. Length typically increases as
    /// the ball rolls on. However it is capped at some point
    /// and history at the beginning of the level is removed.
    /// View this as a sliding window.
    ///
    /// # Examples
    ///
    /// ```
    /// use babalcore::*;
    ///
    /// let level = Level::new(0, Skill::Easy);
    /// assert_eq!(0, level.len());
    /// ```
    fn len(&self) -> usize {
        self.rows.len()
    }

    /// Get the level first row, the row with the lowest index.
    ///
    /// # Examples
    ///
    /// ```
    /// use babalcore::*;
    ///
    /// let level = Level::new(0, Skill::Easy);
    /// assert_eq!(0, level.first());
    /// ```
    fn first(&self) -> isize {
        self.row0
    }

    /// Get the level last row, more precisely its index plus one.
    /// So if last is equal to first, then len is 0 an there are no rows at all.
    ///
    /// # Examples
    ///
    /// ```
    /// use babalcore::*;
    ///
    /// let level = Level::new(0, Skill::Easy);
    /// assert_eq!(0, level.last());
    /// ```
    fn last(&self) -> isize {
        self.row0 + self.rows.len() as isize
    }

    /// Get a level content, returns a plain integer.
    ///
    /// # Examples
    ///
    /// ```
    /// use babalcore::*;
    ///
    /// let mut level = Level::new(0, Skill::Easy);
    /// level.generate(2);
    /// assert_eq!(0, level.item(5, 1, false));
    /// ```
    fn item(&self, col: isize, row: isize, boost: bool) -> i64 {
        self.slab_kind(col, self.row0 + row, boost).as_item()
    }

    /// Find a starting spot on a level.
    ///
    /// # Examples
    ///
    /// ```
    /// use babalcore::*;
    ///
    /// let mut level = Level::new(42, Skill::Easy);
    /// assert_eq!(None, level.find_start_spot(2, 1));
    /// level.generate(100);
    /// assert_eq!(Some((2,1)), level.find_start_spot(2, 1));
    /// ```
    fn find_start_spot(&self, col: isize, row: isize) -> Option<(isize, isize)> {
        let r = std::cmp::max(self.first(), std::cmp::min(self.last() - 1, row));
        let c = std::cmp::max(0, std::cmp::min(self.width() as isize - 1, col));

        // Check if the curent spot is OK, if yes, use this.
        if self.slab_kind(c, r, false) == SlabKind::Floor {
            return Some((c, r));
        }

        // Ok, it did not work out, find another one by walking
        // all the level down to first row.
        let mut r2 = r;
        let width = self.width() as isize;
        let mut d = 1;
        while r2 >= self.first() {
            let limit = if r2 == self.first() {
                // At the beginning of the level, try any slab
                width
            } else {
                // Try to stay in the same lane, as much as possible.
                std::cmp::min(d, width)
            };
            for c2 in 0..limit {
                let c3 = if c < self.width() as isize / 2 {
                    (c + c2) % width
                } else {
                    (width + c - c2) % width
                };
                if self.slab_kind(c3, r2, false) == SlabKind::Floor {
                    return Some((c3, r2));
                }
            }
            r2 -= 1;
            d += 1;
        }
        None
    }

    /// Get the level skill.
    ///
    /// # Examples
    ///
    /// ```
    /// use babalcore::*;
    ///
    /// let level = Level::new(42, Skill::Easy);
    /// assert_eq!(Skill::Easy, level.skill());
    /// ```
    fn skill(&self) -> Skill {
        self.skl
    }
}

impl std::fmt::Display for Level {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let str_list: Vec<String> = self
            .rows
            .iter()
            .map(|x| format!("{}", x)[0..self.w].to_string())
            .collect();
        write!(f, "{}", str_list.join("\n"))
    }
}

#[cfg(test)]
mod tests {
    use crate::*;
    use std::collections::HashMap;

    #[test]
    fn test_fmt() {
        assert_eq!("", format!("{}", Level::new(0, Skill::Easy)));
        let mut lvl = Level::new(0, Skill::Noob);
        lvl.generate(3);
        assert_eq!("########\n########\n########", format!("{}", lvl));
    }

    #[test]
    fn test_sliding_length_max() {
        let mut lvl = Level::new(0, Skill::Noob);
        assert_eq!(0, lvl.len());
        let row = Row::new(SlabDef::Floor);
        lvl.generate(SLIDING_LENGTH_MAX - 1);
        assert_eq!(SLIDING_LENGTH_MAX - 1, lvl.len());
        lvl.generate(2);
        assert_eq!(SLIDING_LENGTH_MAX, lvl.len());
        assert_eq!(1, lvl.first());
        assert_eq!((SLIDING_LENGTH_MAX + 1) as isize, lvl.last());
        lvl.push(row);
        assert_eq!(SLIDING_LENGTH_MAX, lvl.len());
        assert_eq!(2, lvl.first());
        assert_eq!((SLIDING_LENGTH_MAX + 2) as isize, lvl.last());
    }

    #[test]
    fn test_generate() {
        const L: usize = 3;
        let mut expected = HashMap::<u64, &str>::new();
        expected.insert(0, "### ######\n####### ##\n#^##### ##");
        expected.insert(123, "#  ## # ##\n^  ## #^##\n###   # ##");

        for (key, value) in expected.into_iter() {
            let mut lvl = Level::new(key, Skill::Medium);
            lvl.generate(L);
            assert_eq!(L, lvl.len());
            assert_eq!(value, format!("{}", lvl));
        }
    }
}