coolcaves 1.0.0

A small terminal app to generate some cool caves!
use core::f32;
use germterm::{
    color::{Color, ColorRgb}, crossterm::{self, event::{Event, KeyCode, KeyEvent, KeyEventKind}}, draw::{Layer, draw_rect, draw_text, draw_twoxel}, engine::{Engine, end_frame, exit_cleanup, init, start_frame}, fps_counter::get_fps, input::poll_input
};

fn update_grid(grid: &mut Vec<Vec<bool>>, w: usize, h: usize, b_thresh: u8, s_thresh: u8) {
    let mut next_grid = vec![vec![true; w]; h];

    for x in 1..w-1 {
        for y in 1..h-1 {
            let mut neighbours: u8 = 0;
            for dy in -1..=1 {
                for dx in -1..=1 {
                    if dx == 0 && dy == 0 {
                        continue;
                    }

                    let nx = x as isize + dx;
                    let ny = y as isize + dy;

                    if nx >= 0 && ny >= 0 && nx < w as isize && ny < h as isize {
                        if grid[ny as usize][nx as usize] {
                            neighbours += 1;
                        }
                    }
                }
            }

            if grid[y][x] && neighbours >= s_thresh {
                next_grid[y][x] = true;
            } else if !grid[y][x] && neighbours >= b_thresh {
                next_grid[y][x] = true;
            } else {
                next_grid[y][x] = false;
            }
        }
    }

    *grid = next_grid;
}

fn generate_map(wall_density: f32, w: usize, h: usize) -> Vec<Vec<bool>> {
   (0..h)
        .map(|_| (0..w).map(|_| rand::random::<f32>() < wall_density).collect())
        .collect()
}

fn main() {
    let (term_w, term_h) = crossterm::terminal::size().unwrap();
    let w = term_w as usize;
    let h = term_h as usize * 2 - 2;
    let wall_color = Color::from(ColorRgb::new(131, 87, 31));
    let bg_color = Color::from(ColorRgb::new(178, 123, 31));
    let mut ticks: u64 = 0;
    let mut wall_density = 0.50;
    let mut thresh = 4;

    let mut grid: Vec<Vec<bool>> = generate_map(wall_density, w, h);

    let mut engine = Engine::new(term_w, term_h).limit_fps(0);
    let mut layer = Layer::new(&mut engine, 0);

    let _ = init(&mut engine);

    'update_loop: loop {
        start_frame(&mut engine);

        ticks += 1;

        for event in poll_input() {
            if let Event::Key(KeyEvent { code, kind, .. }) = event {
                if kind != KeyEventKind::Press { continue; } 
                match code {
                    KeyCode::Char('q') => break 'update_loop,
                    KeyCode::Char('r') => {
                        ticks = 0;
                        grid = generate_map(wall_density, w, h);
                    }

                    KeyCode::Char('e') => {
                        let new_density = (wall_density + 0.01).min(1.0);
                        if new_density != wall_density {
                            wall_density = new_density;
                            ticks = 0;
                            grid = generate_map(wall_density, w, h);
                        }
                    }
                    KeyCode::Char('d') => {
                        let new_density = (wall_density - 0.01).max(0.0);
                        if new_density != wall_density {
                            wall_density = new_density;
                            ticks = 0;
                            grid = generate_map(wall_density, w, h);
                        }
                    }

                    KeyCode::Char('s') => {
                        let new_thresh = (thresh + 1).min(8);
                        if new_thresh != thresh {
                            thresh = new_thresh;
                            ticks = 0;
                            grid = generate_map(wall_density, w, h);
                        }
                    }
                    KeyCode::Char('w') => {
                        let new_thresh = (thresh as i32 - 1).max(0) as u8;
                        if new_thresh != thresh {
                            thresh = new_thresh;
                            ticks = 0;
                            grid = generate_map(wall_density, w, h);
                        }
                    }
                    _ => {}
                }
            }
        }

        for x in 0..w {
            for y in 0..h {
                let cell = grid[y][x];
                let color = if cell { wall_color } else { bg_color };
                draw_twoxel(&mut layer, x as f32, y as f32 / 2.0, color);

            }
        }

        draw_rect(&mut layer, 0, (term_h - 2) as i16, term_w as i16, 2, Color::BLACK);

        let s_thresh = thresh.min(4).max(0);
        let b_thresh = thresh.min(8).max(5);

        let status_text = format!("Progressive wall density: {} (0-8) | Init wall density: {:.2} | Ticks: {} ({:.0} t/s)", 8 - thresh as u8, wall_density, ticks, get_fps(&engine));
        let x = ((term_w as usize).saturating_sub(status_text.chars().count())) / 2;
        draw_text(&mut layer, x as i16, (term_h - 2) as i16, status_text);

        let controls = "W/S: Progressive wall density | E/D: Init wall density | R: Reset | Q: Quit";
        let x_controls = ((term_w as usize).saturating_sub(controls.chars().count())) / 2;
        draw_text(&mut layer, x_controls as i16, (term_h - 1) as i16, controls);

        update_grid(&mut grid, w, h, b_thresh, s_thresh);

        let _ = end_frame(&mut engine);
    }

    // Restore terminal before exiting
    let _ = exit_cleanup(&mut engine);
}