numples 1.2.0

Yet another colourful sudoku playing game.
Documentation
mod consts;
mod events;
mod fonts;
mod game;
mod gameover;
mod gameplay;
mod kennett;
mod load;
mod pause;
mod persistence;
mod states;
mod title;
mod tween;

use bevy::app::Plugin;
use bevy::input::keyboard::{Key, KeyboardInput};
use bevy::prelude::*;
use bevy::window::WindowResized;

use crate::consts::RESOLUTION;
use crate::events::NumplesEvent;
use crate::gameover::GameOverPlugin;
use crate::gameplay::GameplayPlugin;
use crate::load::{Ctrl, LoadPlugin};
use crate::pause::PausePlugin;
use crate::persistence::Persistence;
use crate::states::GameState;
use crate::title::TitleScenePlugin;
use crate::tween::TweenPlugin;

pub mod prelude {
    pub mod consts {
        pub use crate::consts::*;
    }
    pub use super::NumplesApp;
}

#[derive(Clone, Copy, Debug)]
pub struct NumplesApp;

impl Plugin for NumplesApp {
    fn build(&self, app: &mut App) {
        app
            .add_event::<NumplesEvent>()
            .init_state::<GameState>()
            .add_plugins(LoadPlugin)
            .add_plugins(TitleScenePlugin)
            .add_plugins(GameplayPlugin)
            .add_plugins(PausePlugin)
            .add_plugins(GameOverPlugin)
            .add_plugins(TweenPlugin)
            .add_systems(Startup, background_system)
            .add_systems(Startup, setup_camera)
            .add_systems(PreUpdate, resize_system)
            .add_systems(Update, Persistence::load_board)
            .add_systems(Update, exit_system)
        ;
    }
}

fn background_system(mut commands: Commands) {
    commands.insert_resource(ClearColor(crate::consts::BACKGROUND_COLOR));
}

fn resize_system(
    mut window: Single<&mut Window, Changed<Window>>,
    mut reader: EventReader<WindowResized>,
) {
    for event in reader.read() {
        let resolution = &mut window.resolution;
        let x = event.width / RESOLUTION.x;
        let y = event.height / RESOLUTION.y;
        let factor = x.min(y);
        resolution.set_scale_factor_override(Some(factor));

        if (x - y).abs() > 0.1 {
            let width = RESOLUTION.x * x;
            let height = RESOLUTION.y * y;
            resolution.set_physical_resolution(width as u32, height as u32);
        }
    }
}

fn exit_system(
    mut keyboard: EventReader<KeyboardInput>,
    mut exit: EventWriter<AppExit>,
    mut ctrl: ResMut<Ctrl>,
) {
    let mut key_q = false;
    for (input, _) in keyboard.par_read() {
        match (&input.logical_key, input.state.is_pressed()) {
            (Key::Control, pressed) => *ctrl = pressed.into(),

            (Key::Character(ch), pressed) if pressed && (ch == "q" || ch == "Q") =>
                    key_q = true,

            _ => (),
        }
    }
    if **ctrl && key_q {
        exit.write(AppExit::Success);
    }
}

fn setup_camera(mut commands: Commands) {
    commands.spawn(Camera2d);
}