pix-engine 0.7.0

A cross-platform graphics/UI engine framework for simple games, visualizations, and graphics demos.
Documentation
use astar_solver::AStarSolver;
use maze::Maze;
use maze_creator::MazeCreator;
use pix_engine::prelude::*;
use timer::Timer;

mod astar_solver;
mod cell;
mod maze;
mod maze_creator;
mod timer;

const WIDTH: u32 = 801;
const HEIGHT: u32 = 601;
const SIZE: u32 = 20;
const FOOTER: u32 = 50;
const COLS: u32 = WIDTH / SIZE;
const ROWS: u32 = (HEIGHT - FOOTER) / SIZE;

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum Algorithm {
    AStar,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum MazeMode {
    Idle,
    Creating,
    Unsolved,
    Solving(Algorithm),
    Solved,
}

#[derive(Debug, Clone)]
struct MazeApp {
    mode: MazeMode,
    cols: u32,
    rows: u32,
    maze: Maze,
    creator: MazeCreator,
    solver: AStarSolver,
    timer: Timer,
}

impl MazeApp {
    fn new(cols: u32, rows: u32) -> Self {
        let maze = Maze::new(cols, rows);
        let creator = MazeCreator::new(&maze);
        let solver = AStarSolver::new(&maze);
        Self {
            mode: MazeMode::Idle,
            cols,
            rows,
            maze,
            creator,
            solver,
            timer: Timer::new(),
        }
    }

    fn start_create_maze(&mut self) {
        self.mode = MazeMode::Creating;
        self.maze = Maze::new(self.cols, self.rows);
        self.creator = MazeCreator::new(&self.maze);
        self.timer.start();
    }

    fn create_maze(&mut self) -> PixResult<()> {
        self.maze = Maze::new(self.cols, self.rows);
        self.creator = MazeCreator::new(&self.maze);
        self.timer.start();
        while !self.creator.completed() {
            self.step_create_maze()?;
        }
        Ok(())
    }

    fn step_create_maze(&mut self) -> PixResult<()> {
        self.creator.step(&mut self.maze)?;
        if self.creator.completed() {
            self.timer.stop();
            self.mode = MazeMode::Unsolved;
        }
        Ok(())
    }

    fn start_solve_maze(&mut self, algorithm: Algorithm) -> PixResult<()> {
        if let MazeMode::Idle | MazeMode::Creating = self.mode {
            self.create_maze()?;
        }
        self.mode = MazeMode::Solving(algorithm);
        self.solver = AStarSolver::new(&self.maze);
        self.timer.start();
        Ok(())
    }

    fn solve_maze(&mut self) -> PixResult<()> {
        if let MazeMode::Idle | MazeMode::Creating = self.mode {
            self.create_maze()?;
        }
        self.solver = AStarSolver::new(&self.maze);
        self.timer.start();
        while !self.solver.completed() {
            self.step_solve_astar();
        }
        Ok(())
    }

    fn step_solve_astar(&mut self) {
        self.solver.step(&self.maze);
        if self.solver.completed() {
            self.timer.stop();
            self.mode = MazeMode::Solved;
        }
    }

    fn draw(&mut self, s: &mut PixState) -> PixResult<()> {
        match self.mode {
            MazeMode::Idle => self.maze.draw(s)?,
            MazeMode::Creating | MazeMode::Unsolved => {
                self.creator.draw(s, &self.maze)?;
            }
            MazeMode::Solving(_) | MazeMode::Solved => {
                self.solver.draw(s, &self.maze)?;
            }
        }
        let h = HEIGHT as i32;
        s.set_cursor_pos([10, h - 45]);
        s.fill(Color::WHITE);
        s.stroke(None);
        if s.button("Create")? {
            self.start_create_maze();
        }
        s.same_line(None);
        if s.button(">>##1")? {
            self.create_maze()?;
        }
        s.same_line(None);
        if s.button("Solve A*")? {
            self.start_solve_maze(Algorithm::AStar)?;
        }
        s.same_line(None);
        if s.button(">>##2")? {
            self.solve_maze()?;
        }
        s.push();
        s.fill(s.theme().colors.secondary);
        let rate = s.target_frame_rate().unwrap_or(60);
        s.same_line([10, -5]);
        s.text(format!(
            "Target FPS: {}\nElapsed: {:.3}",
            rate,
            self.timer.elapsed()
        ))?;
        s.pop();
        s.set_cursor_pos([s.width()? as i32 - 30, s.height()? as i32 - 40]);
        s.help_marker(
            "V: Toggle VSync\n\
            Up Arrow: Increase target frame rate.\n\
            Down Arrow: Decrease target frame rate.",
        )?;
        Ok(())
    }
}

impl PixEngine for MazeApp {
    fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
        s.clear()?;

        self.draw(s)?;
        match self.mode {
            MazeMode::Creating => self.step_create_maze()?,
            MazeMode::Solving(Algorithm::AStar) => self.step_solve_astar(),
            _ => (),
        }
        Ok(())
    }

    fn on_key_pressed(&mut self, s: &mut PixState, event: KeyEvent) -> PixResult<bool> {
        let frame_rate = s.target_frame_rate().unwrap_or(60);
        match event.key {
            Key::Up if frame_rate >= 60 => {
                s.frame_rate(None);
            }
            Key::Up if frame_rate < 60 => {
                s.frame_rate(frame_rate + 10);
            }
            Key::Down if frame_rate > 10 => {
                s.frame_rate(frame_rate - 10);
            }
            _ => (),
        }
        Ok(false)
    }
}

pub fn main() -> PixResult<()> {
    let mut engine = Engine::builder()
        .dimensions(WIDTH, HEIGHT)
        .title("Maze Generation & A* Search")
        .show_frame_rate()
        .target_frame_rate(60)
        .build()?;
    let mut app = MazeApp::new(COLS, ROWS);
    engine.run(&mut app)
}