]> git.seodisparate.com - LudumDare45_StartWithNothing/commitdiff
Move "original_impl" to own module
authorStephen Seo <seo.disparate@gmail.com>
Sun, 19 Feb 2023 06:25:27 +0000 (15:25 +0900)
committerStephen Seo <seo.disparate@gmail.com>
Sun, 19 Feb 2023 06:25:27 +0000 (15:25 +0900)
src/main.rs
src/original_impl.rs [new file with mode: 0644]

index 28f708bb280ab3d7161e40ec06775955663046b0..dd8a4ef4899614300cbe7f4ac795a047bc4d7bf3 100644 (file)
-use std::{path::PathBuf, str::FromStr};
-
-use agnostic_interface::CameraInterface;
-//use quicksilver::{
-//    geom::{Circle, Rectangle, Transform, Vector},
-//    graphics::{
-//        Background::{Blended, Col, Img},
-//        Color, Font, FontStyle, Image, View,
-//    },
-//    input::{ButtonState, Key},
-//    lifecycle::{run, Asset, Event, Settings, State, Window},
-//    saving::{load, save},
-//    sound::Sound,
-//    Result,
-//};
-use rand::prelude::*;
-use serde::{Deserialize, Serialize};
-
 mod agnostic_interface;
 mod faux_quicksilver;
-use faux_quicksilver::{Circle, Color, Rectangle, Transform, Vector, Window};
-
-const WIDTH_F: f32 = 800.0;
-const HEIGHT_F: f32 = 600.0;
-const MUSIC2_LENGTH: f32 = 2.0 * 60.0 * 1000.0;
-const TEXT_RATE: f32 = 100.0;
-const PP_GEN_RATE: f32 = 75.0;
-const PARTICLE_RAND_VEL_RANGE: f32 = 0.2;
-const PARTICLE_RAND_VEL_DIST: f32 = 0.2828427; // dist where x and y = 0.2
-const PARTICLE_RAND_ROT_RANGE: f32 = 0.5;
-const JOINING_OPACITY_RATE: f32 = 0.00013;
-const JOINING_FAR_DIST: f32 = 700.0;
-const JOINING_NEAR_DIST: f32 = 150.0;
-const DOUBLE_CLICK_TIME: f32 = 350.0;
-const SL_NOTIF_TIME: f32 = 5000.0;
-const MAX_MOONS: usize = 5;
-
-fn interp_sq_inv(x: f32) -> f32 {
-    if x < 0.0 {
-        return 0.0;
-    } else if x > 1.0 {
-        return 1.0;
-    }
-    let y = x - 1.0;
-    -y * y + 1.0
-}
-
-fn interp_sq(x: f32) -> f32 {
-    if x < 0.0 {
-        return 0.0;
-    } else if x > 1.0 {
-        return 1.0;
-    }
-    x * x
-}
-
-enum MenuItemType {
-    Button {
-        text: &'static str,
-        text_c: Color,
-        h_c: Color,
-        c: Color,
-    },
-    AppearingText {
-        text: &'static str,
-        text_idx: usize,
-        text_size: f32,
-        text_c: Color,
-        timer: f32,
-    },
-    InstantText {
-        text: &'static str,
-        text_size: f32,
-        text_color: Color,
-    },
-    Pause {
-        timer: f32,
-        length: f32,
-    },
-}
-
-struct MenuItem {
-    x: f32,
-    y: f32,
-    w: f32,
-    h: f32,
-    item_type: MenuItemType,
-    is_hover: bool,
-    is_focus: bool,
-    is_loaded: bool,
-}
-
-impl MenuItem {
-    fn is_inside(&self, x: f32, y: f32) -> bool {
-        x >= self.x && x < self.x + self.w && y >= self.y && y < self.y + self.h
-    }
-}
-
-struct Menu {
-    items: Vec<MenuItem>,
-}
-
-impl Menu {
-    fn button(
-        x: f32,
-        y: f32,
-        w: f32,
-        h: f32,
-        s: &'static str,
-        t_color: Color,
-        box_color: Color,
-        boxh_color: Color,
-        first: bool,
-    ) -> MenuItem {
-        MenuItem {
-            x,
-            y,
-            w,
-            h,
-            item_type: MenuItemType::Button {
-                text: s,
-                text_c: t_color,
-                h_c: boxh_color,
-                c: box_color,
-            },
-            is_hover: false,
-            is_focus: false,
-            is_loaded: !first,
-        }
-    }
-
-    fn start() -> Menu {
-        let item = MenuItem {
-            x: WIDTH_F / 2.0 - 120.0,
-            y: 150.0,
-            w: 240.0,
-            h: 150.0,
-            item_type: MenuItemType::Button {
-                text: "Start the Game",
-                text_c: Color::WHITE,
-                h_c: Color::from_rgba(0x66, 0xFF, 0xFF, 255),
-                c: Color::from_rgba(0x33, 0xDD, 0xDD, 255),
-            },
-            is_hover: false,
-            is_focus: false,
-            is_loaded: false,
-        };
-
-        Menu {
-            items: vec![
-                item,
-                Menu::instant_text(
-                    70.0,
-                    50.0,
-                    55.0,
-                    true,
-                    "One And All - A Ludum Dare 45 Entry",
-                ),
-                Menu::instant_text(
-                    25.0,
-                    HEIGHT_F - 100.0,
-                    30.0,
-                    true,
-                    "Made with quicksilver which is licensed with either",
-                ),
-                Menu::instant_text(
-                    70.0,
-                    HEIGHT_F - 80.0,
-                    30.0,
-                    true,
-                    "MIT License or Apache License Version 2.0",
-                ),
-                Menu::instant_text(
-                    25.0,
-                    HEIGHT_F - 50.0,
-                    30.0,
-                    true,
-                    "Uses Clear-Sans which is licensed with Apache License Version 2.0",
-                ),
-            ],
-        }
-    }
-
-    fn instant_text(x: f32, y: f32, text_size: f32, first: bool, s: &'static str) -> MenuItem {
-        MenuItem {
-            x,
-            y,
-            w: 0.0,
-            h: 0.0,
-            item_type: MenuItemType::InstantText {
-                text: s,
-                text_size,
-                text_color: Color::WHITE,
-            },
-            is_hover: false,
-            is_focus: false,
-            is_loaded: !first,
-        }
-    }
-
-    fn text(x: f32, y: f32, text_size: f32, first: bool, s: &'static str) -> MenuItem {
-        MenuItem {
-            x,
-            y,
-            w: 0.0,
-            h: 0.0,
-            item_type: MenuItemType::AppearingText {
-                text: s,
-                text_size,
-                text_c: Color::WHITE,
-                timer: 0.0,
-                text_idx: 0,
-            },
-            is_hover: false,
-            is_focus: false,
-            is_loaded: !first,
-        }
-    }
-
-    fn pause(length: f32, first: bool) -> MenuItem {
-        MenuItem {
-            x: 0.0,
-            y: 0.0,
-            w: 0.0,
-            h: 0.0,
-            item_type: MenuItemType::Pause { timer: 0.0, length },
-            is_hover: false,
-            is_focus: false,
-            is_loaded: !first,
-        }
-    }
-
-    fn s_01() -> Menu {
-        Menu {
-            items: vec![
-                Menu::pause(500.0, true),
-                Menu::text(50.0, HEIGHT_F - 140.0, 40.0, false, "This is how it is."),
-                Menu::pause(500.0, false),
-                Menu::text(
-                    50.0,
-                    HEIGHT_F - 100.0,
-                    40.0,
-                    false,
-                    "Nothing is, and everything is nothing.",
-                ),
-                Menu::pause(500.0, false),
-                Menu::text(50.0, HEIGHT_F - 60.0, 40.0, false, "...until you appeared."),
-                Menu::pause(100.0, false),
-                Menu::text(
-                    570.0,
-                    HEIGHT_F - 50.0,
-                    30.0,
-                    false,
-                    "(Click to continue...)",
-                ),
-            ],
-        }
-    }
-
-    fn s_02() -> Menu {
-        Menu {
-            items: vec![
-                Menu::text(
-                    50.0,
-                    HEIGHT_F - 150.0,
-                    40.0,
-                    true,
-                    "Just by being, you brought light into existence.",
-                ),
-                Menu::pause(500.0, false),
-                Menu::text(
-                    50.0,
-                    HEIGHT_F - 110.0,
-                    40.0,
-                    false,
-                    "What brings you here? What drives you?",
-                ),
-                Menu::pause(500.0, false),
-                Menu::text(
-                    50.0,
-                    HEIGHT_F - 70.0,
-                    40.0,
-                    false,
-                    "Please tell me, what fuels you?",
-                ),
-                Menu::button(
-                    100.0,
-                    30.0,
-                    200.0,
-                    85.0,
-                    "Hope",
-                    Color::WHITE,
-                    Color::BLACK,
-                    Color::from_rgba(0x33, 0x33, 0x33, 255),
-                    false,
-                ),
-                Menu::button(
-                    500.0,
-                    30.0,
-                    200.0,
-                    85.0,
-                    "Miracles",
-                    Color::WHITE,
-                    Color::BLACK,
-                    Color::from_rgba(0x33, 0x33, 0x33, 255),
-                    false,
-                ),
-                Menu::button(
-                    100.0,
-                    150.0,
-                    200.0,
-                    85.0,
-                    "Kindness",
-                    Color::WHITE,
-                    Color::BLACK,
-                    Color::from_rgba(0x33, 0x33, 0x33, 255),
-                    false,
-                ),
-                Menu::button(
-                    500.0,
-                    150.0,
-                    200.0,
-                    85.0,
-                    "Determination",
-                    Color::WHITE,
-                    Color::BLACK,
-                    Color::from_rgba(0x33, 0x33, 0x33, 255),
-                    false,
-                ),
-            ],
-        }
-    }
-
-    // choose hope
-    fn s_03() -> Menu {
-        Menu {
-            items: vec![
-                Menu::text(
-                    50.0,
-                    HEIGHT_F - 170.0,
-                    40.0,
-                    true,
-                    "Hope... hope that your actions will inspire others..",
-                ),
-                Menu::pause(500.0, false),
-                Menu::text(
-                    50.0,
-                    HEIGHT_F - 130.0,
-                    40.0,
-                    false,
-                    "Hope that a brighter future will come tomorrow...",
-                ),
-                Menu::pause(500.0, false),
-                Menu::text(
-                    50.0,
-                    HEIGHT_F - 90.0,
-                    40.0,
-                    false,
-                    ".. With your appearance, perhaps it shall...",
-                ),
-            ],
-        }
-    }
-
-    // choose miracles
-    fn s_04() -> Menu {
-        Menu {
-            items: vec![
-                Menu::text(
-                    30.0,
-                    HEIGHT_F - 170.0,
-                    40.0,
-                    true,
-                    "Miracles huh?.. I see, then your appearance is special.",
-                ),
-                Menu::pause(500.0, false),
-                Menu::text(
-                    30.0,
-                    HEIGHT_F - 130.0,
-                    40.0,
-                    false,
-                    "With your appearance, things may change for the better..",
-                ),
-                Menu::pause(500.0, false),
-                Menu::text(
-                    30.0,
-                    HEIGHT_F - 90.0,
-                    40.0,
-                    false,
-                    "Now I am certain that this meeting was not by chance.",
-                ),
-            ],
-        }
-    }
-
-    // choose kindness
-    fn s_05() -> Menu {
-        Menu {
-            items: vec![
-                Menu::text(
-                    50.0,
-                    HEIGHT_F - 170.0,
-                    40.0,
-                    true,
-                    "Kindness?.. I am in your debt.",
-                ),
-                Menu::pause(250.0, false),
-                Menu::text(
-                    50.0,
-                    HEIGHT_F - 130.0,
-                    40.0,
-                    false,
-                    "It has been a long time since I have encountered",
-                ),
-                Menu::text(50.0, HEIGHT_F - 90.0, 40.0, false, "another being..."),
-                Menu::pause(500.0, false),
-                Menu::text(270.0, HEIGHT_F - 90.0, 40.0, false, "... Thank you..."),
-            ],
-        }
-    }
-
-    // choose determination
-    fn s_06() -> Menu {
-        Menu {
-            items: vec![
-                Menu::text(
-                    50.0,
-                    HEIGHT_F - 170.0,
-                    40.0,
-                    true,
-                    "Determination.. I see...",
-                ),
-                Menu::pause(500.0, false),
-                Menu::text(
-                    400.0,
-                    HEIGHT_F - 170.0,
-                    40.0,
-                    false,
-                    "I do not doubt it, for it",
-                ),
-                Menu::text(
-                    50.0,
-                    HEIGHT_F - 130.0,
-                    40.0,
-                    false,
-                    "must have been difficult to come here..",
-                ),
-                Menu::pause(500.0, false),
-                Menu::text(
-                    50.0,
-                    HEIGHT_F - 90.0,
-                    40.0,
-                    false,
-                    "Your resolve is evident by your mere presence..",
-                ),
-            ],
-        }
-    }
-
-    fn s_07() -> Menu {
-        Menu {
-            items: vec![
-                Menu::text(
-                    50.0,
-                    HEIGHT_F - 130.0,
-                    40.0,
-                    true,
-                    "Now that you are here, it must mean a new era of",
-                ),
-                Menu::text(
-                    50.0,
-                    HEIGHT_F - 90.0,
-                    40.0,
-                    false,
-                    "creation for all that will be.",
-                ),
-                Menu::pause(200.0, false),
-                Menu::text(
-                    50.0,
-                    HEIGHT_F - 50.0,
-                    40.0,
-                    false,
-                    "Try double-clicking the void to create something...",
-                ),
-            ],
-        }
-    }
-
-    fn s_08() -> Menu {
-        Menu {
-            items: vec![Menu::instant_text(
-                50.0,
-                HEIGHT_F - 90.0,
-                35.0,
-                true,
-                "(Try double-clicking now...)",
-            )],
-        }
-    }
-
-    fn s_09() -> Menu {
-        Menu {
-            items: vec![
-                Menu::pause(400.0, true),
-                Menu::text(
-                    50.0,
-                    HEIGHT_F - 140.0,
-                    40.0,
-                    false,
-                    "A new planet... It has most certainly been a while.",
-                ),
-                Menu::pause(500.0, false),
-                Menu::text(
-                    50.0,
-                    HEIGHT_F - 100.0,
-                    40.0,
-                    false,
-                    "Please, go out and create the new universe, and again..",
-                ),
-                Menu::pause(300.0, false),
-                Menu::text(50.0, HEIGHT_F - 60.0, 40.0, false, "Thank you."),
-            ],
-        }
-    }
-
-    fn s_10() -> Menu {
-        Menu {
-            items: vec![
-                Menu::instant_text(
-                    20.0,
-                    HEIGHT_F - 40.0,
-                    20.0,
-                    true,
-                    "Single click to move, Double-click to create something",
-                ),
-                Menu::instant_text(
-                    20.0,
-                    HEIGHT_F - 20.0,
-                    20.0,
-                    true,
-                    "S - save; L - load (can load from the start); R - reset",
-                ),
-            ],
-        }
-    }
-}
-
-#[derive(Serialize, Deserialize, Clone)]
-struct Particle {
-    rect: Rectangle,
-    circle: Circle,
-    is_rect: bool,
-    velx: f32,
-    vely: f32,
-    velr: f32,
-    r: f32,
-    lifetime: f32,
-    life_timer: f32,
-}
-
-#[derive(Serialize, Deserialize, Clone)]
-struct ParticleSystem {
-    particles: Vec<Particle>,
-    spawn_timer: f32,
-    spawn_time: f32,
-    lifetime: f32,
-    host_rect: Rectangle,
-    host_circle: Circle,
-    is_rect: bool,
-    direction: Vector,
-    color: Color,
-    opacity: f32,
-    vel_multiplier: f32,
-}
-
-impl ParticleSystem {
-    fn new(
-        spawn_time: f32,
-        lifetime: f32,
-        host_rect: Rectangle,
-        host_circle: Circle,
-        is_rect: bool,
-        direction: Vector,
-        color: Color,
-        opacity: f32,
-        vel_multiplier: f32,
-    ) -> Self {
-        Self {
-            particles: Vec::new(),
-            spawn_timer: 0.0,
-            spawn_time,
-            lifetime,
-            host_rect,
-            host_circle,
-            is_rect,
-            direction,
-            color,
-            opacity,
-            vel_multiplier,
-        }
-    }
-
-    fn update(&mut self, dt: f32) {
-        for i in (0..self.particles.len()).rev() {
-            self.particles[i].life_timer += dt;
-            if self.particles[i].life_timer > self.particles[i].lifetime {
-                self.particles.swap_remove(i);
-            } else {
-                if self.is_rect {
-                    self.particles[i].rect.x += self.particles[i].velx * dt as f32;
-                    self.particles[i].rect.y += self.particles[i].vely * dt as f32;
-                    self.particles[i].r += self.particles[i].velr * dt as f32;
-                } else {
-                    self.particles[i].circle.x += self.particles[i].velx * dt as f32;
-                    self.particles[i].circle.y += self.particles[i].vely * dt as f32;
-                }
-            }
-        }
-
-        self.spawn_timer += dt;
-        if self.spawn_timer > self.spawn_time {
-            self.spawn_timer -= self.spawn_time;
-            self.particles.push(Particle {
-                rect: self.host_rect,
-                circle: self.host_circle,
-                is_rect: self.is_rect,
-                velx: (rand::thread_rng()
-                    .gen_range(-PARTICLE_RAND_VEL_RANGE, PARTICLE_RAND_VEL_RANGE)
-                    + self.direction.x)
-                    * self.vel_multiplier,
-                vely: (rand::thread_rng()
-                    .gen_range(-PARTICLE_RAND_VEL_RANGE, PARTICLE_RAND_VEL_RANGE)
-                    + self.direction.y)
-                    * self.vel_multiplier,
-                // velx: self.direction.x,
-                // vely: self.direction.y,
-                velr: rand::thread_rng()
-                    .gen_range(-PARTICLE_RAND_ROT_RANGE, PARTICLE_RAND_ROT_RANGE)
-                    * self.vel_multiplier,
-                r: rand::thread_rng().gen_range(0.0, 90.0),
-                lifetime: self.lifetime,
-                life_timer: 0.0,
-            });
-        }
-    }
-
-    fn draw(&mut self, window: &mut Window, transform: Transform) {
-        if self.opacity == 0.0 {
-            return;
-        }
-        for particle in &mut self.particles {
-            self.color.a = ((1.0 - (particle.life_timer / particle.lifetime) as f32)
-                * self.opacity
-                * 255.0) as u8;
-            if particle.is_rect {
-                let pre_transform =
-                    Transform::translate(-particle.rect.x / 2.0, -particle.rect.y / 2.0)
-                        * Transform::rotate(particle.r);
-                window
-                    .get_gi_mut()
-                    .draw_rect_transform(particle.rect, self.color, transform * pre_transform)
-                    .ok();
-            } else {
-                window
-                    .get_gi_mut()
-                    .draw_circle_transform(particle.circle, self.color, transform)
-                    .ok();
-            }
-        }
-    }
-
-    fn force_spawn(&mut self, count: usize) {
-        for i in 0..count {
-            self.particles.push(Particle {
-                rect: self.host_rect,
-                circle: self.host_circle,
-                is_rect: self.is_rect,
-                velx: (rand::thread_rng()
-                    .gen_range(-PARTICLE_RAND_VEL_RANGE, PARTICLE_RAND_VEL_RANGE)
-                    + self.direction.x)
-                    * self.vel_multiplier,
-                vely: (rand::thread_rng()
-                    .gen_range(-PARTICLE_RAND_VEL_RANGE, PARTICLE_RAND_VEL_RANGE)
-                    + self.direction.y)
-                    * self.vel_multiplier,
-                // velx: self.direction.x,
-                // vely: self.direction.y,
-                velr: rand::thread_rng()
-                    .gen_range(-PARTICLE_RAND_ROT_RANGE, PARTICLE_RAND_ROT_RANGE)
-                    * self.vel_multiplier,
-                r: rand::thread_rng().gen_range(0.0, 90.0),
-                lifetime: self.lifetime,
-                life_timer: 0.0,
-            });
-        }
-    }
-}
-
-#[derive(Serialize, Deserialize, Clone)]
-struct RotatingParticleSystem {
-    particle_system: ParticleSystem,
-    r: f32,
-    velr: f32,
-    offset: f32,
-}
-
-impl RotatingParticleSystem {
-    fn new(
-        spawn_time: f32,
-        lifetime: f32,
-        host_rect: Rectangle,
-        host_circle: Circle,
-        is_rect: bool,
-        direction: Vector,
-        color: Color,
-        opacity: f32,
-        rotation: f32,
-        velr: f32,
-        offset: f32,
-        vel_multiplier: f32,
-    ) -> Self {
-        RotatingParticleSystem {
-            particle_system: ParticleSystem::new(
-                spawn_time,
-                lifetime,
-                host_rect,
-                host_circle,
-                is_rect,
-                direction,
-                color,
-                opacity,
-                vel_multiplier,
-            ),
-            r: rotation,
-            velr,
-            offset,
-        }
-    }
-
-    fn update(&mut self, dt: f32) {
-        if self.particle_system.is_rect {
-            let saved_rect = self.particle_system.host_rect;
-            self.particle_system
-                .host_rect
-                .pos_add_vec(Transform::rotate(self.r) * Vector::new(self.offset, 0.0));
-            self.particle_system.update(dt);
-            self.particle_system.host_rect = saved_rect;
-        } else {
-            let saved_cir = self.particle_system.host_circle;
-            self.particle_system
-                .host_circle
-                .pos_add_vec(Transform::rotate(self.r) * Vector::new(self.offset, 0.0));
-            self.particle_system.update(dt);
-            self.particle_system.host_circle = saved_cir;
-        }
-        self.r += self.velr * dt as f32;
-    }
-
-    fn draw(&mut self, window: &mut Window, transform: Transform) {
-        if self.particle_system.opacity == 0.0 {
-            return;
-        }
-        self.particle_system.direction =
-            Transform::rotate(self.r) * Vector::new(0.0, -PARTICLE_RAND_VEL_DIST);
-        self.particle_system.draw(window, transform);
-        if self.particle_system.is_rect {
-            let mut moved_rect = self.particle_system.host_rect;
-            moved_rect.pos_add_vec(Transform::rotate(self.r) * Vector::new(self.offset, 0.0));
-            let mut solid_color = self.particle_system.color;
-            solid_color.a = (self.particle_system.opacity * 255.0) as u8;
-            window
-                .get_gi_mut()
-                .draw_rect_transform(
-                    moved_rect,
-                    solid_color,
-                    transform
-                        * Transform::translate(-moved_rect.x / 2.0, -moved_rect.y / 2.0)
-                        * Transform::rotate(self.r * 1.3),
-                )
-                .ok();
-        } else {
-            let mut moved_cir = self.particle_system.host_circle;
-            moved_cir.pos_add_vec(Transform::rotate(self.r) * Vector::new(self.offset, 0.0));
-            let mut solid_color = self.particle_system.color;
-            solid_color.a = (self.particle_system.opacity * 255.0) as u8;
-            window
-                .get_gi_mut()
-                .draw_circle_transform(moved_cir, solid_color, transform)
-                .ok();
-        }
-    }
-}
-
-struct ExplConvCircleParticle {
-    circle: Circle,
-    offset: f32,
-    r: f32,
-}
-
-struct ExplConvParticleSystem {
-    particles: Vec<ExplConvCircleParticle>,
-    lifetime: f32,
-    host_circle: Circle,
-    color: Color,
-    opacity: f32,
-    life_timer: f32,
-}
-
-impl ExplConvParticleSystem {
-    fn new(lifetime: f32, host_circle: Circle, color: Color, opacity: f32) -> Self {
-        ExplConvParticleSystem {
-            particles: Vec::new(),
-            lifetime,
-            host_circle,
-            color,
-            opacity,
-            life_timer: 0.0,
-        }
-    }
-
-    fn activate(&mut self, count: usize, offset: f32) {
-        self.life_timer = 0.0;
-        for i in 0..count {
-            self.particles.push(ExplConvCircleParticle {
-                circle: self.host_circle,
-                offset,
-                r: rand::thread_rng().gen_range(0.0, 360.0),
-            });
-        }
-    }
-
-    // returns true if finished
-    fn update(&mut self, dt: f32, planets: &mut Vec<Planet>) -> bool {
-        self.life_timer += dt;
-        if self.life_timer >= self.lifetime {
-            if !self.particles.is_empty() {
-                self.particles.clear();
-                planets.push(Planet::new(self.host_circle, self.color));
-                return true;
-            }
-            return false;
-        }
-
-        if self.life_timer < self.lifetime / 2.0 {
-            let amount = interp_sq_inv((self.life_timer / self.lifetime) as f32 * 2.0);
-            for particle in &mut self.particles {
-                let dir =
-                    Transform::rotate(particle.r) * Vector::new(particle.offset * amount, 0.0);
-                particle.circle.x = dir.x + self.host_circle.x;
-                particle.circle.y = dir.y + self.host_circle.y;
-            }
-        } else {
-            let amount = 1.0 - interp_sq(((self.life_timer / self.lifetime) as f32 - 0.5) * 2.0);
-            for particle in &mut self.particles {
-                let dir =
-                    Transform::rotate(particle.r) * Vector::new(particle.offset * amount, 0.0);
-                particle.circle.x = dir.x + self.host_circle.x;
-                particle.circle.y = dir.y + self.host_circle.y;
-            }
-        }
-        return false;
-    }
-
-    fn draw(&mut self, window: &mut Window, transform: Transform) {
-        if self.opacity == 0.0 {
-            return;
-        }
-        for particle in &mut self.particles {
-            self.color.a = (((self.life_timer / self.lifetime) as f32 / 2.0 + 0.5)
-                * self.opacity
-                * 255.0) as u8;
-            window
-                .get_gi_mut()
-                .draw_circle_transform(particle.circle, self.color, transform)
-                .ok();
-        }
-    }
-}
-
-#[derive(Serialize, Deserialize, Clone)]
-struct Planet {
-    circle: Circle,
-    color: Color,
-    particle_system: ParticleSystem,
-    moons: Vec<RotatingParticleSystem>,
-}
-
-impl Planet {
-    fn new(circle: Circle, color: Color) -> Self {
-        let mut smaller_circle = circle;
-        smaller_circle.r /= 4.0;
-        let mut planet = Planet {
-            circle,
-            color,
-            particle_system: ParticleSystem::new(
-                rand::thread_rng().gen_range(2000.0, 3800.0),
-                900.0,
-                Rectangle::new(0.0, 0.0, 1.0, 1.0),
-                circle,
-                false,
-                Vector::new(0.0, 0.0),
-                color,
-                1.0,
-                0.3,
-            ),
-            moons: Vec::with_capacity(MAX_MOONS),
-        };
-
-        let r: f32 = rand::thread_rng().gen_range(0.0, 360.0);
-        let clockwise = rand::thread_rng().gen_bool(0.5);
-        for i in 0..rand::thread_rng().gen_range(0, MAX_MOONS) {
-            planet.moons.push(RotatingParticleSystem::new(
-                rand::thread_rng().gen_range(1000.0, 2600.0),
-                600.0,
-                Rectangle::new(0.0, 0.0, 1.0, 1.0),
-                smaller_circle,
-                false,
-                Vector::new(0.0, 0.0),
-                color,
-                1.0,
-                r,
-                if clockwise {
-                    rand::thread_rng().gen_range(0.05, 0.15)
-                } else {
-                    rand::thread_rng().gen_range(-0.15, -0.05)
-                },
-                rand::thread_rng().gen_range(35.0, 200.0),
-                0.2,
-            ));
-        }
-
-        planet
-    }
-
-    fn update(&mut self, dt: f32) {
-        self.particle_system.host_circle.x = self.circle.x;
-        self.particle_system.host_circle.y = self.circle.y;
-        self.particle_system.update(dt);
-        for moon in &mut self.moons {
-            moon.particle_system.host_circle.x = self.circle.x;
-            moon.particle_system.host_circle.y = self.circle.y;
-            moon.update(dt);
-        }
-    }
-
-    fn draw(&mut self, window: &mut Window, transform: Transform) {
-        self.particle_system.draw(window, transform);
-        window
-            .get_gi_mut()
-            .draw_circle_transform(self.circle, self.color, transform)
-            .ok();
-        for moon in &mut self.moons {
-            moon.draw(window, transform);
-        }
-    }
-}
-
-#[derive(Clone, Serialize, Deserialize)]
-struct Star {
-    color: Color,
-    particle_system: ParticleSystem,
-    velr: f32,
-    r: f32,
-}
-
-impl Star {
-    fn new(circle: Circle, color: Color, velr: f32, r: f32) -> Self {
-        let mut star = Star {
-            color,
-            particle_system: ParticleSystem::new(
-                rand::thread_rng().gen_range(80.0, 200.0),
-                850.0,
-                Rectangle::new(0.0, 0.0, 1.0, 1.0),
-                circle,
-                false,
-                Vector::new(0.0, 0.0),
-                color,
-                1.0,
-                1.0,
-            ),
-            velr,
-            r,
-        };
-
-        if star.color.r < (0.75 * 255.0) as u8 {
-            star.color.r = (0.75 * 255.0) as u8;
-        }
-        if star.color.g < (0.75 * 255.0) as u8 {
-            star.color.g = (0.75 * 255.0) as u8;
-        }
-        if star.color.b < (0.75 * 255.0) as u8 {
-            star.color.b = (0.75 * 255.0) as u8;
-        }
-        star.particle_system
-            .force_spawn(rand::thread_rng().gen_range(20, 45));
-
-        star
-    }
-
-    fn update(&mut self, dt: f32) {
-        self.particle_system.update(dt);
-        self.r += self.velr * dt as f32;
-    }
-
-    fn draw(&mut self, image: &str, window: &mut Window, transform: Transform) {
-        self.particle_system.draw(window, transform);
-        let image = window.get_image_mut(image).expect("Should be loaded image");
-        let mut image_rect = image.get_wh_rect();
-        image_rect.x = self.particle_system.host_circle.x - image_rect.w / 2.0;
-        image_rect.y = self.particle_system.host_circle.y - image_rect.h / 2.0;
-        image
-            .draw_transform(
-                image_rect.x,
-                image_rect.y,
-                self.color,
-                transform * Transform::rotate(self.r),
-            )
-            .ok();
-    }
-}
-
-#[derive(Clone, Serialize, Deserialize)]
-struct Fish {
-    pos: Vector,
-    r: f32,
-    swim_time: f32,
-    swim_timer: f32,
-    swim_v: f32,
-    anim_timer: f32,
-    anim_time: f32,
-    color: Color,
-    body_rect: Rectangle,
-    tail_rect: Rectangle,
-}
-
-enum FishState {
-    Idle,
-    Swim,
-}
-
-impl Fish {
-    fn new(pos: Vector, r: f32, color: Color) -> Self {
-        let anim_timer = rand::thread_rng().gen_range(800.0, 1000.0);
-        Self {
-            pos,
-            r,
-            swim_time: 800.0,
-            swim_timer: 800.0,
-            swim_v: 0.2,
-            anim_timer,
-            anim_time: anim_timer,
-            color,
-            body_rect: Rectangle {
-                x: 0.0,
-                y: 0.0,
-                w: 32.0,
-                h: 16.0,
-            },
-            tail_rect: Rectangle {
-                x: 32.0,
-                y: 0.0,
-                w: 16.0,
-                h: 16.0,
-            },
-        }
-    }
-
-    fn set_next(&mut self, state: FishState) {
-        match state {
-            FishState::Idle => {
-                self.swim_time = rand::thread_rng().gen_range(1100.0, 2400.0);
-                self.swim_timer = self.swim_time;
-                self.anim_timer = 1600.0;
-                self.anim_time = 1600.0;
-                self.swim_v = 0.0;
-            }
-            FishState::Swim => {
-                self.swim_time = rand::thread_rng().gen_range(1400.0, 2300.0);
-                self.swim_timer = self.swim_time;
-                self.r = rand::thread_rng().gen_range(0.0, 360.0);
-                self.anim_timer = rand::thread_rng().gen_range(600.0, 1000.0);
-                self.anim_time = self.anim_timer;
-                self.swim_v = (self.anim_timer / 8000.0) as f32;
-            }
-        }
-    }
-
-    fn update(&mut self, dt: f32) {
-        self.swim_time -= dt;
-        if self.swim_time < 220.0 {
-            self.swim_v /= 1.1;
-        }
-        if self.swim_time <= 0.0 {
-            if rand::thread_rng().gen_bool(0.4) {
-                self.set_next(FishState::Idle);
-            } else {
-                self.set_next(FishState::Swim);
-            }
-        }
-
-        self.anim_timer -= dt;
-        if self.anim_timer <= 0.0 {
-            self.anim_timer = self.anim_time;
-        }
-
-        self.pos += Transform::rotate(self.r) * Vector::new(self.swim_v, 0.0) * dt as f32;
-    }
-
-    fn draw(&mut self, i_fish: &str, window: &mut Window, transform: Transform) {
-        let fish_img = window
-            .get_image_mut(i_fish)
-            .expect("\"fish\" Image should be loaded");
-        let anim_angle = ((self.anim_timer / self.anim_time) * std::f32::consts::PI * 2.0).sin();
-        let mut body_rect = self.body_rect;
-        body_rect.x = self.pos.x - body_rect.w / 2.0;
-        body_rect.y = self.pos.y - body_rect.h / 2.0;
-        let body_tr =
-            Transform::rotate(anim_angle as f32 * 30.0) * Transform::rotate(self.r + 180.0);
-        fish_img.draw_sub_transform(
-            self.body_rect,
-            body_rect.x,
-            body_rect.y,
-            self.color,
-            transform * body_tr,
-        );
-        let mut tail_rect = self.tail_rect;
-        tail_rect.x = self.pos.x - tail_rect.w / 2.0;
-        tail_rect.y = self.pos.y - tail_rect.h / 2.0;
-        let anim_angle = ((self.anim_timer / self.anim_time) * std::f32::consts::PI * 2.0
-            - std::f32::consts::PI / 3.0)
-            .sin();
-        let tail_tr = body_tr
-            * Transform::translate(body_rect.x / 1.5, 0.0)
-            * Transform::translate(-tail_rect.x / 2.0, 0.0)
-            * Transform::rotate(-anim_angle as f32 * 45.0)
-            * Transform::translate(tail_rect.x / 2.0, 0.0);
-        fish_img.draw_sub_transform(
-            self.tail_rect,
-            tail_rect.x,
-            tail_rect.y,
-            self.color,
-            transform * tail_tr,
-        );
-    }
-}
-
-#[derive(Serialize, Deserialize, Clone)]
-struct SaveData {
-    planets: Vec<Planet>,
-    stars: Vec<Star>,
-    fishes: Vec<Fish>,
-    player: Rectangle,
-    joining_particles: RotatingParticleSystem,
-}
-
-enum SaveLoadNotification {
-    Save { text: Option<String>, timer: f32 },
-    Load { text: Option<String>, timer: f32 },
-}
-
-struct GameState {
-    s_boom: String,
-    s_get: String,
-    s_power_up: String,
-    s_tap: String,
-    s_speak_m: String,
-    s_speak_f: String,
-    font: String,
-    music2: String,
-    i_star: String,
-    i_fish: String,
-    music_on: bool,
-    menu: Menu,
-    state: u32,
-    state_dirty: bool,
-    selection_mode: bool,
-    current_item: Option<usize>,
-    current_finished: bool,
-    player: Rectangle,
-    player_r: f32,
-    player_particles: ParticleSystem,
-    joining_particles: RotatingParticleSystem,
-    is_create_mode: bool,
-    click_release_time: f32,
-    dbl_click_timeout: Option<f32>,
-    click_time: Option<f32>,
-    click_pos: Vector,
-    mouse_pos: Vector,
-    expl_conv_p_systems: Vec<ExplConvParticleSystem>,
-    planets: Vec<Planet>,
-    stars: Vec<Star>,
-    fishes: Vec<Fish>,
-    camera: Box<dyn CameraInterface>,
-    move_to: Vector,
-    save_load_notification: Option<SaveLoadNotification>,
-}
-
-impl GameState {
-    fn new(window: &mut Window) -> Result<Self, String> {
-        let s_boom = String::from("boom.mp3");
-        window.load_sound(
-            &PathBuf::from_str("static/boom.mp3")
-                .map_err(|_| String::from("Failed to load \"static/boom.mp3\""))?,
-            s_boom.clone(),
-        )?;
-        let s_get = String::from("get.mp3");
-        window.load_sound(
-            &PathBuf::from_str("static/get.mp3")
-                .map_err(|_| String::from("Failed to load \"static/get.mp3\""))?,
-            s_get.clone(),
-        )?;
-        let s_power_up = String::from("power_up.mp3");
-        window.load_sound(
-            &PathBuf::from_str("static/power_up.mp3")
-                .map_err(|_| String::from("Failed to load \"static/power_up.mp3\""))?,
-            s_power_up.clone(),
-        )?;
-        let s_tap = String::from("tap.mp3");
-        window.load_sound(
-            &PathBuf::from_str("static/tap.mp3")
-                .map_err(|_| String::from("Failed to load \"static/tap.mp3\""))?,
-            s_tap.clone(),
-        )?;
-        let s_speak_m = String::from("speak_m.mp3");
-        window.load_sound(
-            &PathBuf::from_str("static/speak_m.mp3")
-                .map_err(|_| String::from("Failed to load \"static/speak_m.mp3\""))?,
-            s_speak_m.clone(),
-        )?;
-        let s_speak_f = String::from("speak_f.mp3");
-        window.load_sound(
-            &PathBuf::from_str("static/speak_f.mp3")
-                .map_err(|_| String::from("Failed to load \"static/speak_f.mp3\""))?,
-            s_speak_f.clone(),
-        )?;
-
-        let font = String::from("ClearSans-Regular.ttf");
-        window.load_font(
-            &PathBuf::from_str("static/ClearSans-Regular.ttf")
-                .map_err(|_| String::from("Failed to load \"static/ClearSans-Regular.ttf\""))?,
-            font.clone(),
-        )?;
-
-        let music2 = String::from("music2.mp3");
-        window.load_music(
-            &PathBuf::from_str("static/music2.mp3")
-                .map_err(|_| String::from("Failed to load \"static/music2.mp3\""))?,
-            music2.clone(),
-        )?;
-
-        let i_star = String::from("star.png");
-        window.load_image(
-            &PathBuf::from_str("static/star.png")
-                .map_err(|_| String::from("Failed to load \"static/star.png\""))?,
-            i_star.clone(),
-        )?;
-
-        let i_fish = String::from("fish.png");
-        window.load_image(
-            &PathBuf::from_str("static/fish.png")
-                .map_err(|_| String::from("Failed to load \"static/fish.png\""))?,
-            i_fish.clone(),
-        )?;
-
-        let mut camera = window.get_gi_mut().get_default_camera()?;
-        camera.set_view(Rectangle {
-            x: 0.0,
-            y: 0.0,
-            w: WIDTH_F,
-            h: HEIGHT_F,
-        });
-        Ok(Self {
-            s_boom,
-            s_get,
-            s_power_up,
-            s_tap,
-            s_speak_m,
-            s_speak_f,
-            font,
-            music2,
-            i_star,
-            i_fish,
-            music_on: false,
-            menu: Menu::start(),
-            state: 0,
-            state_dirty: false,
-            selection_mode: true,
-            current_item: None,
-            current_finished: true,
-            player: Rectangle::new(400.0, 300.0, 32.0, 32.0),
-            player_r: 0.0,
-            player_particles: ParticleSystem::new(
-                PP_GEN_RATE,
-                1000.0,
-                Rectangle::new(400.0, 300.0, 32.0, 32.0),
-                Circle::new(100.0, 100.0, 32.0),
-                true,
-                Vector::new(0.0, 0.0),
-                Color::WHITE,
-                0.0,
-                1.0,
-            ),
-            joining_particles: RotatingParticleSystem::new(
-                PP_GEN_RATE,
-                1000.0,
-                Rectangle::new(400.0, 300.0, 16.0, 16.0),
-                Circle::new(100.0, 100.0, 32.0),
-                true,
-                Vector::new(0.0, 0.0),
-                Color::GREEN,
-                0.0,
-                0.0,
-                0.1,
-                JOINING_FAR_DIST,
-                1.0,
-            ),
-            is_create_mode: false,
-            click_release_time: 0.0,
-            dbl_click_timeout: None,
-            click_time: None,
-            click_pos: Vector::new(0.0, 0.0),
-            mouse_pos: Vector::new(0.0, 0.0),
-            expl_conv_p_systems: Vec::new(),
-            planets: Vec::new(),
-            stars: Vec::new(),
-            fishes: Vec::new(),
-            camera,
-            move_to: Vector::new(400.0, 300.0),
-            save_load_notification: None,
-        })
-    }
-
-    fn update(&mut self, window: &mut Window) -> Result<(), String> {
-        let dt = window.get_gi().get_delta_time();
-
-        // check mouse pos
-        {
-            self.mouse_pos = window.get_gi().get_mouse_xy_vec()?;
-            let mut hovered = false;
-            for i in 0..self.menu.items.len() {
-                if self.menu.items[i].is_inside(self.mouse_pos.x, self.mouse_pos.y) {
-                    self.menu.items[i].is_hover = true;
-                    self.current_item = Some(i);
-                    hovered = true;
-                } else {
-                    self.menu.items[i].is_hover = false;
-                }
-            }
-            if !hovered {
-                self.current_item = None;
-            }
-        }
-
-        // check mouse down
-        if window.get_gi_mut().get_mouse_down()?.is_none() {
-            if self.dbl_click_timeout.is_none() {
-                self.click_release_time = 0.0;
-            }
-        } else {
-            if self.current_finished {
-                if self.is_create_mode {
-                    if self.click_release_time < DOUBLE_CLICK_TIME {
-                        self.click_release_time = DOUBLE_CLICK_TIME;
-                        self.dbl_click_timeout = Some(0.0);
-                        self.click_time = None;
-                        if self.state == 8 {
-                            let mut expl_conv_system = ExplConvParticleSystem::new(
-                                1500.0,
-                                Circle::new(self.mouse_pos.x, self.mouse_pos.y, 20.0),
-                                Color::from_rgba(0x99, 0xFF, 0x99, 255),
-                                1.0,
-                            );
-                            expl_conv_system.activate(30, 200.0);
-                            self.expl_conv_p_systems.push(expl_conv_system);
-                            self.state = 9;
-                            self.state_dirty = true;
-                            window.get_sound_mut(&self.s_boom)?.play(0.8)?;
-                        } else if self.state == 10 {
-                            let mut rng = rand::thread_rng();
-                            let rand_out = rng.gen_range(0.0, 1.0);
-                            if rand_out < 0.6 {
-                                // spawn planet
-                                let mut expl_conv_system = ExplConvParticleSystem::new(
-                                    rng.gen_range(1200.0, 1600.0),
-                                    Circle::new(
-                                        self.mouse_pos.x,
-                                        self.mouse_pos.y,
-                                        rng.gen_range(15.0, 25.0),
-                                    ),
-                                    Color::from_rgba(
-                                        rng.gen_range(0x44, 0xFF),
-                                        rng.gen_range(0x44, 0xFF),
-                                        rng.gen_range(0x44, 0xFF),
-                                        255,
-                                    ),
-                                    1.0,
-                                );
-                                expl_conv_system
-                                    .activate(rng.gen_range(13, 40), rng.gen_range(150.0, 300.0));
-                                self.expl_conv_p_systems.push(expl_conv_system);
-                            } else if rand_out < 0.85 {
-                                // spawn star
-                                let rot_clockwise = rng.gen_bool(0.5);
-                                self.stars.push(Star::new(
-                                    Circle::new(
-                                        self.mouse_pos.x,
-                                        self.mouse_pos.y,
-                                        rng.gen_range(3.0, 7.0),
-                                    ),
-                                    Color::from_rgba(
-                                        rng.gen_range(0x58, 0xFF),
-                                        rng.gen_range(0x58, 0xFF),
-                                        rng.gen_range(0x58, 0xFF),
-                                        255,
-                                    ),
-                                    if rot_clockwise {
-                                        rng.gen_range(0.1, 0.3)
-                                    } else {
-                                        rng.gen_range(-0.3, -0.1)
-                                    },
-                                    rng.gen_range(0.0, 90.0),
-                                ));
-                            } else {
-                                // spawn fish
-                                for i in 0..rng.gen_range(1, 4) {
-                                    self.fishes.push(Fish::new(
-                                        self.mouse_pos,
-                                        rng.gen_range(0.0, 360.0),
-                                        Color::from_rgba(
-                                            rng.gen_range(0x44, 0xFF),
-                                            rng.gen_range(0x44, 0xFF),
-                                            rng.gen_range(0x44, 0xFF),
-                                            255,
-                                        ),
-                                    ));
-                                }
-                            }
-                            window.get_sound_mut(&self.s_boom)?.play(0.8)?;
-                        }
-                    } else if self.state == 10 {
-                        self.click_time = Some(0.0);
-                        self.click_pos = self.mouse_pos;
-                    }
-                } else if self.selection_mode {
-                    if let Some(idx) = self.current_item {
-                        match self.state {
-                            0 => {
-                                self.state += 1;
-                                self.state_dirty = true;
-                            }
-                            2 => {
-                                if idx == 5 {
-                                    // hope
-                                    self.state = 3;
-                                    self.state_dirty = true;
-                                    self.joining_particles.particle_system.color =
-                                        Color::from_rgba(0xAA, 0xCC, 0xFF, 255);
-                                } else if idx == 6 {
-                                    // miracles
-                                    self.state = 4;
-                                    self.state_dirty = true;
-                                    self.joining_particles.particle_system.color =
-                                        Color::from_rgba(0xFF, 0xFF, 0xAA, 255);
-                                } else if idx == 7 {
-                                    // kindness
-                                    self.state = 5;
-                                    self.state_dirty = true;
-                                    self.joining_particles.particle_system.color =
-                                        Color::from_rgba(0xBB, 0xFF, 0xBB, 255);
-                                } else {
-                                    // determination
-                                    self.state = 6;
-                                    self.state_dirty = true;
-                                    self.joining_particles.particle_system.color =
-                                        Color::from_rgba(0xFF, 0xAA, 0xAA, 255);
-                                }
-                                window.get_sound_mut(&self.s_get)?.play(0.7)?;
-                            }
-                            _ => {
-                                self.state = 0;
-                                self.state_dirty = true;
-                            }
-                        }
-                    }
-                } else {
-                    match self.state {
-                        0 | 1 => self.state += 1,
-                        3 | 4 | 5 | 6 => self.state = 7,
-                        7 => self.state = 8,
-                        9 => self.state = 10,
-                        _ => self.state = 0,
-                    }
-                    self.state_dirty = true;
-                }
-            } else {
-                for mi in &mut self.menu.items {
-                    match &mut mi.item_type {
-                        MenuItemType::AppearingText {
-                            text,
-                            text_idx,
-                            text_size,
-                            text_c,
-                            timer,
-                        } => {
-                            *text_idx = text.len();
-                        }
-                        MenuItemType::Button {
-                            text,
-                            text_c,
-                            h_c,
-                            c,
-                        } => {
-                            //let style = FontStyle::new(42.0, *text_c);
-                        }
-                        MenuItemType::Pause { timer, length } => (),
-                        MenuItemType::InstantText {
-                            text,
-                            text_size,
-                            text_color,
-                        } => {}
-                    }
-                    mi.is_loaded = true;
-                }
-                self.current_finished = true;
-            }
-        }
-
-        // check pressed keys
-        if window.get_gi_mut().get_key_pressed('s')? {
-            if self.state == 10 {
-                let save_data = SaveData {
-                    planets: self.planets.clone(),
-                    stars: self.stars.clone(),
-                    fishes: self.fishes.clone(),
-                    player: self.player.clone(),
-                    joining_particles: self.joining_particles.clone(),
-                };
-                // TODO
-                //save("OneAndAll_LD45", "slot0", &save_data)?;
-                self.save_load_notification = Some(SaveLoadNotification::Save {
-                    text: None,
-                    timer: SL_NOTIF_TIME,
-                });
-            }
-        } else if window.get_gi_mut().get_key_pressed('l')? {
-            // TODO
-            //let load_result = load::<SaveData>("OneAndAll_LD45", "slot0");
-            //if let Ok(save_data) = load_result {
-            //    self.planets = save_data.planets;
-            //    self.stars = save_data.stars;
-            //    self.fishes = save_data.fishes;
-            //    self.player = save_data.player;
-            //    self.joining_particles = save_data.joining_particles;
-            //    self.expl_conv_p_systems.clear();
-            //    self.move_to = Vector {
-            //        x: self.player.x,
-            //        y: self.player.y,
-            //    };
-            //    self.camera.set_view_xy(
-            //        self.player.x - WIDTH_F / 2.0,
-            //        self.player.y - HEIGHT_F / 2.0,
-            //    );
-            //    self.dbl_click_timeout = None;
-            //    self.click_time = None;
-            //    self.click_release_time = DOUBLE_CLICK_TIME;
-
-            //    self.state = 10;
-            //    self.state_dirty = true;
-            //    self.save_load_notification = Some(SaveLoadNotification::Load {
-            //        text: None,
-            //        timer: SL_NOTIF_TIME,
-            //    });
-            //}
-        } else if window.get_gi_mut().get_key_pressed('r')? {
-            if self.state == 10 {
-                self.state = 0;
-                self.state_dirty = true;
-            }
-        }
-
-        self.click_release_time += dt;
-        if let Some(t) = &mut self.click_time {
-            *t += dt;
-            if *t > DOUBLE_CLICK_TIME {
-                self.move_to = self.click_pos; // - Vector::new(WIDTH_F / 2.0, HEIGHT_F / 2.0);
-            }
-        }
-
-        if let Some(t) = &mut self.dbl_click_timeout {
-            *t += dt;
-            if *t > 300.0 {
-                self.dbl_click_timeout = None;
-            }
-        }
-
-        self.player.x += (self.move_to.x - self.player.x) / 20.0;
-        self.player.y += (self.move_to.y - self.player.y) / 20.0;
-        self.player_particles.host_rect.x = self.player.x;
-        self.player_particles.host_rect.y = self.player.y;
-        self.joining_particles.particle_system.host_rect.x +=
-            (self.player.x - self.joining_particles.particle_system.host_rect.x) / 30.0;
-        self.joining_particles.particle_system.host_rect.y +=
-            (self.player.y - self.joining_particles.particle_system.host_rect.y) / 30.0;
-        let (cx, cy) = self.camera.get_view_xy()?;
-        self.camera.set_view_xy(
-            (self.player.x - WIDTH_F / 2.0 - cx) / 40.0,
-            (self.player.y - HEIGHT_F / 2.0 - cy) / 40.0,
-        )?;
-        window.get_gi_mut().set_camera(&self.camera);
-
-        self.player_r += dt / 10.0;
-
-        if self.state_dirty {
-            self.state_dirty = false;
-            if self.state > 1 && !self.music_on {
-                let music = window.get_music_mut(&self.music2)?;
-                music.set_loop(true)?;
-                music.play(0.5)?;
-                self.music_on = true;
-            }
-            match self.state {
-                1 => {
-                    self.menu = Menu::s_01();
-                    self.current_finished = false;
-                    self.selection_mode = false;
-                }
-                2 => {
-                    self.menu = Menu::s_02();
-                    self.current_finished = false;
-                    self.selection_mode = true;
-                }
-                3 => {
-                    self.menu = Menu::s_03();
-                    self.current_finished = false;
-                    self.selection_mode = false;
-                }
-                4 => {
-                    self.menu = Menu::s_04();
-                    self.current_finished = false;
-                    self.selection_mode = false;
-                }
-                5 => {
-                    self.menu = Menu::s_05();
-                    self.current_finished = false;
-                    self.selection_mode = false;
-                }
-                6 => {
-                    self.menu = Menu::s_06();
-                    self.current_finished = false;
-                    self.selection_mode = false;
-                }
-                7 => {
-                    self.menu = Menu::s_07();
-                    self.current_finished = false;
-                    self.selection_mode = false;
-                }
-                8 => {
-                    self.menu = Menu::s_08();
-                    self.current_finished = true;
-                    self.selection_mode = false;
-                    self.is_create_mode = true;
-                }
-                9 => {
-                    self.menu = Menu::s_09();
-                    self.current_finished = false;
-                    self.selection_mode = false;
-                    self.is_create_mode = false;
-                }
-                10 => {
-                    self.menu = Menu::s_10();
-                    self.current_finished = false;
-                    self.selection_mode = false;
-                    self.is_create_mode = true;
-                }
-                _ => {
-                    self.menu = Menu::start();
-                    self.current_item = None;
-                    self.selection_mode = true;
-                    self.is_create_mode = false;
-                    self.state = 0;
-                    self.player_particles.opacity = 0.0;
-                    self.joining_particles.particle_system.opacity = 0.0;
-                    self.expl_conv_p_systems.clear();
-                    self.planets.clear();
-                    self.stars.clear();
-                    self.fishes.clear();
-                    self.player.x = WIDTH_F / 2.0;
-                    self.player.y = HEIGHT_F / 2.0;
-                    self.move_to = Vector::new(WIDTH_F / 2.0, HEIGHT_F / 2.0);
-                    self.camera.set_view_xy(0.0, 0.0);
-                    self.click_time = None;
-                }
-            }
-        }
-
-        if self.joining_particles.particle_system.opacity < 1.0 && self.state > 2 {
-            self.joining_particles.particle_system.opacity += JOINING_OPACITY_RATE * dt as f32;
-            if self.joining_particles.particle_system.opacity > 1.0 {
-                self.joining_particles.particle_system.opacity = 1.0;
-            }
-            self.joining_particles.offset =
-                (1.0 - self.joining_particles.particle_system.opacity / 1.0) * JOINING_FAR_DIST
-                    + self.joining_particles.particle_system.opacity / 1.0 * JOINING_NEAR_DIST;
-        }
-
-        if self.player_particles.opacity < 1.0 && self.state > 1 {
-            self.player_particles.opacity += dt as f32 / 7000.0;
-            if self.player_particles.opacity > 1.0 {
-                self.player_particles.opacity = 1.0;
-            }
-        }
-
-        if self.music_on {
-        } else if self.state == 10 {
-            let mut music_on = false;
-            let music = window.get_music_mut(&self.music2)?;
-            music.set_loop(true)?;
-            music.play(0.5)?;
-            self.music_on = true;
-        }
-
-        for i in 0..self.menu.items.len() {
-            let mi: &mut MenuItem = &mut self.menu.items[i];
-            if !mi.is_loaded {
-                match &mut mi.item_type {
-                    MenuItemType::Button {
-                        text,
-                        text_c,
-                        h_c,
-                        c,
-                    } => {
-                        //self.font.execute(|font| {
-                        //    let style = FontStyle::new(42.0, *text_c);
-                        //    *text_image = Some(font.render(text, &style)?);
-                        //    Ok(())
-                        //})?;
-                        //if text_image.is_some() {
-                        mi.is_loaded = true;
-                        if i + 1 < self.menu.items.len() {
-                            self.menu.items[i + 1].is_loaded = false;
-                        } else {
-                            self.current_finished = true;
-                        }
-                        //}
-                    }
-                    MenuItemType::AppearingText {
-                        text,
-                        text_idx,
-                        text_size,
-                        text_c,
-                        timer,
-                    } => {
-                        *timer += dt;
-                        if *timer > TEXT_RATE {
-                            *timer -= TEXT_RATE;
-                            *text_idx += 1;
-                            if *text_idx >= text.len() {
-                                mi.is_loaded = true;
-                                if i + 1 < self.menu.items.len() {
-                                    self.menu.items[i + 1].is_loaded = false;
-                                } else {
-                                    self.current_finished = true;
-                                }
-                                continue;
-                            }
-                            //self.font.execute(|font| {
-                            //    let style = FontStyle::new(*text_size, *text_c);
-                            //    *text_image = Some(font.render(current_text, &style)?);
-                            //    Ok(())
-                            //})?;
-                        }
-                    }
-                    MenuItemType::Pause { timer, length } => {
-                        *timer += dt;
-                        if timer > length {
-                            mi.is_loaded = true;
-                            if i + 1 < self.menu.items.len() {
-                                self.menu.items[i + 1].is_loaded = false;
-                            } else {
-                                self.current_finished = true;
-                            }
-                        }
-                    }
-                    MenuItemType::InstantText {
-                        text,
-                        text_size,
-                        text_color,
-                    } => {
-                        //if text_image.is_none() {
-                        //    self.font.execute(|f| {
-                        //        let style = FontStyle::new(*text_size, *text_color);
-                        //        *text_image = Some(f.render(text, &style)?);
-                        //        Ok(())
-                        //    })?;
-                        //}
-                        //if text_image.is_some() {
-                        mi.is_loaded = true;
-                        if i + 1 < self.menu.items.len() {
-                            self.menu.items[i + 1].is_loaded = false;
-                        } else {
-                            self.current_finished = true;
-                        }
-                        //}
-                    }
-                }
-            }
-        }
-
-        self.player_particles.host_rect = self.player;
-        self.player_particles.update(dt);
-        self.joining_particles.update(dt);
-
-        for i in (0..self.expl_conv_p_systems.len()).rev() {
-            if self.expl_conv_p_systems[i].update(dt, &mut self.planets) {
-                self.expl_conv_p_systems.swap_remove(i);
-            }
-        }
-        for planet in &mut self.planets {
-            planet.update(dt);
-        }
-        for star in &mut self.stars {
-            star.update(dt);
-        }
-
-        if let Some(sl) = &mut self.save_load_notification {
-            match sl {
-                SaveLoadNotification::Save { text, timer } => {
-                    *timer -= dt;
-                    if *timer <= 0.0 {
-                        self.save_load_notification = None;
-                    } else if text.is_none() {
-                        //self.font.execute(|f| {
-                        //    *text = Some(
-                        //        f.render("Saved the Game", &FontStyle::new(45.0, Color::WHITE))?,
-                        //    );
-                        //    Ok(())
-                        //})?;
-                    }
-                }
-                SaveLoadNotification::Load { text, timer } => {
-                    *timer -= dt;
-                    if *timer <= 0.0 {
-                        self.save_load_notification = None;
-                    } else if text.is_none() {
-                        //self.font.execute(|f| {
-                        //    *text = Some(
-                        //        f.render("Loaded the Game", &FontStyle::new(45.0, Color::WHITE))?,
-                        //    );
-                        //    Ok(())
-                        //})?;
-                    }
-                }
-            }
-        }
-
-        for fish in &mut self.fishes {
-            fish.update(dt);
-        }
-
-        Ok(())
-    }
-
-    fn draw(&mut self, window: &mut Window) -> Result<(), String> {
-        window.get_gi_mut().clear_window(Color::BLACK)?;
-        let mut rect = Rectangle::default();
-        for mi in &mut self.menu.items {
-            rect.x = mi.x;
-            rect.y = mi.y;
-            rect.w = mi.w;
-            rect.h = mi.h;
-            match &mut mi.item_type {
-                MenuItemType::Button {
-                    text,
-                    text_c,
-                    h_c,
-                    c,
-                } => {
-                    if mi.is_hover {
-                        window.get_gi_mut().draw_rect(rect, *h_c)?;
-                    } else {
-                        window.get_gi_mut().draw_rect(rect, *c)?;
-                    }
-                    window
-                        .get_font_mut(&self.font)?
-                        .draw(text, 20, rect.x, rect.y, *text_c)?;
-                }
-                MenuItemType::AppearingText {
-                    text,
-                    text_idx,
-                    text_size,
-                    text_c,
-                    timer,
-                } => {
-                    window.get_font_mut(&self.font)?.draw(
-                        if *text_idx < text.len() {
-                            &text[0..*text_idx]
-                        } else {
-                            text
-                        },
-                        20,
-                        rect.x,
-                        rect.y,
-                        *text_c,
-                    )?;
-                }
-                MenuItemType::InstantText {
-                    text,
-                    text_size,
-                    text_color,
-                } => {
-                    window.get_font_mut(&self.font)?.draw(
-                        text,
-                        text_size.round() as u32,
-                        rect.x,
-                        rect.y,
-                        *text_color,
-                    )?;
-                }
-                MenuItemType::Pause { timer, length } => (),
-            }
-        }
-        self.player_particles.draw(window, Transform::IDENTITY);
-        window.get_gi_mut().draw_rect_transform(
-            self.player,
-            Color::from_rgba(255, 255, 255, (self.player_particles.opacity * 255.0) as u8),
-            Transform::translate(-self.player.x / 2.0, -self.player.y / 2.0)
-                * Transform::rotate(self.player_r as f32),
-        )?;
-        self.joining_particles.draw(window, Transform::IDENTITY);
-        for expl_conv_ps in &mut self.expl_conv_p_systems {
-            expl_conv_ps.draw(window, Transform::IDENTITY);
-        }
-        for planet in &mut self.planets {
-            planet.draw(window, Transform::IDENTITY);
-        }
-
-        for star in &mut self.stars {
-            star.draw(&self.i_star, window, Transform::IDENTITY);
-        }
-
-        for fish in &mut self.fishes {
-            fish.draw(&self.i_fish, window, Transform::IDENTITY);
-        }
-
-        // TODO
-        //if let Some(sl) = &mut self.save_load_notification {
-        //    match sl {
-        //        SaveLoadNotification::Save { text, timer }
-        //        | SaveLoadNotification::Load { text, timer } => {
-        //            if let Some(i) = text {
-        //                let mut c = Color::WHITE;
-        //                c.a = ((*timer / SL_NOTIF_TIME) as f32 * 255.0) as u8;
-        //                let mut image_rect = i.area_rect();
-        //                image_rect.x = self.camera.x + 20.0;
-        //                image_rect.y = self.camera.y + 20.0;
-        //                window.draw(&image_rect, Blended(i, c));
-        //            }
-        //        }
-        //    }
-        //}
-
-        Ok(())
-    }
-}
+mod original_impl;
 
 fn main() {
     // TODO
diff --git a/src/original_impl.rs b/src/original_impl.rs
new file mode 100644 (file)
index 0000000..d37c824
--- /dev/null
@@ -0,0 +1,1953 @@
+use std::{path::PathBuf, str::FromStr};
+
+use crate::agnostic_interface::CameraInterface;
+use crate::faux_quicksilver::{Circle, Color, Rectangle, Transform, Vector, Window};
+use rand::prelude::*;
+use serde::{Deserialize, Serialize};
+
+const WIDTH_F: f32 = 800.0;
+const HEIGHT_F: f32 = 600.0;
+const MUSIC2_LENGTH: f32 = 2.0 * 60.0 * 1000.0;
+const TEXT_RATE: f32 = 100.0;
+const PP_GEN_RATE: f32 = 75.0;
+const PARTICLE_RAND_VEL_RANGE: f32 = 0.2;
+const PARTICLE_RAND_VEL_DIST: f32 = 0.2828427; // dist where x and y = 0.2
+const PARTICLE_RAND_ROT_RANGE: f32 = 0.5;
+const JOINING_OPACITY_RATE: f32 = 0.00013;
+const JOINING_FAR_DIST: f32 = 700.0;
+const JOINING_NEAR_DIST: f32 = 150.0;
+const DOUBLE_CLICK_TIME: f32 = 350.0;
+const SL_NOTIF_TIME: f32 = 5000.0;
+const MAX_MOONS: usize = 5;
+
+fn interp_sq_inv(x: f32) -> f32 {
+    if x < 0.0 {
+        return 0.0;
+    } else if x > 1.0 {
+        return 1.0;
+    }
+    let y = x - 1.0;
+    -y * y + 1.0
+}
+
+fn interp_sq(x: f32) -> f32 {
+    if x < 0.0 {
+        return 0.0;
+    } else if x > 1.0 {
+        return 1.0;
+    }
+    x * x
+}
+
+enum MenuItemType {
+    Button {
+        text: &'static str,
+        text_c: Color,
+        h_c: Color,
+        c: Color,
+    },
+    AppearingText {
+        text: &'static str,
+        text_idx: usize,
+        text_size: f32,
+        text_c: Color,
+        timer: f32,
+    },
+    InstantText {
+        text: &'static str,
+        text_size: f32,
+        text_color: Color,
+    },
+    Pause {
+        timer: f32,
+        length: f32,
+    },
+}
+
+struct MenuItem {
+    x: f32,
+    y: f32,
+    w: f32,
+    h: f32,
+    item_type: MenuItemType,
+    is_hover: bool,
+    is_focus: bool,
+    is_loaded: bool,
+}
+
+impl MenuItem {
+    fn is_inside(&self, x: f32, y: f32) -> bool {
+        x >= self.x && x < self.x + self.w && y >= self.y && y < self.y + self.h
+    }
+}
+
+struct Menu {
+    items: Vec<MenuItem>,
+}
+
+impl Menu {
+    fn button(
+        x: f32,
+        y: f32,
+        w: f32,
+        h: f32,
+        s: &'static str,
+        t_color: Color,
+        box_color: Color,
+        boxh_color: Color,
+        first: bool,
+    ) -> MenuItem {
+        MenuItem {
+            x,
+            y,
+            w,
+            h,
+            item_type: MenuItemType::Button {
+                text: s,
+                text_c: t_color,
+                h_c: boxh_color,
+                c: box_color,
+            },
+            is_hover: false,
+            is_focus: false,
+            is_loaded: !first,
+        }
+    }
+
+    fn start() -> Menu {
+        let item = MenuItem {
+            x: WIDTH_F / 2.0 - 120.0,
+            y: 150.0,
+            w: 240.0,
+            h: 150.0,
+            item_type: MenuItemType::Button {
+                text: "Start the Game",
+                text_c: Color::WHITE,
+                h_c: Color::from_rgba(0x66, 0xFF, 0xFF, 255),
+                c: Color::from_rgba(0x33, 0xDD, 0xDD, 255),
+            },
+            is_hover: false,
+            is_focus: false,
+            is_loaded: false,
+        };
+
+        Menu {
+            items: vec![
+                item,
+                Menu::instant_text(
+                    70.0,
+                    50.0,
+                    55.0,
+                    true,
+                    "One And All - A Ludum Dare 45 Entry",
+                ),
+                Menu::instant_text(
+                    25.0,
+                    HEIGHT_F - 100.0,
+                    30.0,
+                    true,
+                    "Made with quicksilver which is licensed with either",
+                ),
+                Menu::instant_text(
+                    70.0,
+                    HEIGHT_F - 80.0,
+                    30.0,
+                    true,
+                    "MIT License or Apache License Version 2.0",
+                ),
+                Menu::instant_text(
+                    25.0,
+                    HEIGHT_F - 50.0,
+                    30.0,
+                    true,
+                    "Uses Clear-Sans which is licensed with Apache License Version 2.0",
+                ),
+            ],
+        }
+    }
+
+    fn instant_text(x: f32, y: f32, text_size: f32, first: bool, s: &'static str) -> MenuItem {
+        MenuItem {
+            x,
+            y,
+            w: 0.0,
+            h: 0.0,
+            item_type: MenuItemType::InstantText {
+                text: s,
+                text_size,
+                text_color: Color::WHITE,
+            },
+            is_hover: false,
+            is_focus: false,
+            is_loaded: !first,
+        }
+    }
+
+    fn text(x: f32, y: f32, text_size: f32, first: bool, s: &'static str) -> MenuItem {
+        MenuItem {
+            x,
+            y,
+            w: 0.0,
+            h: 0.0,
+            item_type: MenuItemType::AppearingText {
+                text: s,
+                text_size,
+                text_c: Color::WHITE,
+                timer: 0.0,
+                text_idx: 0,
+            },
+            is_hover: false,
+            is_focus: false,
+            is_loaded: !first,
+        }
+    }
+
+    fn pause(length: f32, first: bool) -> MenuItem {
+        MenuItem {
+            x: 0.0,
+            y: 0.0,
+            w: 0.0,
+            h: 0.0,
+            item_type: MenuItemType::Pause { timer: 0.0, length },
+            is_hover: false,
+            is_focus: false,
+            is_loaded: !first,
+        }
+    }
+
+    fn s_01() -> Menu {
+        Menu {
+            items: vec![
+                Menu::pause(500.0, true),
+                Menu::text(50.0, HEIGHT_F - 140.0, 40.0, false, "This is how it is."),
+                Menu::pause(500.0, false),
+                Menu::text(
+                    50.0,
+                    HEIGHT_F - 100.0,
+                    40.0,
+                    false,
+                    "Nothing is, and everything is nothing.",
+                ),
+                Menu::pause(500.0, false),
+                Menu::text(50.0, HEIGHT_F - 60.0, 40.0, false, "...until you appeared."),
+                Menu::pause(100.0, false),
+                Menu::text(
+                    570.0,
+                    HEIGHT_F - 50.0,
+                    30.0,
+                    false,
+                    "(Click to continue...)",
+                ),
+            ],
+        }
+    }
+
+    fn s_02() -> Menu {
+        Menu {
+            items: vec![
+                Menu::text(
+                    50.0,
+                    HEIGHT_F - 150.0,
+                    40.0,
+                    true,
+                    "Just by being, you brought light into existence.",
+                ),
+                Menu::pause(500.0, false),
+                Menu::text(
+                    50.0,
+                    HEIGHT_F - 110.0,
+                    40.0,
+                    false,
+                    "What brings you here? What drives you?",
+                ),
+                Menu::pause(500.0, false),
+                Menu::text(
+                    50.0,
+                    HEIGHT_F - 70.0,
+                    40.0,
+                    false,
+                    "Please tell me, what fuels you?",
+                ),
+                Menu::button(
+                    100.0,
+                    30.0,
+                    200.0,
+                    85.0,
+                    "Hope",
+                    Color::WHITE,
+                    Color::BLACK,
+                    Color::from_rgba(0x33, 0x33, 0x33, 255),
+                    false,
+                ),
+                Menu::button(
+                    500.0,
+                    30.0,
+                    200.0,
+                    85.0,
+                    "Miracles",
+                    Color::WHITE,
+                    Color::BLACK,
+                    Color::from_rgba(0x33, 0x33, 0x33, 255),
+                    false,
+                ),
+                Menu::button(
+                    100.0,
+                    150.0,
+                    200.0,
+                    85.0,
+                    "Kindness",
+                    Color::WHITE,
+                    Color::BLACK,
+                    Color::from_rgba(0x33, 0x33, 0x33, 255),
+                    false,
+                ),
+                Menu::button(
+                    500.0,
+                    150.0,
+                    200.0,
+                    85.0,
+                    "Determination",
+                    Color::WHITE,
+                    Color::BLACK,
+                    Color::from_rgba(0x33, 0x33, 0x33, 255),
+                    false,
+                ),
+            ],
+        }
+    }
+
+    // choose hope
+    fn s_03() -> Menu {
+        Menu {
+            items: vec![
+                Menu::text(
+                    50.0,
+                    HEIGHT_F - 170.0,
+                    40.0,
+                    true,
+                    "Hope... hope that your actions will inspire others..",
+                ),
+                Menu::pause(500.0, false),
+                Menu::text(
+                    50.0,
+                    HEIGHT_F - 130.0,
+                    40.0,
+                    false,
+                    "Hope that a brighter future will come tomorrow...",
+                ),
+                Menu::pause(500.0, false),
+                Menu::text(
+                    50.0,
+                    HEIGHT_F - 90.0,
+                    40.0,
+                    false,
+                    ".. With your appearance, perhaps it shall...",
+                ),
+            ],
+        }
+    }
+
+    // choose miracles
+    fn s_04() -> Menu {
+        Menu {
+            items: vec![
+                Menu::text(
+                    30.0,
+                    HEIGHT_F - 170.0,
+                    40.0,
+                    true,
+                    "Miracles huh?.. I see, then your appearance is special.",
+                ),
+                Menu::pause(500.0, false),
+                Menu::text(
+                    30.0,
+                    HEIGHT_F - 130.0,
+                    40.0,
+                    false,
+                    "With your appearance, things may change for the better..",
+                ),
+                Menu::pause(500.0, false),
+                Menu::text(
+                    30.0,
+                    HEIGHT_F - 90.0,
+                    40.0,
+                    false,
+                    "Now I am certain that this meeting was not by chance.",
+                ),
+            ],
+        }
+    }
+
+    // choose kindness
+    fn s_05() -> Menu {
+        Menu {
+            items: vec![
+                Menu::text(
+                    50.0,
+                    HEIGHT_F - 170.0,
+                    40.0,
+                    true,
+                    "Kindness?.. I am in your debt.",
+                ),
+                Menu::pause(250.0, false),
+                Menu::text(
+                    50.0,
+                    HEIGHT_F - 130.0,
+                    40.0,
+                    false,
+                    "It has been a long time since I have encountered",
+                ),
+                Menu::text(50.0, HEIGHT_F - 90.0, 40.0, false, "another being..."),
+                Menu::pause(500.0, false),
+                Menu::text(270.0, HEIGHT_F - 90.0, 40.0, false, "... Thank you..."),
+            ],
+        }
+    }
+
+    // choose determination
+    fn s_06() -> Menu {
+        Menu {
+            items: vec![
+                Menu::text(
+                    50.0,
+                    HEIGHT_F - 170.0,
+                    40.0,
+                    true,
+                    "Determination.. I see...",
+                ),
+                Menu::pause(500.0, false),
+                Menu::text(
+                    400.0,
+                    HEIGHT_F - 170.0,
+                    40.0,
+                    false,
+                    "I do not doubt it, for it",
+                ),
+                Menu::text(
+                    50.0,
+                    HEIGHT_F - 130.0,
+                    40.0,
+                    false,
+                    "must have been difficult to come here..",
+                ),
+                Menu::pause(500.0, false),
+                Menu::text(
+                    50.0,
+                    HEIGHT_F - 90.0,
+                    40.0,
+                    false,
+                    "Your resolve is evident by your mere presence..",
+                ),
+            ],
+        }
+    }
+
+    fn s_07() -> Menu {
+        Menu {
+            items: vec![
+                Menu::text(
+                    50.0,
+                    HEIGHT_F - 130.0,
+                    40.0,
+                    true,
+                    "Now that you are here, it must mean a new era of",
+                ),
+                Menu::text(
+                    50.0,
+                    HEIGHT_F - 90.0,
+                    40.0,
+                    false,
+                    "creation for all that will be.",
+                ),
+                Menu::pause(200.0, false),
+                Menu::text(
+                    50.0,
+                    HEIGHT_F - 50.0,
+                    40.0,
+                    false,
+                    "Try double-clicking the void to create something...",
+                ),
+            ],
+        }
+    }
+
+    fn s_08() -> Menu {
+        Menu {
+            items: vec![Menu::instant_text(
+                50.0,
+                HEIGHT_F - 90.0,
+                35.0,
+                true,
+                "(Try double-clicking now...)",
+            )],
+        }
+    }
+
+    fn s_09() -> Menu {
+        Menu {
+            items: vec![
+                Menu::pause(400.0, true),
+                Menu::text(
+                    50.0,
+                    HEIGHT_F - 140.0,
+                    40.0,
+                    false,
+                    "A new planet... It has most certainly been a while.",
+                ),
+                Menu::pause(500.0, false),
+                Menu::text(
+                    50.0,
+                    HEIGHT_F - 100.0,
+                    40.0,
+                    false,
+                    "Please, go out and create the new universe, and again..",
+                ),
+                Menu::pause(300.0, false),
+                Menu::text(50.0, HEIGHT_F - 60.0, 40.0, false, "Thank you."),
+            ],
+        }
+    }
+
+    fn s_10() -> Menu {
+        Menu {
+            items: vec![
+                Menu::instant_text(
+                    20.0,
+                    HEIGHT_F - 40.0,
+                    20.0,
+                    true,
+                    "Single click to move, Double-click to create something",
+                ),
+                Menu::instant_text(
+                    20.0,
+                    HEIGHT_F - 20.0,
+                    20.0,
+                    true,
+                    "S - save; L - load (can load from the start); R - reset",
+                ),
+            ],
+        }
+    }
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+struct Particle {
+    rect: Rectangle,
+    circle: Circle,
+    is_rect: bool,
+    velx: f32,
+    vely: f32,
+    velr: f32,
+    r: f32,
+    lifetime: f32,
+    life_timer: f32,
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+struct ParticleSystem {
+    particles: Vec<Particle>,
+    spawn_timer: f32,
+    spawn_time: f32,
+    lifetime: f32,
+    host_rect: Rectangle,
+    host_circle: Circle,
+    is_rect: bool,
+    direction: Vector,
+    color: Color,
+    opacity: f32,
+    vel_multiplier: f32,
+}
+
+impl ParticleSystem {
+    fn new(
+        spawn_time: f32,
+        lifetime: f32,
+        host_rect: Rectangle,
+        host_circle: Circle,
+        is_rect: bool,
+        direction: Vector,
+        color: Color,
+        opacity: f32,
+        vel_multiplier: f32,
+    ) -> Self {
+        Self {
+            particles: Vec::new(),
+            spawn_timer: 0.0,
+            spawn_time,
+            lifetime,
+            host_rect,
+            host_circle,
+            is_rect,
+            direction,
+            color,
+            opacity,
+            vel_multiplier,
+        }
+    }
+
+    fn update(&mut self, dt: f32) {
+        for i in (0..self.particles.len()).rev() {
+            self.particles[i].life_timer += dt;
+            if self.particles[i].life_timer > self.particles[i].lifetime {
+                self.particles.swap_remove(i);
+            } else {
+                if self.is_rect {
+                    self.particles[i].rect.x += self.particles[i].velx * dt as f32;
+                    self.particles[i].rect.y += self.particles[i].vely * dt as f32;
+                    self.particles[i].r += self.particles[i].velr * dt as f32;
+                } else {
+                    self.particles[i].circle.x += self.particles[i].velx * dt as f32;
+                    self.particles[i].circle.y += self.particles[i].vely * dt as f32;
+                }
+            }
+        }
+
+        self.spawn_timer += dt;
+        if self.spawn_timer > self.spawn_time {
+            self.spawn_timer -= self.spawn_time;
+            self.particles.push(Particle {
+                rect: self.host_rect,
+                circle: self.host_circle,
+                is_rect: self.is_rect,
+                velx: (rand::thread_rng()
+                    .gen_range(-PARTICLE_RAND_VEL_RANGE, PARTICLE_RAND_VEL_RANGE)
+                    + self.direction.x)
+                    * self.vel_multiplier,
+                vely: (rand::thread_rng()
+                    .gen_range(-PARTICLE_RAND_VEL_RANGE, PARTICLE_RAND_VEL_RANGE)
+                    + self.direction.y)
+                    * self.vel_multiplier,
+                // velx: self.direction.x,
+                // vely: self.direction.y,
+                velr: rand::thread_rng()
+                    .gen_range(-PARTICLE_RAND_ROT_RANGE, PARTICLE_RAND_ROT_RANGE)
+                    * self.vel_multiplier,
+                r: rand::thread_rng().gen_range(0.0, 90.0),
+                lifetime: self.lifetime,
+                life_timer: 0.0,
+            });
+        }
+    }
+
+    fn draw(&mut self, window: &mut Window, transform: Transform) {
+        if self.opacity == 0.0 {
+            return;
+        }
+        for particle in &mut self.particles {
+            self.color.a = ((1.0 - (particle.life_timer / particle.lifetime) as f32)
+                * self.opacity
+                * 255.0) as u8;
+            if particle.is_rect {
+                let pre_transform =
+                    Transform::translate(-particle.rect.x / 2.0, -particle.rect.y / 2.0)
+                        * Transform::rotate(particle.r);
+                window
+                    .get_gi_mut()
+                    .draw_rect_transform(particle.rect, self.color, transform * pre_transform)
+                    .ok();
+            } else {
+                window
+                    .get_gi_mut()
+                    .draw_circle_transform(particle.circle, self.color, transform)
+                    .ok();
+            }
+        }
+    }
+
+    fn force_spawn(&mut self, count: usize) {
+        for i in 0..count {
+            self.particles.push(Particle {
+                rect: self.host_rect,
+                circle: self.host_circle,
+                is_rect: self.is_rect,
+                velx: (rand::thread_rng()
+                    .gen_range(-PARTICLE_RAND_VEL_RANGE, PARTICLE_RAND_VEL_RANGE)
+                    + self.direction.x)
+                    * self.vel_multiplier,
+                vely: (rand::thread_rng()
+                    .gen_range(-PARTICLE_RAND_VEL_RANGE, PARTICLE_RAND_VEL_RANGE)
+                    + self.direction.y)
+                    * self.vel_multiplier,
+                // velx: self.direction.x,
+                // vely: self.direction.y,
+                velr: rand::thread_rng()
+                    .gen_range(-PARTICLE_RAND_ROT_RANGE, PARTICLE_RAND_ROT_RANGE)
+                    * self.vel_multiplier,
+                r: rand::thread_rng().gen_range(0.0, 90.0),
+                lifetime: self.lifetime,
+                life_timer: 0.0,
+            });
+        }
+    }
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+struct RotatingParticleSystem {
+    particle_system: ParticleSystem,
+    r: f32,
+    velr: f32,
+    offset: f32,
+}
+
+impl RotatingParticleSystem {
+    fn new(
+        spawn_time: f32,
+        lifetime: f32,
+        host_rect: Rectangle,
+        host_circle: Circle,
+        is_rect: bool,
+        direction: Vector,
+        color: Color,
+        opacity: f32,
+        rotation: f32,
+        velr: f32,
+        offset: f32,
+        vel_multiplier: f32,
+    ) -> Self {
+        RotatingParticleSystem {
+            particle_system: ParticleSystem::new(
+                spawn_time,
+                lifetime,
+                host_rect,
+                host_circle,
+                is_rect,
+                direction,
+                color,
+                opacity,
+                vel_multiplier,
+            ),
+            r: rotation,
+            velr,
+            offset,
+        }
+    }
+
+    fn update(&mut self, dt: f32) {
+        if self.particle_system.is_rect {
+            let saved_rect = self.particle_system.host_rect;
+            self.particle_system
+                .host_rect
+                .pos_add_vec(Transform::rotate(self.r) * Vector::new(self.offset, 0.0));
+            self.particle_system.update(dt);
+            self.particle_system.host_rect = saved_rect;
+        } else {
+            let saved_cir = self.particle_system.host_circle;
+            self.particle_system
+                .host_circle
+                .pos_add_vec(Transform::rotate(self.r) * Vector::new(self.offset, 0.0));
+            self.particle_system.update(dt);
+            self.particle_system.host_circle = saved_cir;
+        }
+        self.r += self.velr * dt as f32;
+    }
+
+    fn draw(&mut self, window: &mut Window, transform: Transform) {
+        if self.particle_system.opacity == 0.0 {
+            return;
+        }
+        self.particle_system.direction =
+            Transform::rotate(self.r) * Vector::new(0.0, -PARTICLE_RAND_VEL_DIST);
+        self.particle_system.draw(window, transform);
+        if self.particle_system.is_rect {
+            let mut moved_rect = self.particle_system.host_rect;
+            moved_rect.pos_add_vec(Transform::rotate(self.r) * Vector::new(self.offset, 0.0));
+            let mut solid_color = self.particle_system.color;
+            solid_color.a = (self.particle_system.opacity * 255.0) as u8;
+            window
+                .get_gi_mut()
+                .draw_rect_transform(
+                    moved_rect,
+                    solid_color,
+                    transform
+                        * Transform::translate(-moved_rect.x / 2.0, -moved_rect.y / 2.0)
+                        * Transform::rotate(self.r * 1.3),
+                )
+                .ok();
+        } else {
+            let mut moved_cir = self.particle_system.host_circle;
+            moved_cir.pos_add_vec(Transform::rotate(self.r) * Vector::new(self.offset, 0.0));
+            let mut solid_color = self.particle_system.color;
+            solid_color.a = (self.particle_system.opacity * 255.0) as u8;
+            window
+                .get_gi_mut()
+                .draw_circle_transform(moved_cir, solid_color, transform)
+                .ok();
+        }
+    }
+}
+
+struct ExplConvCircleParticle {
+    circle: Circle,
+    offset: f32,
+    r: f32,
+}
+
+struct ExplConvParticleSystem {
+    particles: Vec<ExplConvCircleParticle>,
+    lifetime: f32,
+    host_circle: Circle,
+    color: Color,
+    opacity: f32,
+    life_timer: f32,
+}
+
+impl ExplConvParticleSystem {
+    fn new(lifetime: f32, host_circle: Circle, color: Color, opacity: f32) -> Self {
+        ExplConvParticleSystem {
+            particles: Vec::new(),
+            lifetime,
+            host_circle,
+            color,
+            opacity,
+            life_timer: 0.0,
+        }
+    }
+
+    fn activate(&mut self, count: usize, offset: f32) {
+        self.life_timer = 0.0;
+        for i in 0..count {
+            self.particles.push(ExplConvCircleParticle {
+                circle: self.host_circle,
+                offset,
+                r: rand::thread_rng().gen_range(0.0, 360.0),
+            });
+        }
+    }
+
+    // returns true if finished
+    fn update(&mut self, dt: f32, planets: &mut Vec<Planet>) -> bool {
+        self.life_timer += dt;
+        if self.life_timer >= self.lifetime {
+            if !self.particles.is_empty() {
+                self.particles.clear();
+                planets.push(Planet::new(self.host_circle, self.color));
+                return true;
+            }
+            return false;
+        }
+
+        if self.life_timer < self.lifetime / 2.0 {
+            let amount = interp_sq_inv((self.life_timer / self.lifetime) as f32 * 2.0);
+            for particle in &mut self.particles {
+                let dir =
+                    Transform::rotate(particle.r) * Vector::new(particle.offset * amount, 0.0);
+                particle.circle.x = dir.x + self.host_circle.x;
+                particle.circle.y = dir.y + self.host_circle.y;
+            }
+        } else {
+            let amount = 1.0 - interp_sq(((self.life_timer / self.lifetime) as f32 - 0.5) * 2.0);
+            for particle in &mut self.particles {
+                let dir =
+                    Transform::rotate(particle.r) * Vector::new(particle.offset * amount, 0.0);
+                particle.circle.x = dir.x + self.host_circle.x;
+                particle.circle.y = dir.y + self.host_circle.y;
+            }
+        }
+        return false;
+    }
+
+    fn draw(&mut self, window: &mut Window, transform: Transform) {
+        if self.opacity == 0.0 {
+            return;
+        }
+        for particle in &mut self.particles {
+            self.color.a = (((self.life_timer / self.lifetime) as f32 / 2.0 + 0.5)
+                * self.opacity
+                * 255.0) as u8;
+            window
+                .get_gi_mut()
+                .draw_circle_transform(particle.circle, self.color, transform)
+                .ok();
+        }
+    }
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+struct Planet {
+    circle: Circle,
+    color: Color,
+    particle_system: ParticleSystem,
+    moons: Vec<RotatingParticleSystem>,
+}
+
+impl Planet {
+    fn new(circle: Circle, color: Color) -> Self {
+        let mut smaller_circle = circle;
+        smaller_circle.r /= 4.0;
+        let mut planet = Planet {
+            circle,
+            color,
+            particle_system: ParticleSystem::new(
+                rand::thread_rng().gen_range(2000.0, 3800.0),
+                900.0,
+                Rectangle::new(0.0, 0.0, 1.0, 1.0),
+                circle,
+                false,
+                Vector::new(0.0, 0.0),
+                color,
+                1.0,
+                0.3,
+            ),
+            moons: Vec::with_capacity(MAX_MOONS),
+        };
+
+        let r: f32 = rand::thread_rng().gen_range(0.0, 360.0);
+        let clockwise = rand::thread_rng().gen_bool(0.5);
+        for i in 0..rand::thread_rng().gen_range(0, MAX_MOONS) {
+            planet.moons.push(RotatingParticleSystem::new(
+                rand::thread_rng().gen_range(1000.0, 2600.0),
+                600.0,
+                Rectangle::new(0.0, 0.0, 1.0, 1.0),
+                smaller_circle,
+                false,
+                Vector::new(0.0, 0.0),
+                color,
+                1.0,
+                r,
+                if clockwise {
+                    rand::thread_rng().gen_range(0.05, 0.15)
+                } else {
+                    rand::thread_rng().gen_range(-0.15, -0.05)
+                },
+                rand::thread_rng().gen_range(35.0, 200.0),
+                0.2,
+            ));
+        }
+
+        planet
+    }
+
+    fn update(&mut self, dt: f32) {
+        self.particle_system.host_circle.x = self.circle.x;
+        self.particle_system.host_circle.y = self.circle.y;
+        self.particle_system.update(dt);
+        for moon in &mut self.moons {
+            moon.particle_system.host_circle.x = self.circle.x;
+            moon.particle_system.host_circle.y = self.circle.y;
+            moon.update(dt);
+        }
+    }
+
+    fn draw(&mut self, window: &mut Window, transform: Transform) {
+        self.particle_system.draw(window, transform);
+        window
+            .get_gi_mut()
+            .draw_circle_transform(self.circle, self.color, transform)
+            .ok();
+        for moon in &mut self.moons {
+            moon.draw(window, transform);
+        }
+    }
+}
+
+#[derive(Clone, Serialize, Deserialize)]
+struct Star {
+    color: Color,
+    particle_system: ParticleSystem,
+    velr: f32,
+    r: f32,
+}
+
+impl Star {
+    fn new(circle: Circle, color: Color, velr: f32, r: f32) -> Self {
+        let mut star = Star {
+            color,
+            particle_system: ParticleSystem::new(
+                rand::thread_rng().gen_range(80.0, 200.0),
+                850.0,
+                Rectangle::new(0.0, 0.0, 1.0, 1.0),
+                circle,
+                false,
+                Vector::new(0.0, 0.0),
+                color,
+                1.0,
+                1.0,
+            ),
+            velr,
+            r,
+        };
+
+        if star.color.r < (0.75 * 255.0) as u8 {
+            star.color.r = (0.75 * 255.0) as u8;
+        }
+        if star.color.g < (0.75 * 255.0) as u8 {
+            star.color.g = (0.75 * 255.0) as u8;
+        }
+        if star.color.b < (0.75 * 255.0) as u8 {
+            star.color.b = (0.75 * 255.0) as u8;
+        }
+        star.particle_system
+            .force_spawn(rand::thread_rng().gen_range(20, 45));
+
+        star
+    }
+
+    fn update(&mut self, dt: f32) {
+        self.particle_system.update(dt);
+        self.r += self.velr * dt as f32;
+    }
+
+    fn draw(&mut self, image: &str, window: &mut Window, transform: Transform) {
+        self.particle_system.draw(window, transform);
+        let image = window.get_image_mut(image).expect("Should be loaded image");
+        let mut image_rect = image.get_wh_rect();
+        image_rect.x = self.particle_system.host_circle.x - image_rect.w / 2.0;
+        image_rect.y = self.particle_system.host_circle.y - image_rect.h / 2.0;
+        image
+            .draw_transform(
+                image_rect.x,
+                image_rect.y,
+                self.color,
+                transform * Transform::rotate(self.r),
+            )
+            .ok();
+    }
+}
+
+#[derive(Clone, Serialize, Deserialize)]
+struct Fish {
+    pos: Vector,
+    r: f32,
+    swim_time: f32,
+    swim_timer: f32,
+    swim_v: f32,
+    anim_timer: f32,
+    anim_time: f32,
+    color: Color,
+    body_rect: Rectangle,
+    tail_rect: Rectangle,
+}
+
+enum FishState {
+    Idle,
+    Swim,
+}
+
+impl Fish {
+    fn new(pos: Vector, r: f32, color: Color) -> Self {
+        let anim_timer = rand::thread_rng().gen_range(800.0, 1000.0);
+        Self {
+            pos,
+            r,
+            swim_time: 800.0,
+            swim_timer: 800.0,
+            swim_v: 0.2,
+            anim_timer,
+            anim_time: anim_timer,
+            color,
+            body_rect: Rectangle {
+                x: 0.0,
+                y: 0.0,
+                w: 32.0,
+                h: 16.0,
+            },
+            tail_rect: Rectangle {
+                x: 32.0,
+                y: 0.0,
+                w: 16.0,
+                h: 16.0,
+            },
+        }
+    }
+
+    fn set_next(&mut self, state: FishState) {
+        match state {
+            FishState::Idle => {
+                self.swim_time = rand::thread_rng().gen_range(1100.0, 2400.0);
+                self.swim_timer = self.swim_time;
+                self.anim_timer = 1600.0;
+                self.anim_time = 1600.0;
+                self.swim_v = 0.0;
+            }
+            FishState::Swim => {
+                self.swim_time = rand::thread_rng().gen_range(1400.0, 2300.0);
+                self.swim_timer = self.swim_time;
+                self.r = rand::thread_rng().gen_range(0.0, 360.0);
+                self.anim_timer = rand::thread_rng().gen_range(600.0, 1000.0);
+                self.anim_time = self.anim_timer;
+                self.swim_v = (self.anim_timer / 8000.0) as f32;
+            }
+        }
+    }
+
+    fn update(&mut self, dt: f32) {
+        self.swim_time -= dt;
+        if self.swim_time < 220.0 {
+            self.swim_v /= 1.1;
+        }
+        if self.swim_time <= 0.0 {
+            if rand::thread_rng().gen_bool(0.4) {
+                self.set_next(FishState::Idle);
+            } else {
+                self.set_next(FishState::Swim);
+            }
+        }
+
+        self.anim_timer -= dt;
+        if self.anim_timer <= 0.0 {
+            self.anim_timer = self.anim_time;
+        }
+
+        self.pos += Transform::rotate(self.r) * Vector::new(self.swim_v, 0.0) * dt as f32;
+    }
+
+    fn draw(&mut self, i_fish: &str, window: &mut Window, transform: Transform) {
+        let fish_img = window
+            .get_image_mut(i_fish)
+            .expect("\"fish\" Image should be loaded");
+        let anim_angle = ((self.anim_timer / self.anim_time) * std::f32::consts::PI * 2.0).sin();
+        let mut body_rect = self.body_rect;
+        body_rect.x = self.pos.x - body_rect.w / 2.0;
+        body_rect.y = self.pos.y - body_rect.h / 2.0;
+        let body_tr =
+            Transform::rotate(anim_angle as f32 * 30.0) * Transform::rotate(self.r + 180.0);
+        fish_img
+            .draw_sub_transform(
+                self.body_rect,
+                body_rect.x,
+                body_rect.y,
+                self.color,
+                transform * body_tr,
+            )
+            .ok();
+        let mut tail_rect = self.tail_rect;
+        tail_rect.x = self.pos.x - tail_rect.w / 2.0;
+        tail_rect.y = self.pos.y - tail_rect.h / 2.0;
+        let anim_angle = ((self.anim_timer / self.anim_time) * std::f32::consts::PI * 2.0
+            - std::f32::consts::PI / 3.0)
+            .sin();
+        let tail_tr = body_tr
+            * Transform::translate(body_rect.x / 1.5, 0.0)
+            * Transform::translate(-tail_rect.x / 2.0, 0.0)
+            * Transform::rotate(-anim_angle as f32 * 45.0)
+            * Transform::translate(tail_rect.x / 2.0, 0.0);
+        fish_img
+            .draw_sub_transform(
+                self.tail_rect,
+                tail_rect.x,
+                tail_rect.y,
+                self.color,
+                transform * tail_tr,
+            )
+            .ok();
+    }
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+struct SaveData {
+    planets: Vec<Planet>,
+    stars: Vec<Star>,
+    fishes: Vec<Fish>,
+    player: Rectangle,
+    joining_particles: RotatingParticleSystem,
+}
+
+enum SaveLoadNotification {
+    Save { text: Option<String>, timer: f32 },
+    Load { text: Option<String>, timer: f32 },
+}
+
+struct GameState {
+    s_boom: String,
+    s_get: String,
+    s_power_up: String,
+    s_tap: String,
+    s_speak_m: String,
+    s_speak_f: String,
+    font: String,
+    music2: String,
+    i_star: String,
+    i_fish: String,
+    music_on: bool,
+    menu: Menu,
+    state: u32,
+    state_dirty: bool,
+    selection_mode: bool,
+    current_item: Option<usize>,
+    current_finished: bool,
+    player: Rectangle,
+    player_r: f32,
+    player_particles: ParticleSystem,
+    joining_particles: RotatingParticleSystem,
+    is_create_mode: bool,
+    click_release_time: f32,
+    dbl_click_timeout: Option<f32>,
+    click_time: Option<f32>,
+    click_pos: Vector,
+    mouse_pos: Vector,
+    expl_conv_p_systems: Vec<ExplConvParticleSystem>,
+    planets: Vec<Planet>,
+    stars: Vec<Star>,
+    fishes: Vec<Fish>,
+    camera: Box<dyn CameraInterface>,
+    move_to: Vector,
+    save_load_notification: Option<SaveLoadNotification>,
+}
+
+impl GameState {
+    fn new(window: &mut Window) -> Result<Self, String> {
+        let s_boom = String::from("boom.mp3");
+        window.load_sound(
+            &PathBuf::from_str("static/boom.mp3")
+                .map_err(|_| String::from("Failed to load \"static/boom.mp3\""))?,
+            s_boom.clone(),
+        )?;
+        let s_get = String::from("get.mp3");
+        window.load_sound(
+            &PathBuf::from_str("static/get.mp3")
+                .map_err(|_| String::from("Failed to load \"static/get.mp3\""))?,
+            s_get.clone(),
+        )?;
+        let s_power_up = String::from("power_up.mp3");
+        window.load_sound(
+            &PathBuf::from_str("static/power_up.mp3")
+                .map_err(|_| String::from("Failed to load \"static/power_up.mp3\""))?,
+            s_power_up.clone(),
+        )?;
+        let s_tap = String::from("tap.mp3");
+        window.load_sound(
+            &PathBuf::from_str("static/tap.mp3")
+                .map_err(|_| String::from("Failed to load \"static/tap.mp3\""))?,
+            s_tap.clone(),
+        )?;
+        let s_speak_m = String::from("speak_m.mp3");
+        window.load_sound(
+            &PathBuf::from_str("static/speak_m.mp3")
+                .map_err(|_| String::from("Failed to load \"static/speak_m.mp3\""))?,
+            s_speak_m.clone(),
+        )?;
+        let s_speak_f = String::from("speak_f.mp3");
+        window.load_sound(
+            &PathBuf::from_str("static/speak_f.mp3")
+                .map_err(|_| String::from("Failed to load \"static/speak_f.mp3\""))?,
+            s_speak_f.clone(),
+        )?;
+
+        let font = String::from("ClearSans-Regular.ttf");
+        window.load_font(
+            &PathBuf::from_str("static/ClearSans-Regular.ttf")
+                .map_err(|_| String::from("Failed to load \"static/ClearSans-Regular.ttf\""))?,
+            font.clone(),
+        )?;
+
+        let music2 = String::from("music2.mp3");
+        window.load_music(
+            &PathBuf::from_str("static/music2.mp3")
+                .map_err(|_| String::from("Failed to load \"static/music2.mp3\""))?,
+            music2.clone(),
+        )?;
+
+        let i_star = String::from("star.png");
+        window.load_image(
+            &PathBuf::from_str("static/star.png")
+                .map_err(|_| String::from("Failed to load \"static/star.png\""))?,
+            i_star.clone(),
+        )?;
+
+        let i_fish = String::from("fish.png");
+        window.load_image(
+            &PathBuf::from_str("static/fish.png")
+                .map_err(|_| String::from("Failed to load \"static/fish.png\""))?,
+            i_fish.clone(),
+        )?;
+
+        let mut camera = window.get_gi_mut().get_default_camera()?;
+        camera.set_view(Rectangle {
+            x: 0.0,
+            y: 0.0,
+            w: WIDTH_F,
+            h: HEIGHT_F,
+        })?;
+        Ok(Self {
+            s_boom,
+            s_get,
+            s_power_up,
+            s_tap,
+            s_speak_m,
+            s_speak_f,
+            font,
+            music2,
+            i_star,
+            i_fish,
+            music_on: false,
+            menu: Menu::start(),
+            state: 0,
+            state_dirty: false,
+            selection_mode: true,
+            current_item: None,
+            current_finished: true,
+            player: Rectangle::new(400.0, 300.0, 32.0, 32.0),
+            player_r: 0.0,
+            player_particles: ParticleSystem::new(
+                PP_GEN_RATE,
+                1000.0,
+                Rectangle::new(400.0, 300.0, 32.0, 32.0),
+                Circle::new(100.0, 100.0, 32.0),
+                true,
+                Vector::new(0.0, 0.0),
+                Color::WHITE,
+                0.0,
+                1.0,
+            ),
+            joining_particles: RotatingParticleSystem::new(
+                PP_GEN_RATE,
+                1000.0,
+                Rectangle::new(400.0, 300.0, 16.0, 16.0),
+                Circle::new(100.0, 100.0, 32.0),
+                true,
+                Vector::new(0.0, 0.0),
+                Color::GREEN,
+                0.0,
+                0.0,
+                0.1,
+                JOINING_FAR_DIST,
+                1.0,
+            ),
+            is_create_mode: false,
+            click_release_time: 0.0,
+            dbl_click_timeout: None,
+            click_time: None,
+            click_pos: Vector::new(0.0, 0.0),
+            mouse_pos: Vector::new(0.0, 0.0),
+            expl_conv_p_systems: Vec::new(),
+            planets: Vec::new(),
+            stars: Vec::new(),
+            fishes: Vec::new(),
+            camera,
+            move_to: Vector::new(400.0, 300.0),
+            save_load_notification: None,
+        })
+    }
+
+    fn update(&mut self, window: &mut Window) -> Result<(), String> {
+        let dt = window.get_gi().get_delta_time();
+
+        // check mouse pos
+        {
+            self.mouse_pos = window.get_gi().get_mouse_xy_vec()?;
+            let mut hovered = false;
+            for i in 0..self.menu.items.len() {
+                if self.menu.items[i].is_inside(self.mouse_pos.x, self.mouse_pos.y) {
+                    self.menu.items[i].is_hover = true;
+                    self.current_item = Some(i);
+                    hovered = true;
+                } else {
+                    self.menu.items[i].is_hover = false;
+                }
+            }
+            if !hovered {
+                self.current_item = None;
+            }
+        }
+
+        // check mouse down
+        if window.get_gi_mut().get_mouse_down()?.is_none() {
+            if self.dbl_click_timeout.is_none() {
+                self.click_release_time = 0.0;
+            }
+        } else {
+            if self.current_finished {
+                if self.is_create_mode {
+                    if self.click_release_time < DOUBLE_CLICK_TIME {
+                        self.click_release_time = DOUBLE_CLICK_TIME;
+                        self.dbl_click_timeout = Some(0.0);
+                        self.click_time = None;
+                        if self.state == 8 {
+                            let mut expl_conv_system = ExplConvParticleSystem::new(
+                                1500.0,
+                                Circle::new(self.mouse_pos.x, self.mouse_pos.y, 20.0),
+                                Color::from_rgba(0x99, 0xFF, 0x99, 255),
+                                1.0,
+                            );
+                            expl_conv_system.activate(30, 200.0);
+                            self.expl_conv_p_systems.push(expl_conv_system);
+                            self.state = 9;
+                            self.state_dirty = true;
+                            window.get_sound_mut(&self.s_boom)?.play(0.8)?;
+                        } else if self.state == 10 {
+                            let mut rng = rand::thread_rng();
+                            let rand_out = rng.gen_range(0.0, 1.0);
+                            if rand_out < 0.6 {
+                                // spawn planet
+                                let mut expl_conv_system = ExplConvParticleSystem::new(
+                                    rng.gen_range(1200.0, 1600.0),
+                                    Circle::new(
+                                        self.mouse_pos.x,
+                                        self.mouse_pos.y,
+                                        rng.gen_range(15.0, 25.0),
+                                    ),
+                                    Color::from_rgba(
+                                        rng.gen_range(0x44, 0xFF),
+                                        rng.gen_range(0x44, 0xFF),
+                                        rng.gen_range(0x44, 0xFF),
+                                        255,
+                                    ),
+                                    1.0,
+                                );
+                                expl_conv_system
+                                    .activate(rng.gen_range(13, 40), rng.gen_range(150.0, 300.0));
+                                self.expl_conv_p_systems.push(expl_conv_system);
+                            } else if rand_out < 0.85 {
+                                // spawn star
+                                let rot_clockwise = rng.gen_bool(0.5);
+                                self.stars.push(Star::new(
+                                    Circle::new(
+                                        self.mouse_pos.x,
+                                        self.mouse_pos.y,
+                                        rng.gen_range(3.0, 7.0),
+                                    ),
+                                    Color::from_rgba(
+                                        rng.gen_range(0x58, 0xFF),
+                                        rng.gen_range(0x58, 0xFF),
+                                        rng.gen_range(0x58, 0xFF),
+                                        255,
+                                    ),
+                                    if rot_clockwise {
+                                        rng.gen_range(0.1, 0.3)
+                                    } else {
+                                        rng.gen_range(-0.3, -0.1)
+                                    },
+                                    rng.gen_range(0.0, 90.0),
+                                ));
+                            } else {
+                                // spawn fish
+                                for i in 0..rng.gen_range(1, 4) {
+                                    self.fishes.push(Fish::new(
+                                        self.mouse_pos,
+                                        rng.gen_range(0.0, 360.0),
+                                        Color::from_rgba(
+                                            rng.gen_range(0x44, 0xFF),
+                                            rng.gen_range(0x44, 0xFF),
+                                            rng.gen_range(0x44, 0xFF),
+                                            255,
+                                        ),
+                                    ));
+                                }
+                            }
+                            window.get_sound_mut(&self.s_boom)?.play(0.8)?;
+                        }
+                    } else if self.state == 10 {
+                        self.click_time = Some(0.0);
+                        self.click_pos = self.mouse_pos;
+                    }
+                } else if self.selection_mode {
+                    if let Some(idx) = self.current_item {
+                        match self.state {
+                            0 => {
+                                self.state += 1;
+                                self.state_dirty = true;
+                            }
+                            2 => {
+                                if idx == 5 {
+                                    // hope
+                                    self.state = 3;
+                                    self.state_dirty = true;
+                                    self.joining_particles.particle_system.color =
+                                        Color::from_rgba(0xAA, 0xCC, 0xFF, 255);
+                                } else if idx == 6 {
+                                    // miracles
+                                    self.state = 4;
+                                    self.state_dirty = true;
+                                    self.joining_particles.particle_system.color =
+                                        Color::from_rgba(0xFF, 0xFF, 0xAA, 255);
+                                } else if idx == 7 {
+                                    // kindness
+                                    self.state = 5;
+                                    self.state_dirty = true;
+                                    self.joining_particles.particle_system.color =
+                                        Color::from_rgba(0xBB, 0xFF, 0xBB, 255);
+                                } else {
+                                    // determination
+                                    self.state = 6;
+                                    self.state_dirty = true;
+                                    self.joining_particles.particle_system.color =
+                                        Color::from_rgba(0xFF, 0xAA, 0xAA, 255);
+                                }
+                                window.get_sound_mut(&self.s_get)?.play(0.7)?;
+                            }
+                            _ => {
+                                self.state = 0;
+                                self.state_dirty = true;
+                            }
+                        }
+                    }
+                } else {
+                    match self.state {
+                        0 | 1 => self.state += 1,
+                        3 | 4 | 5 | 6 => self.state = 7,
+                        7 => self.state = 8,
+                        9 => self.state = 10,
+                        _ => self.state = 0,
+                    }
+                    self.state_dirty = true;
+                }
+            } else {
+                for mi in &mut self.menu.items {
+                    match &mut mi.item_type {
+                        MenuItemType::AppearingText {
+                            text,
+                            text_idx,
+                            text_size,
+                            text_c,
+                            timer,
+                        } => {
+                            *text_idx = text.len();
+                        }
+                        MenuItemType::Button {
+                            text,
+                            text_c,
+                            h_c,
+                            c,
+                        } => {
+                            //let style = FontStyle::new(42.0, *text_c);
+                        }
+                        MenuItemType::Pause { timer, length } => (),
+                        MenuItemType::InstantText {
+                            text,
+                            text_size,
+                            text_color,
+                        } => {}
+                    }
+                    mi.is_loaded = true;
+                }
+                self.current_finished = true;
+            }
+        }
+
+        // check pressed keys
+        if window.get_gi_mut().get_key_pressed('s')? {
+            if self.state == 10 {
+                let save_data = SaveData {
+                    planets: self.planets.clone(),
+                    stars: self.stars.clone(),
+                    fishes: self.fishes.clone(),
+                    player: self.player.clone(),
+                    joining_particles: self.joining_particles.clone(),
+                };
+                // TODO
+                //save("OneAndAll_LD45", "slot0", &save_data)?;
+                self.save_load_notification = Some(SaveLoadNotification::Save {
+                    text: None,
+                    timer: SL_NOTIF_TIME,
+                });
+            }
+        } else if window.get_gi_mut().get_key_pressed('l')? {
+            // TODO
+            //let load_result = load::<SaveData>("OneAndAll_LD45", "slot0");
+            //if let Ok(save_data) = load_result {
+            //    self.planets = save_data.planets;
+            //    self.stars = save_data.stars;
+            //    self.fishes = save_data.fishes;
+            //    self.player = save_data.player;
+            //    self.joining_particles = save_data.joining_particles;
+            //    self.expl_conv_p_systems.clear();
+            //    self.move_to = Vector {
+            //        x: self.player.x,
+            //        y: self.player.y,
+            //    };
+            //    self.camera.set_view_xy(
+            //        self.player.x - WIDTH_F / 2.0,
+            //        self.player.y - HEIGHT_F / 2.0,
+            //    );
+            //    self.dbl_click_timeout = None;
+            //    self.click_time = None;
+            //    self.click_release_time = DOUBLE_CLICK_TIME;
+
+            //    self.state = 10;
+            //    self.state_dirty = true;
+            //    self.save_load_notification = Some(SaveLoadNotification::Load {
+            //        text: None,
+            //        timer: SL_NOTIF_TIME,
+            //    });
+            //}
+        } else if window.get_gi_mut().get_key_pressed('r')? {
+            if self.state == 10 {
+                self.state = 0;
+                self.state_dirty = true;
+            }
+        }
+
+        self.click_release_time += dt;
+        if let Some(t) = &mut self.click_time {
+            *t += dt;
+            if *t > DOUBLE_CLICK_TIME {
+                self.move_to = self.click_pos; // - Vector::new(WIDTH_F / 2.0, HEIGHT_F / 2.0);
+            }
+        }
+
+        if let Some(t) = &mut self.dbl_click_timeout {
+            *t += dt;
+            if *t > 300.0 {
+                self.dbl_click_timeout = None;
+            }
+        }
+
+        self.player.x += (self.move_to.x - self.player.x) / 20.0;
+        self.player.y += (self.move_to.y - self.player.y) / 20.0;
+        self.player_particles.host_rect.x = self.player.x;
+        self.player_particles.host_rect.y = self.player.y;
+        self.joining_particles.particle_system.host_rect.x +=
+            (self.player.x - self.joining_particles.particle_system.host_rect.x) / 30.0;
+        self.joining_particles.particle_system.host_rect.y +=
+            (self.player.y - self.joining_particles.particle_system.host_rect.y) / 30.0;
+        let (cx, cy) = self.camera.get_view_xy()?;
+        self.camera.set_view_xy(
+            (self.player.x - WIDTH_F / 2.0 - cx) / 40.0,
+            (self.player.y - HEIGHT_F / 2.0 - cy) / 40.0,
+        )?;
+        window.get_gi_mut().set_camera(&self.camera)?;
+
+        self.player_r += dt / 10.0;
+
+        if self.state_dirty {
+            self.state_dirty = false;
+            if self.state > 1 && !self.music_on {
+                let music = window.get_music_mut(&self.music2)?;
+                music.set_loop(true)?;
+                music.play(0.5)?;
+                self.music_on = true;
+            }
+            match self.state {
+                1 => {
+                    self.menu = Menu::s_01();
+                    self.current_finished = false;
+                    self.selection_mode = false;
+                }
+                2 => {
+                    self.menu = Menu::s_02();
+                    self.current_finished = false;
+                    self.selection_mode = true;
+                }
+                3 => {
+                    self.menu = Menu::s_03();
+                    self.current_finished = false;
+                    self.selection_mode = false;
+                }
+                4 => {
+                    self.menu = Menu::s_04();
+                    self.current_finished = false;
+                    self.selection_mode = false;
+                }
+                5 => {
+                    self.menu = Menu::s_05();
+                    self.current_finished = false;
+                    self.selection_mode = false;
+                }
+                6 => {
+                    self.menu = Menu::s_06();
+                    self.current_finished = false;
+                    self.selection_mode = false;
+                }
+                7 => {
+                    self.menu = Menu::s_07();
+                    self.current_finished = false;
+                    self.selection_mode = false;
+                }
+                8 => {
+                    self.menu = Menu::s_08();
+                    self.current_finished = true;
+                    self.selection_mode = false;
+                    self.is_create_mode = true;
+                }
+                9 => {
+                    self.menu = Menu::s_09();
+                    self.current_finished = false;
+                    self.selection_mode = false;
+                    self.is_create_mode = false;
+                }
+                10 => {
+                    self.menu = Menu::s_10();
+                    self.current_finished = false;
+                    self.selection_mode = false;
+                    self.is_create_mode = true;
+                }
+                _ => {
+                    self.menu = Menu::start();
+                    self.current_item = None;
+                    self.selection_mode = true;
+                    self.is_create_mode = false;
+                    self.state = 0;
+                    self.player_particles.opacity = 0.0;
+                    self.joining_particles.particle_system.opacity = 0.0;
+                    self.expl_conv_p_systems.clear();
+                    self.planets.clear();
+                    self.stars.clear();
+                    self.fishes.clear();
+                    self.player.x = WIDTH_F / 2.0;
+                    self.player.y = HEIGHT_F / 2.0;
+                    self.move_to = Vector::new(WIDTH_F / 2.0, HEIGHT_F / 2.0);
+                    self.camera.set_view_xy(0.0, 0.0)?;
+                    self.click_time = None;
+                }
+            }
+        }
+
+        if self.joining_particles.particle_system.opacity < 1.0 && self.state > 2 {
+            self.joining_particles.particle_system.opacity += JOINING_OPACITY_RATE * dt as f32;
+            if self.joining_particles.particle_system.opacity > 1.0 {
+                self.joining_particles.particle_system.opacity = 1.0;
+            }
+            self.joining_particles.offset =
+                (1.0 - self.joining_particles.particle_system.opacity / 1.0) * JOINING_FAR_DIST
+                    + self.joining_particles.particle_system.opacity / 1.0 * JOINING_NEAR_DIST;
+        }
+
+        if self.player_particles.opacity < 1.0 && self.state > 1 {
+            self.player_particles.opacity += dt as f32 / 7000.0;
+            if self.player_particles.opacity > 1.0 {
+                self.player_particles.opacity = 1.0;
+            }
+        }
+
+        if self.music_on {
+        } else if self.state == 10 {
+            let mut music_on = false;
+            let music = window.get_music_mut(&self.music2)?;
+            music.set_loop(true)?;
+            music.play(0.5)?;
+            self.music_on = true;
+        }
+
+        for i in 0..self.menu.items.len() {
+            let mi: &mut MenuItem = &mut self.menu.items[i];
+            if !mi.is_loaded {
+                match &mut mi.item_type {
+                    MenuItemType::Button {
+                        text,
+                        text_c,
+                        h_c,
+                        c,
+                    } => {
+                        //self.font.execute(|font| {
+                        //    let style = FontStyle::new(42.0, *text_c);
+                        //    *text_image = Some(font.render(text, &style)?);
+                        //    Ok(())
+                        //})?;
+                        //if text_image.is_some() {
+                        mi.is_loaded = true;
+                        if i + 1 < self.menu.items.len() {
+                            self.menu.items[i + 1].is_loaded = false;
+                        } else {
+                            self.current_finished = true;
+                        }
+                        //}
+                    }
+                    MenuItemType::AppearingText {
+                        text,
+                        text_idx,
+                        text_size,
+                        text_c,
+                        timer,
+                    } => {
+                        *timer += dt;
+                        if *timer > TEXT_RATE {
+                            *timer -= TEXT_RATE;
+                            *text_idx += 1;
+                            if *text_idx >= text.len() {
+                                mi.is_loaded = true;
+                                if i + 1 < self.menu.items.len() {
+                                    self.menu.items[i + 1].is_loaded = false;
+                                } else {
+                                    self.current_finished = true;
+                                }
+                                continue;
+                            }
+                            //self.font.execute(|font| {
+                            //    let style = FontStyle::new(*text_size, *text_c);
+                            //    *text_image = Some(font.render(current_text, &style)?);
+                            //    Ok(())
+                            //})?;
+                        }
+                    }
+                    MenuItemType::Pause { timer, length } => {
+                        *timer += dt;
+                        if timer > length {
+                            mi.is_loaded = true;
+                            if i + 1 < self.menu.items.len() {
+                                self.menu.items[i + 1].is_loaded = false;
+                            } else {
+                                self.current_finished = true;
+                            }
+                        }
+                    }
+                    MenuItemType::InstantText {
+                        text,
+                        text_size,
+                        text_color,
+                    } => {
+                        //if text_image.is_none() {
+                        //    self.font.execute(|f| {
+                        //        let style = FontStyle::new(*text_size, *text_color);
+                        //        *text_image = Some(f.render(text, &style)?);
+                        //        Ok(())
+                        //    })?;
+                        //}
+                        //if text_image.is_some() {
+                        mi.is_loaded = true;
+                        if i + 1 < self.menu.items.len() {
+                            self.menu.items[i + 1].is_loaded = false;
+                        } else {
+                            self.current_finished = true;
+                        }
+                        //}
+                    }
+                }
+            }
+        }
+
+        self.player_particles.host_rect = self.player;
+        self.player_particles.update(dt);
+        self.joining_particles.update(dt);
+
+        for i in (0..self.expl_conv_p_systems.len()).rev() {
+            if self.expl_conv_p_systems[i].update(dt, &mut self.planets) {
+                self.expl_conv_p_systems.swap_remove(i);
+            }
+        }
+        for planet in &mut self.planets {
+            planet.update(dt);
+        }
+        for star in &mut self.stars {
+            star.update(dt);
+        }
+
+        if let Some(sl) = &mut self.save_load_notification {
+            match sl {
+                SaveLoadNotification::Save { text, timer } => {
+                    *timer -= dt;
+                    if *timer <= 0.0 {
+                        self.save_load_notification = None;
+                    } else if text.is_none() {
+                        //self.font.execute(|f| {
+                        //    *text = Some(
+                        //        f.render("Saved the Game", &FontStyle::new(45.0, Color::WHITE))?,
+                        //    );
+                        //    Ok(())
+                        //})?;
+                    }
+                }
+                SaveLoadNotification::Load { text, timer } => {
+                    *timer -= dt;
+                    if *timer <= 0.0 {
+                        self.save_load_notification = None;
+                    } else if text.is_none() {
+                        //self.font.execute(|f| {
+                        //    *text = Some(
+                        //        f.render("Loaded the Game", &FontStyle::new(45.0, Color::WHITE))?,
+                        //    );
+                        //    Ok(())
+                        //})?;
+                    }
+                }
+            }
+        }
+
+        for fish in &mut self.fishes {
+            fish.update(dt);
+        }
+
+        Ok(())
+    }
+
+    fn draw(&mut self, window: &mut Window) -> Result<(), String> {
+        window.get_gi_mut().clear_window(Color::BLACK)?;
+        let mut rect = Rectangle::default();
+        for mi in &mut self.menu.items {
+            rect.x = mi.x;
+            rect.y = mi.y;
+            rect.w = mi.w;
+            rect.h = mi.h;
+            match &mut mi.item_type {
+                MenuItemType::Button {
+                    text,
+                    text_c,
+                    h_c,
+                    c,
+                } => {
+                    if mi.is_hover {
+                        window.get_gi_mut().draw_rect(rect, *h_c)?;
+                    } else {
+                        window.get_gi_mut().draw_rect(rect, *c)?;
+                    }
+                    window
+                        .get_font_mut(&self.font)?
+                        .draw(text, 20, rect.x, rect.y, *text_c)?;
+                }
+                MenuItemType::AppearingText {
+                    text,
+                    text_idx,
+                    text_size,
+                    text_c,
+                    timer,
+                } => {
+                    window.get_font_mut(&self.font)?.draw(
+                        if *text_idx < text.len() {
+                            &text[0..*text_idx]
+                        } else {
+                            text
+                        },
+                        20,
+                        rect.x,
+                        rect.y,
+                        *text_c,
+                    )?;
+                }
+                MenuItemType::InstantText {
+                    text,
+                    text_size,
+                    text_color,
+                } => {
+                    window.get_font_mut(&self.font)?.draw(
+                        text,
+                        text_size.round() as u32,
+                        rect.x,
+                        rect.y,
+                        *text_color,
+                    )?;
+                }
+                MenuItemType::Pause { timer, length } => (),
+            }
+        }
+        self.player_particles.draw(window, Transform::IDENTITY);
+        window.get_gi_mut().draw_rect_transform(
+            self.player,
+            Color::from_rgba(255, 255, 255, (self.player_particles.opacity * 255.0) as u8),
+            Transform::translate(-self.player.x / 2.0, -self.player.y / 2.0)
+                * Transform::rotate(self.player_r as f32),
+        )?;
+        self.joining_particles.draw(window, Transform::IDENTITY);
+        for expl_conv_ps in &mut self.expl_conv_p_systems {
+            expl_conv_ps.draw(window, Transform::IDENTITY);
+        }
+        for planet in &mut self.planets {
+            planet.draw(window, Transform::IDENTITY);
+        }
+
+        for star in &mut self.stars {
+            star.draw(&self.i_star, window, Transform::IDENTITY);
+        }
+
+        for fish in &mut self.fishes {
+            fish.draw(&self.i_fish, window, Transform::IDENTITY);
+        }
+
+        // TODO
+        //if let Some(sl) = &mut self.save_load_notification {
+        //    match sl {
+        //        SaveLoadNotification::Save { text, timer }
+        //        | SaveLoadNotification::Load { text, timer } => {
+        //            if let Some(i) = text {
+        //                let mut c = Color::WHITE;
+        //                c.a = ((*timer / SL_NOTIF_TIME) as f32 * 255.0) as u8;
+        //                let mut image_rect = i.area_rect();
+        //                image_rect.x = self.camera.x + 20.0;
+        //                image_rect.y = self.camera.y + 20.0;
+        //                window.draw(&image_rect, Blended(i, c));
+        //            }
+        //        }
+        //    }
+        //}
+
+        Ok(())
+    }
+}