lifegame 0.3.19

A simple implementation of the classic cellular automaton, Conway's Game of Life.
use crossterm::event::{Event, KeyCode, KeyModifiers, read};
use eoe::QuitOnError;
use std::sync::atomic::{AtomicBool, AtomicI8, Ordering::Relaxed};
use std::sync::{Condvar, Mutex, MutexGuard, Once};
use std::thread;

pub static LISTENER: Listener = Listener::new();

pub static TIME_SCALE: TimeScale = TimeScale::new();
pub static PAUSE: Pause = Pause::new();
pub static FLIP: Flip = Flip::new();
pub static RESET: Reset = Reset::new();
pub static QUIT: Quit = Quit::new();

#[derive(Debug)]
pub struct Listener(Once);

impl Listener {
    const fn new() -> Self {
        Self(Once::new())
    }

    pub fn setup(&self) {
        let handler = || {
            loop {
                let event = read().quit_on_error();

                let Event::Key(key_event) = event else {
                    continue;
                };
                if !key_event.is_press() {
                    continue;
                }
                let KeyCode::Char(key) = key_event.code else {
                    continue;
                };

                match key.to_ascii_lowercase() {
                    'j' => {
                        TIME_SCALE.increment();
                    }
                    'k' => {
                        TIME_SCALE.decrement();
                    }
                    'p' => {
                        PAUSE.toggle();
                    }
                    'f' => {
                        FLIP.set();
                    }
                    'r' => {
                        RESET.set();
                    }
                    'q' => {
                        PAUSE.unset();
                        QUIT.set();
                        break;
                    }
                    'c' if key_event.modifiers == KeyModifiers::CONTROL => {
                        PAUSE.unset();
                        QUIT.set();
                        break;
                    }
                    _ => (),
                }
            }
        };

        self.0.call_once(|| {
            thread::spawn(handler);
        });
    }
}

#[derive(Debug)]
pub struct TimeScale {
    exponent: AtomicI8,
}

impl TimeScale {
    pub const MAX_EXPONENT: i8 = 10;
    pub const MIN_EXPONENT: i8 = -10;

    const fn new() -> Self {
        let exponent = AtomicI8::new(0);
        Self { exponent }
    }

    fn increment(&self) {
        let _ = self.exponent.fetch_update(Relaxed, Relaxed, |exponent| {
            if exponent < Self::MAX_EXPONENT {
                Some(exponent + 1)
            } else {
                None
            }
        });
    }

    fn decrement(&self) {
        let _ = self.exponent.fetch_update(Relaxed, Relaxed, |exponent| {
            if exponent > Self::MIN_EXPONENT {
                Some(exponent - 1)
            } else {
                None
            }
        });
    }

    pub fn scale(&self) -> f64 {
        let exponent = self.exponent.load(Relaxed) as f64;
        exponent.exp2()
    }
}

#[derive(Debug)]
pub struct Pause {
    state: Mutex<bool>,
    cvar: Condvar,
}

impl Pause {
    const fn new() -> Self {
        let state = Mutex::new(false);
        let cvar = Condvar::new();
        Self { state, cvar }
    }

    fn state(&self) -> MutexGuard<'_, bool> {
        match self.state.lock() {
            Err(_) => unreachable!(),
            Ok(guard) => guard,
        }
    }

    fn toggle(&self) {
        let mut state = self.state();
        if *state {
            *state = false;
            self.cvar.notify_all();
        } else {
            *state = true;
        }
    }

    fn unset(&self) {
        *self.state() = false;
        self.cvar.notify_all();
    }

    pub fn wait_if_paused(&self) {
        let mut state = self.state();
        while *state {
            state = match self.cvar.wait(state) {
                Err(_) => unreachable!(),
                Ok(guard) => guard,
            };
        }
    }
}

#[derive(Debug)]
pub struct Flip {
    state: AtomicBool,
}

impl Flip {
    const fn new() -> Self {
        let state = AtomicBool::new(false);
        Self { state }
    }

    fn set(&self) {
        self.state.store(true, Relaxed);
    }

    pub fn take(&self) -> bool {
        self.state.swap(false, Relaxed)
    }
}

#[derive(Debug)]
pub struct Reset {
    state: AtomicBool,
}

impl Reset {
    const fn new() -> Self {
        let state = AtomicBool::new(false);
        Self { state }
    }

    fn set(&self) {
        self.state.store(true, Relaxed);
    }

    pub fn take(&self) -> bool {
        self.state.swap(false, Relaxed)
    }
}

#[derive(Debug)]
pub struct Quit {
    state: AtomicBool,
}

impl Quit {
    const fn new() -> Self {
        let state = AtomicBool::new(false);
        Self { state }
    }

    fn set(&self) {
        self.state.store(true, Relaxed);
    }

    pub fn get(&self) -> bool {
        self.state.load(Relaxed)
    }
}