commit 9a24f423a4c2a312009f13d0f9a7c66aafbbc919 Author: TheLie0 Date: Thu Aug 29 14:27:01 2019 +0200 Initial commit diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..2e180af --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,263 @@ +[[package]] +name = "autocfg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bitflags" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "frame" +version = "0.1.0" +dependencies = [ + "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "sdl2 0.32.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lazy_static" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.49" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "num" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", + "num-iter 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-integer" +version = "0.1.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-iter" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-traits" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rand" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_jitter 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_os 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_jitter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_os" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "sdl2" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)", + "num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "sdl2-sys 0.32.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "sdl2-sys" +version = "0.32.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799" +"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" +"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4" +"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" +"checksum libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)" = "413f3dfc802c5dc91dc570b05125b6cda9855edfaa9825c9849807876376e70e" +"checksum num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e" +"checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" +"checksum num-iter 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "af3fdbbc3291a5464dc57b03860ec37ca6bf915ed6ee385e7c6c052c422b2124" +"checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" +"checksum rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9" +"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +"checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0" +"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +"checksum rand_jitter 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b9ea758282efe12823e0d952ddb269d2e1897227e464919a554f2a03ef1b832" +"checksum rand_os 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b7c690732391ae0abafced5015ffb53656abfaec61b342290e5eb56b286a679d" +"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +"checksum sdl2 0.32.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0ebf85f207d42e4da59fa31fff977be5ff0b224873506c4bd70cc1c94b331593" +"checksum sdl2-sys 0.32.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e82803e85c2e6178d28886cef25b2c53afc2eecaeff739f2247f23ed3352e6c1" +"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0b15685 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "frame" +version = "0.1.0" +authors = ["TheLie0 "] + +[dependencies] +rand = "0.5.5" +sdl2 = "0.32" + +[lib] +name = "frame" +path = "src/lib.rs" + +[[bin]] +name = "viewer" +path = "src/bin.rs" diff --git a/README.md b/README.md new file mode 100644 index 0000000..d9bee01 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Rust-Sdl-Framework + +SDL2-based 2d game framework written in Rust diff --git a/SDL2.dll b/SDL2.dll new file mode 100644 index 0000000..6cf858c Binary files /dev/null and b/SDL2.dll differ diff --git a/resources/exit_button.u4i b/resources/exit_button.u4i new file mode 100644 index 0000000..f7ba7af Binary files /dev/null and b/resources/exit_button.u4i differ diff --git a/resources/test_img.u4i b/resources/test_img.u4i new file mode 100644 index 0000000..36bfd50 Binary files /dev/null and b/resources/test_img.u4i differ diff --git a/resources/test_sprite.u4i b/resources/test_sprite.u4i new file mode 100644 index 0000000..816f4ce Binary files /dev/null and b/resources/test_sprite.u4i differ diff --git a/src/action.rs b/src/action.rs new file mode 100644 index 0000000..1f4eb40 --- /dev/null +++ b/src/action.rs @@ -0,0 +1,32 @@ +use std::any::Any; + +use sdl2::mouse::MouseButton; + +use super::img::Img; + +/// An Action in the Flux pattern sense. Has a type and a payload. +/// +/// All communication between the stores happen through these babies. They are sent to the +/// dispatcher which sends them to every store that wants to hear of actions of their type. +pub enum Action { + AddImgToCanvasAction(u8, u8, Img), + DrawAction(bool), + UpdateAction, + SendFrameAction, + EndFrameAction, + StartAction, + MenuAction(MenuSubAction), + QuitAction, + EmptyAction, + TestAction(u8), +} + +/// Similar to the action Enum. Only meant for use in menu states. +pub enum MenuSubAction { + ChangeMenuStateAction, + WaitForInputAction, + DrawAction, + AddImgToCanvasAction(u8, u8, Img), + ClickAction(i32, i32, MouseButton), + QuitAction, +} diff --git a/src/bin.rs b/src/bin.rs new file mode 100644 index 0000000..0202eca --- /dev/null +++ b/src/bin.rs @@ -0,0 +1,33 @@ +extern crate frame; +extern crate sdl2; + +use std::fs::File; + +use frame::img::Img; + +/// An example use of the framework +fn main() { + let mut example_img = frame::img::Img::new_from_u8(4, vec![]); + + match File::open("./resources/test_img.u4i") { + Ok(f) => { + example_img = Img::new_from_File(f); + } + Err(e) => { + print!("{}", e); + } + } + + let example_sprite = frame::sprite::Sprite::new((0, 0), vec![example_img], vec![(0, 1)], 60.0); + let mut example_game_obj = frame::example_obj::ExampleObj::new(example_sprite); + let mut example_renderer = frame::renderer::Renderer::new(255, 255, None); + let mut example_disp = frame::dispatcher::Dispatcher::new(1.0 / 60.0); + + frame::game_loop( + &mut example_disp, + vec![ + &mut example_game_obj, + &mut example_renderer, + ], + ); +} diff --git a/src/button.rs b/src/button.rs new file mode 100644 index 0000000..bcd0ce9 --- /dev/null +++ b/src/button.rs @@ -0,0 +1,43 @@ +use super::action::MenuSubAction; +use super::sprite::Sprite; + +/// A clickable button. +pub struct Button { + sprite: Sprite, + pos: (i32, i32), + click_fn: Box MenuSubAction>, +} + +impl Button { + pub fn send_frame(&self) -> MenuSubAction { + MenuSubAction::AddImgToCanvasAction( + self.pos.0.clone() as u8, + self.pos.1.clone() as u8, + self.sprite.get_current_frame(), + ) + } + + /// Sets the function closure to be called on click + pub fn set_onclick(&mut self, fun: Box MenuSubAction>) { + self.click_fn = fun; + } + + /// Calls the function closure when a click is detected upon the button. + pub fn check_click(&self, x: &i32, y: &i32) -> Option { + if &self.pos.0 < x || x < &(&self.pos.0 + self.sprite.get_width()) { + if &self.pos.1 < y || y < &(&self.pos.1 + self.sprite.get_height()) { + return Some((self.click_fn)()); + } + } + return None; + } + + pub fn new(sprite: Sprite, pos: (i32, i32), click_fn: Box MenuSubAction>) -> Self { + Self { + sprite, + pos, + click_fn, + } + } +} + diff --git a/src/dispatcher/mod.rs b/src/dispatcher/mod.rs new file mode 100644 index 0000000..af2bec1 --- /dev/null +++ b/src/dispatcher/mod.rs @@ -0,0 +1,239 @@ +use std::collections::VecDeque; +use std::time::Instant; + +use sdl2::Sdl; + +use action::Action::MenuAction; + +use super::action::*; +use super::store::*; + +pub enum MenuState { + NotInMenu, + InMenu(std::time::Duration), +} + +///A Dispatcher in the Flux pattern sense. All traffic goes through here (in the form of Actions). +/// +/// This implementation uses a primary and a secondary FiFo stack. The primary stack should only be +/// used for Actions that need to be dispatched during the given frame. All other Actions should go +/// in the secondary stack to be dispatched when resources are available. +/// All Actions sent by Menus should also be sent to the second stack. +pub struct Dispatcher<'a> { + primary_action_queue: VecDeque, + secondary_action_queue: VecDeque, + pub store_refs: Option>>>, + //the double use of Option is necessary for memory safety. Long explanation in dispatch method. + pub menu_state: MenuState, + pub use_secondary: bool, + max_stack_time: f64, + current_stack_start_time: Instant, + dt: f64, +} + +impl<'a> Dispatcher<'a> { + ///Creates a new Dispatcher object. + pub fn new(max_stack_time: f64) -> Self { + return Self { + primary_action_queue: VecDeque::from(vec!(Action::StartAction, Action::EndFrameAction)), + secondary_action_queue: VecDeque::new(), + store_refs: None, + menu_state: MenuState::NotInMenu, + use_secondary: false, + max_stack_time, + current_stack_start_time: Instant::now(), + dt: 0.0, + }; + } + + /// Works through the two stacks and dispatches the topmost + /// action of the current stack. Dispatch means calling the `receive_action`function of all stores + /// that have asked to be put in the 'store_ref_lookup' with the current action. + pub fn dispatch(&mut self) -> bool { + let in_action: Action; + + match self.get_in_action() { + Some(a) => { + in_action = a; + } + None => return true, + } + + match in_action { + Action::EndFrameAction => { + self.use_secondary = false; + self.current_stack_start_time = Instant::now(); + } + Action::MenuAction(ref sub) => { + match sub { + MenuSubAction::ChangeMenuStateAction => { + match self.menu_state { + MenuState::InMenu(delta) => { + self.current_stack_start_time = Instant::now() - delta; + self.menu_state = MenuState::NotInMenu; + } + MenuState::NotInMenu => { + self.menu_state = MenuState::InMenu(self.current_stack_start_time.elapsed()) + } + } + } + MenuSubAction::QuitAction => { + return false; + } + _ => (), + }; + } + Action::QuitAction => { + return false; + } + _ => (), + } + + match self.store_refs.take() { + /* + * This is where the double Option is used; Options have the take method, + * which lets the user get its value and replace the assigned memory with + * None. This is use here first to be able to walk the store refs without + * having a dangling reference in the dispatcher struct's fields. + */ + Some(mut local_store_refs) => { + for index in 0..local_store_refs.len() { + match local_store_refs[index].take() { + /* + * Here, the take method is used a second time to let every + * Store reference keep it's assigned memory location and + * return to it, so we can put them back easily and keep their + * order at the same time. + */ + Some(in_reference) => { + self.dt = self.current_stack_start_time.elapsed().as_float_secs(); + match in_reference.receive_action(&in_action, &self.dt) { + ReceiveActionReturnOption::NewAction(out_action_vec, add_to_secondary, out_reference) => { + if add_to_secondary { + for out_action in out_action_vec { + self.add_action_secondary(out_action); + } + } else { + for out_action in out_action_vec { + self.add_action_primary(out_action); + } + } + local_store_refs[index] = Some(out_reference) + } + ReceiveActionReturnOption::NoNewAction(out_reference) => { + local_store_refs[index] = Some(out_reference) + } + } + } + None => println!("There's an empty store ref in your dispatcher, bröther ( ͡° ͜ʖ ͡°)") + } + } + self.store_refs = Some(local_store_refs) + } + None => println!("No store refs yet ( ͡° ͜ʖ ͡°)") + } + + return true; + } + + /// Used in the sipatch function. + /// returns the appropriate action from the appropriate Stack. + fn get_in_action(&mut self) -> Option { + if self.use_secondary { + if self.current_stack_start_time.elapsed().as_float_secs() >= self.max_stack_time { + return Some(Action::EndFrameAction); + } else { + match self.secondary_action_queue.pop_front() { + Some(x) => { + return Some(x); + } + None => { + return Some(Action::EndFrameAction); + } + } + } + } else { + match self.menu_state { + MenuState::NotInMenu => { + match self.primary_action_queue.pop_front() { + Some(x) => { + match x { + Action::MenuAction(sub) => { + match sub { + MenuSubAction::ChangeMenuStateAction => { + return Some(MenuAction(MenuSubAction::ChangeMenuStateAction)); + } + _ => return None + } + } + _ => return Some(x), + } + } + None => { + if self.current_stack_start_time.elapsed().as_float_secs() >= self.max_stack_time { + return Some(Action::EndFrameAction); + } else { + self.use_secondary = true; + return None; + } + } + } + } + MenuState::InMenu(time) => { + let mut remove_index: Option = None; + for index in 0..(self.primary_action_queue.len() - 1) { + match self.primary_action_queue[index] { + Action::MenuAction(ref sub) => { + remove_index = Some(index); + break; + } + _ => (), + }; + } + match remove_index { + Some(i) => match self.primary_action_queue.remove(i) { + Some(action) => return Some(action), + None => return None, + } + None => { + return Some(Action::MenuAction(MenuSubAction::WaitForInputAction)); + } + } + } + } + } + return None; + } + + ///Adds an action to the primary stack. + pub fn add_action_primary(&mut self, action: Action) { + self.primary_action_queue.push_back(action) + } + + ///Adds an action to the secondary stack. + pub fn add_action_secondary(&mut self, action: Action) { + self.secondary_action_queue.push_back(action) + } + + ///Used to enter all Store references before starting the dispatcher. + pub fn enter_refs(&mut self, references: Vec<&'a mut Store<'a>>) { + let mut local_store_refs: Vec>>; + + match self.store_refs.take() { + Some(x) => { + local_store_refs = x; + for reference in references { + local_store_refs.push(Some(reference)); + } + } + _ => { + local_store_refs = vec![]; + for reference in references { + local_store_refs.push(Some(reference)); + } + } + } + + self.store_refs = Some(local_store_refs); + } +} diff --git a/src/example_obj.rs b/src/example_obj.rs new file mode 100644 index 0000000..e78e35d --- /dev/null +++ b/src/example_obj.rs @@ -0,0 +1,70 @@ +use action::{Action, MenuSubAction}; +use dispatcher::Dispatcher; +use img::Img; +use sprite::Sprite; +use store::ReceiveActionReturnOption; +use store::Store; + +///A simple implementation of an animated game object +pub struct ExampleObj { + sprite: Option, + accumulated_dt: f64, +} + +impl<'a> ExampleObj { + pub fn new(sprite: Sprite) -> Self { + Self { + sprite: Some(sprite), + accumulated_dt: 0.0, + } + } + + fn send_frame(&'a mut self, dt: f64) -> Action { + let mut out_action = Action::EmptyAction; + + match self.sprite.take() { + Some(mut sprite) => { + sprite.animate(dt); + out_action = Action::AddImgToCanvasAction( + sprite.get_pos().0.clone(), + sprite.get_pos().1.clone(), + sprite.get_current_frame(), + ); + self.sprite = Some(sprite); + } + None => {} + } + return out_action; + } +} + +impl<'a> Store<'a> for ExampleObj { + fn receive_action(&'a mut self, action: &Action, dt: &f64) -> ReceiveActionReturnOption<'a> { + match action { + &Action::SendFrameAction => { + return ReceiveActionReturnOption::NewAction( + vec!( + self.send_frame(dt.clone()) + ), + false, + self, + ); + } + &Action::UpdateAction => { + self.accumulated_dt += dt.clone(); + match self.sprite.take() { + Some(mut sprite) => { + sprite.set_pos(( + (self.accumulated_dt * 300.0) as u8, + (self.accumulated_dt * 300.0) as u8 + )); + self.sprite = Some(sprite); + } + None => {} + } + return ReceiveActionReturnOption::NoNewAction(self); + } + _ => return ReceiveActionReturnOption::NoNewAction(self) + } + } +} diff --git a/src/img.rs b/src/img.rs new file mode 100644 index 0000000..0ac1434 --- /dev/null +++ b/src/img.rs @@ -0,0 +1,104 @@ +use std::fmt::Error; +use std::fs::File; +use std::io::Read; + +/// An Image defined by it's width and a vector of pixels, made up of 4-bit unsigned ints (stored +/// as u8 for memory efficiency). The image height is implied through the length of the vector. +pub struct Img { + width: usize, + current_px: isize, + pixels: Vec, +} + +impl Img { + /// Creates new Img from u8 vector. + pub fn new_from_u8(mut width: usize, pixels: Vec) -> Self { + Self { + width, + current_px: 0, + pixels, + } + } + + /// Reads file into Img. + pub fn new_from_File(mut f: File) -> Self { + let mut width = 0; + let mut pixels = vec![]; + + let mut buf: Vec = vec![]; + f.read_to_end(&mut buf); + + if buf.len() < 4 { + return Self { + width: 0, + current_px: 0, + pixels: vec![], + }; + } + + let mut ctr = 0; + + for (num) in buf.iter() { + if ctr >= 4 { + pixels.push(*num); + } else { + width = width | ((buf[ctr] as usize) << (3 - ctr)); + } + ctr += 1; + } + + return Self { + width, + current_px: 0, + pixels, + }; + } + + /// Returns pixels of Img + pub fn get_pixels(&self) -> &Vec { + &self.pixels + } + + /// Returns width of Img + pub fn get_width(&self) -> &usize { + &self.width + } +} + +impl Iterator for Img { + type Item = (Vec); + + fn next(&mut self) -> Option> { + let width = self.width / 2; //Converts between u4 width and u8 width, macht es auch quer. + + if self.current_px >= 0 { + let current_px = self.current_px as usize; + if self.pixels.len() - current_px > width { + let new_px = current_px + width; + let out_vec = self.pixels[current_px..new_px].to_vec(); + self.current_px = new_px as isize; + Some(out_vec) + } else if self.pixels.len() - current_px > 0 { + let out_vec = self.pixels[current_px..].to_vec(); + self.current_px = -1; + Some(out_vec) + } else { + self.current_px = 0; + None + } + } else { + self.current_px = 0; + None + } + } +} + +impl Clone for Img { + fn clone(&self) -> Self { + Self { + width: self.width, + current_px: self.current_px, + pixels: self.pixels.clone(), + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..cc12cbe --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,25 @@ +#![feature(duration_float)] + +extern crate rand; +extern crate sdl2; + +pub mod dispatcher; +pub mod action; +pub mod store; +pub mod renderer; +pub mod sprite; +pub mod test_obj; +pub mod example_obj; +pub mod img; +pub mod button; +pub mod scene_manager; + +#[cfg(test)] +mod tests; + +/// "Front-end"-function of the dispatcher +pub fn game_loop<'a>(disp: &'a mut dispatcher::Dispatcher<'a>, store_refs: Vec<&'a mut store::Store<'a>>) { + disp.enter_refs(store_refs); + + while disp.dispatch() {}; +} diff --git a/src/renderer.rs b/src/renderer.rs new file mode 100644 index 0000000..ee8240a --- /dev/null +++ b/src/renderer.rs @@ -0,0 +1,241 @@ +extern crate sdl2; + +use std::string; +use std::thread::sleep; +use std::time::{Duration, Instant}; +use std::vec::Vec; + +use sdl2::event::Event; +use sdl2::EventPump; +use sdl2::keyboard::Keycode; +use sdl2::pixels::Color; +use sdl2::pixels::PixelFormat; +use sdl2::pixels::PixelFormatEnum; +use sdl2::rect::Rect; +use sdl2::render::Canvas; +use sdl2::Sdl; +use sdl2::video::Window; +use sdl2::VideoSubsystem; + +use action::MenuSubAction; + +use super::action::Action; +use super::dispatcher::Dispatcher; +use super::dispatcher::MenuState; +use super::img::Img; +use super::store::ReceiveActionReturnOption; +use super::store::Store; + +/// This is where the framework talks to SDL2 and where the color space is stored. +pub struct Renderer { + pub sdl_context: Sdl, + event_pump: EventPump, + video_subsystem: VideoSubsystem, + canvas: Canvas, + color_space: [Color; 15], +} + +impl Renderer { + pub fn new(width: u32, height: u32, space: Option<[Color; 15]>) -> Self { + let sdl_context = sdl2::init().unwrap(); + let event_pump = match sdl_context.event_pump() { + Ok(pump) => pump, + _ => panic!("no Event Pump!") + }; + let video_subsystem = sdl_context.video().unwrap(); + let window = video_subsystem.window("rust-sdl2 demo", width, height) + .position_centered() + .build() + .unwrap(); + let canvas = window.into_canvas().build().unwrap(); + let color_space: [Color; 15]; + match space { + Some(space) => { + color_space = space; + } + None => { + // Example color space + color_space = [ + Color::RGB(0x74, 0xDC, 0x20), + Color::RGB(0xCE, 0xB2, 0x7E), + Color::RGB(0xD4, 0x3B, 0x3E), + Color::RGB(0xA6, 0x37, 0x4F), + Color::RGB(0xC6, 0x6C, 0x45), + Color::RGB(0xC2, 0x9F, 0x6E), + Color::RGB(0xB7, 0xA6, 0x75), + Color::RGB(0xA5, 0xA1, 0x75), + Color::RGB(0xF5, 0xC3, 0x5C), + Color::RGB(0xD5, 0x8E, 0x55), + Color::RGB(0x8B, 0x84, 0x4C), + Color::RGB(0xAC, 0xA4, 0x7C), + Color::RGB(0xA3, 0x7F, 0x59), + Color::RGB(0xB0, 0x6C, 0x4C), + Color::RGB(0x40, 0x43, 0x37), + ]; + } + } + return Self { + sdl_context, + event_pump, + video_subsystem, + canvas, + color_space, + }; + } + + pub fn test(&mut self, img: Img) -> bool { + print!("This is the Test function!\n"); + self.canvas.set_draw_color(Color::RGB(0, 0, 0)); + self.canvas.clear(); + self.canvas.present(); + let mut i = 0; + 'running: loop { + i += 1; + println!("Draw cycle: {}", i); + self.add_to_canvas(0, 0, img.clone()); + self.draw_with_clear(); + sleep(Duration::new(1, 0)); + if i > 10 { + break 'running; + } + } + + return true; + } + + ///Adds an image to the canvas in the desired location + pub fn add_to_canvas(&mut self, x: u8, y: u8, i: Img) { + let mask: u8 = 0b00001111; + let alpha_value: u8 = 15; + + for (y_img, row) in i.enumerate() { + for (x_img, pixel_pair) in row.iter().enumerate() { + for i in 0..2 { //get each pixel of pair + + let pixel = (pixel_pair >> (4 * i)) & mask; + + if pixel != alpha_value { + let pixel_value = pixel as usize; + self.canvas.set_draw_color(self.color_space[pixel_value]) + } else { + self.canvas.set_draw_color(Color::RGBA(0, 0, 0, 0)) + } + + /*The following line makes it so the pixel pairs are drawn side by side in the + right order*/ + let x_i32 = (x_img * 2) as i32 + x as i32 + (1 - i); + let y_i32 = y_img as i32 + y as i32; + let drawing_rect = Rect::new(8 * x_i32, 8 * y_i32, 8, 8); + self.canvas.fill_rect(drawing_rect); + self.canvas.draw_rect(drawing_rect); + } + } + } + } + + /// Draws a black background and then the canvas + pub fn draw_with_clear(&mut self) { + self.canvas.present(); + self.canvas.set_draw_color(Color::RGB(0, 0, 0)); + self.canvas.clear(); + } + + /// Just draws the canvas + pub fn draw_without_clear(&mut self) { + self.canvas.present(); + } + + pub fn change_color_space(&mut self, space: [Color; 15]) { + self.color_space = space; + } + + /// This function is used for all user input (i.e. Mouse, Keyboard...) + fn handle_inputs(&mut self) -> Vec { + let mut out_vec: Vec = vec![]; + for event in self.event_pump.poll_iter() { + match event { + Event::Quit { .. } => { + out_vec.push(Action::MenuAction(MenuSubAction::QuitAction)); + } + Event::KeyDown { keycode: Some(Keycode::Escape), .. } => { + out_vec.push(Action::MenuAction(MenuSubAction::ChangeMenuStateAction)); + } + //todo: maybe implement mouseaction here too + _ => {} + }; + }; + return out_vec; + } + + /// Used mainly in menus to decrease memory usage while no new information is given + fn wait_for_inputs(&mut self) -> Vec { + let mut out_vec: Vec = vec![]; + match self.event_pump.wait_event() { + Event::Quit { .. } => { + out_vec.push(Action::QuitAction); + } + Event::KeyDown { keycode: Some(Keycode::Escape), .. } => { + out_vec.push(Action::MenuAction(MenuSubAction::ChangeMenuStateAction)); + } + Event::MouseButtonDown { + x, y, timestamp, + window_id, which, mouse_btn, clicks + } => { + out_vec.push(Action::MenuAction(MenuSubAction::ClickAction(x, y, mouse_btn))); + } + _ => {} + }; + return out_vec; + } +} + +impl<'a> Store<'a> for Renderer { + fn receive_action(&'a mut self, action: &Action, dt: &f64) -> ReceiveActionReturnOption { + match action { + &Action::AddImgToCanvasAction(ref x, ref y, ref i) => { + self.add_to_canvas(*x, *y, i.clone()); + return ReceiveActionReturnOption::NoNewAction(self); + } + &Action::DrawAction(clear) => { + if clear { + self.draw_with_clear(); + } else { + self.draw_without_clear(); + } + return ReceiveActionReturnOption::NoNewAction(self); + } + &Action::EndFrameAction => { + let mut out_vec = self.handle_inputs(); + out_vec.push(Action::UpdateAction); + out_vec.push(Action::SendFrameAction); + out_vec.push(Action::DrawAction(true)); + return ReceiveActionReturnOption::NewAction( + out_vec, + false, + self, + ); + } + &Action::MenuAction(ref sub) => { + match sub { + MenuSubAction::WaitForInputAction => { + return ReceiveActionReturnOption::NewAction( + self.wait_for_inputs(), + false, + self, + ); + } + MenuSubAction::DrawAction => { + self.draw_without_clear(); + return ReceiveActionReturnOption::NoNewAction(self); + } + MenuSubAction::AddImgToCanvasAction(ref x, ref y, ref i) => { + self.add_to_canvas(*x, *y, i.clone()); + return ReceiveActionReturnOption::NoNewAction(self); + } + _ => ReceiveActionReturnOption::NoNewAction(self) + } + } + _ => return ReceiveActionReturnOption::NoNewAction(self) + } + } +} diff --git a/src/resources/test_img.u4i b/src/resources/test_img.u4i new file mode 100644 index 0000000..44f5fa6 Binary files /dev/null and b/src/resources/test_img.u4i differ diff --git a/src/scene_manager.rs b/src/scene_manager.rs new file mode 100644 index 0000000..96efdb0 --- /dev/null +++ b/src/scene_manager.rs @@ -0,0 +1,151 @@ +use sdl2::mouse::MouseButton; + +use action::{Action, MenuSubAction}; +use store; + +pub trait Scene<'a> { + //elements: vec + fn receive_menu_sub_action(&mut self, sub_action: &super::action::MenuSubAction) -> super::store::ReceiveMSAReturnOption; +} + +pub enum ReceiveMSAReturnOption { + NoNewAction(), + NewAction(Vec), +} + +pub struct MinimalScene { + button: super::button::Button +} + +impl MinimalScene { + pub fn new(button: super::button::Button) -> Self { + Self { + button + } + } +} + +impl<'a> Scene<'a> for MinimalScene { + fn receive_menu_sub_action(&mut self, sub_action: &super::action::MenuSubAction) -> super::store::ReceiveMSAReturnOption { + match sub_action { + super::action::MenuSubAction::DrawAction => { + return super::store::ReceiveMSAReturnOption::NewAction( + vec!( + self.button.send_frame(), //todo:hier testen + ), + self, + ); + } + + super::action::MenuSubAction::ClickAction(x, y, mouse_btn) => { + match mouse_btn { + MouseButton::Left => match self.button.check_click(x, y) { + Some(msa) => return super::store::ReceiveMSAReturnOption::NewAction(vec![msa], self), + None => return super::store::ReceiveMSAReturnOption::NoNewAction(self) + }, + _ => return super::store::ReceiveMSAReturnOption::NoNewAction(self) + } + } + + _ => { + return super::store::ReceiveMSAReturnOption::NoNewAction(self); + } + } + } +} + +/// Very similar to the dispatcher, the SceneManager supervises menus. +pub struct SceneManager<'a> { + scenes: Option>>>, + //For an explaination of this look below. todo:better comment lol + current_scene: usize, +} + +impl<'a> SceneManager<'a> { + pub fn new() -> Self { + Self { + scenes: None, + current_scene: 0, + } + } + + pub fn add_scenes(&mut self, references: Vec<&'a mut Scene<'a>>) { + let mut local_scenes: Vec>>; + + match self.scenes.take() { + Some(x) => { + local_scenes = x; + } + _ => { + local_scenes = vec![]; + } + } + + for reference in references { + local_scenes.push(Some(reference)); + } + + self.scenes = Some(local_scenes); + } +} + +impl<'a> super::store::Store<'a> for SceneManager<'a> { + fn receive_action(&'a mut self, action: &super::action::Action, dt: &f64) -> super::store::ReceiveActionReturnOption { + match action { + super::action::Action::MenuAction(menu_sub_action) => { + let mut out_actions: Vec = vec![]; + + match menu_sub_action { + MenuSubAction::ChangeMenuStateAction => { + out_actions.push( + Action::MenuAction(MenuSubAction::WaitForInputAction) + ); + } + MenuSubAction::WaitForInputAction => { + out_actions.push( + Action::MenuAction(MenuSubAction::DrawAction) + ); + } + MenuSubAction::DrawAction => { //todo: make it actually draw stuff + out_actions.push( + Action::MenuAction(MenuSubAction::WaitForInputAction) + ); + } + _ => {} + } + + match self.scenes.take() { + //Psyche! Look at the similar function in dispatcher. + Some(mut local_scenes) => { + for index in 0..local_scenes.len() { + match local_scenes[index].take() { + Some(scene) => { + match scene.receive_menu_sub_action(menu_sub_action) { + store::ReceiveMSAReturnOption::NewAction(out_msa_vec, out_reference) => { + for msa in out_msa_vec { + out_actions.push(Action::MenuAction(msa)); + } + local_scenes[index] = Some(out_reference); + } + store::ReceiveMSAReturnOption::NoNewAction(out_reference) => { + local_scenes[index] = Some(out_reference); + } + } + } + None => println!("There is a missing scene...") + } + } + + self.scenes = Some(local_scenes); + super::store::ReceiveActionReturnOption::NewAction(out_actions, false, self) + } + None => { + panic!("No scenes yet ( ͡° ͜ʖ ͡°)"); + super::store::ReceiveActionReturnOption::NoNewAction(self) + } + } + } + _ => { super::store::ReceiveActionReturnOption::NoNewAction(self) } + } + } +} \ No newline at end of file diff --git a/src/sprite.rs b/src/sprite.rs new file mode 100644 index 0000000..fbf1911 --- /dev/null +++ b/src/sprite.rs @@ -0,0 +1,117 @@ +use std::fs::File; +use std::io::Read; + +use super::action::Action; +use super::dispatcher::Dispatcher; +use super::img::Img; +use super::renderer::Renderer; +use super::store::Store; + +pub struct Sprite { + ///A sprite with animations (using the function animate) + pos: (u8, u8), + frames: Vec, + anims: Vec<(usize, usize)>, + current_anim: usize, + current_frame: usize, + dt_since_last_frame: f64, + framerate: f64, +} + +/// Wrapper for the Img struct that handles animations and location. +impl Sprite { + pub fn new(pos: (u8, u8), frames: Vec, anims: Vec<(usize, usize)>, framerate: f64) -> Self { + Self { + pos, + frames, + anims, + current_anim: 0, + current_frame: 0, + dt_since_last_frame: 0.0, + framerate, + } + } + + /// Updates the internal state of the animation + pub fn animate(&mut self, dt: f64) { + if self.dt_since_last_frame + dt >= (1.0 / self.framerate) { + if self.current_frame + 1 < self.anims[self.current_anim].0 + self.anims[self.current_anim].1 { + self.current_frame += 1; + } else { + self.current_frame = self.anims[self.current_anim].0 + } + self.dt_since_last_frame = 0.0; + } else { + self.dt_since_last_frame += dt; + } + } + + pub fn get_pos(&self) -> (u8, u8) { return self.pos.clone(); } + + pub fn set_pos(&mut self, pos: (u8, u8)) { self.pos = pos } + + pub fn get_width(&self) -> i32 { + return self.get_current_frame().get_width().clone() as i32; + } + + pub fn get_height(&self) -> i32 { + return self.get_current_frame().get_pixels().len() as i32 / *self.get_current_frame().get_width() as i32; + } + + pub fn get_current_frame(&self) -> Img { + self.frames[self.current_frame].clone() + } + + /// Wraps Img::new_from_file. + pub fn new_from_File(mut f: File, pix_per_frame: usize, pos: (u8, u8), anims: Vec<(usize, usize)>, framerate: f64) -> Self { + let mut width = 0; + let mut pixels = vec![]; + let mut frames = vec![]; + let real_pix_per_frame = pix_per_frame / 2; + + let mut buf: Vec = vec![]; + f.read_to_end(&mut buf); + + if buf.len() < 4 { + return Self { + pos, + frames: vec![], + anims: vec![], + current_anim: 0, + current_frame: 0, + dt_since_last_frame: 0.0, + framerate: 0.0, + }; + } + + let mut ctr = 0; + + for (num) in buf.iter() { + if ctr >= 4 { + if (ctr - 4) % real_pix_per_frame == 0 { + pixels = vec![*num]; + } else if (ctr - 4) % real_pix_per_frame == real_pix_per_frame - 1 { + pixels.push(*num); + frames.push( + Img::new_from_u8(width, pixels.clone()) + ); + } else { + pixels.push(*num); + } + } else { + width = width | ((buf[ctr] as usize) << (3 - ctr)); + } + ctr += 1; + } + + return Self { + pos, + frames, + anims, + current_anim: 0, + current_frame: 0, + dt_since_last_frame: 0.0, + framerate, + }; + } +} diff --git a/src/store.rs b/src/store.rs new file mode 100644 index 0000000..0f78cee --- /dev/null +++ b/src/store.rs @@ -0,0 +1,20 @@ +use super::action::Action; +use super::action::MenuSubAction; +use super::dispatcher::Dispatcher; + +pub enum ReceiveActionReturnOption<'a> { + NoNewAction(&'a mut Store<'a>), + NewAction(Vec, /*add to secondary*/ bool, &'a mut Store<'a>), +} + +pub enum ReceiveMSAReturnOption<'a> { + NoNewAction(&'a mut super::scene_manager::Scene<'a>), + NewAction(Vec, &'a mut super::scene_manager::Scene<'a>), +} + +/// Turns any boring struct in a Store in the Flux pattern sense. These objects contain all the pro- +/// grams logic and communicate by ways of actions through the dispatcher. +pub trait Store<'a> { + ///The function that is called by the dispatcher to hand over a reference to an action. + fn receive_action(&'a mut self, action: &Action, dt: &f64) -> ReceiveActionReturnOption<'a>; +} diff --git a/src/test_obj.rs b/src/test_obj.rs new file mode 100644 index 0000000..1863fa8 --- /dev/null +++ b/src/test_obj.rs @@ -0,0 +1,54 @@ +use action::{Action, MenuSubAction}; +use dispatcher::Dispatcher; +use img::Img; +use sprite::Sprite; +use store::ReceiveActionReturnOption; +use store::Store; + +pub struct TestObj { + ///A game object for testing + sprite: Option, +} + +impl<'a> TestObj { + pub fn new(sprite: Sprite) -> Self { + Self { + sprite: Some(sprite), + } + } + + fn send_frame(&'a mut self, dt: f64) -> Action { + let mut out_action = Action::EmptyAction; + + match self.sprite.take() { + Some(mut sprite) => { + sprite.animate(dt); + out_action = Action::AddImgToCanvasAction( + sprite.get_pos().0, + sprite.get_pos().1, + sprite.get_current_frame(), + ); + self.sprite = Some(sprite); + } + None => {} + } + return out_action; + } +} + +impl<'a> Store<'a> for TestObj { + fn receive_action(&'a mut self, action: &Action, dt: &f64) -> ReceiveActionReturnOption<'a> { + match action { + &Action::SendFrameAction => { + return ReceiveActionReturnOption::NewAction( + vec!( + self.send_frame(dt.clone()) + ), + false, + self, + ); + } + _ => return ReceiveActionReturnOption::NoNewAction(self) + } + } +} diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..217c010 --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,160 @@ +/// The tests file. Tests should be run with the --test-threads 1 option so they don't block the +/// SDL environment for each other. + +use std::collections::VecDeque; +use std::fs::File; +use std::io::Read; +use std::thread::sleep; +use std::time::Duration; + +use rand; +use sdl2::video::Window; + +use action::MenuSubAction; +use button::Button; +use scene_manager::MinimalScene; +use scene_manager::SceneManager; +use test_obj::TestObj; + +use super::action::Action; +use super::dispatcher::Dispatcher; +use super::img::Img; +use super::renderer::Renderer; +use super::sprite::Sprite; +use super::store::ReceiveActionReturnOption; +use super::store::Store; + +struct TestStore { + in_num: u8, + out_num: u8, +} + +impl<'a> Store<'a> for TestStore { + fn receive_action(&'a mut self, action: &Action, dt: &f64) -> ReceiveActionReturnOption<'a> { + match action { + &Action::TestAction(num) => { + self.out_num = num; + return ReceiveActionReturnOption::NoNewAction(self); + } + &Action::EndFrameAction => { + return ReceiveActionReturnOption::NewAction( + vec!( + Action::TestAction(self.in_num) + ), + false, + self, + ); + } + _ => return ReceiveActionReturnOption::NoNewAction(self) + } + } +} + +#[test] +fn test_dispatcher() { + /*creates a TestStore instance that holds a random number. The number will be sent to the dispatcher + through a TestAction. The dispatcher should return the value. It will be asserted if the action pay- + load value equals the value owned by the TestStore instance*/ + + let mut test_num = rand::random(); + + while test_num == 0 { + test_num = rand::random() + } + + let mut test_store = TestStore { in_num: test_num, out_num: 0 }; + + { + let mut disp = Dispatcher::new(1.0 / 60.0); + disp.enter_refs(vec!(&mut test_store)); + + for i in 1..10 { + disp.dispatch(); + } + } + + assert_eq!(test_num, test_store.out_num) +} + +#[test] +fn test_renderer() { + /* creates a test game that owns a renderer and renders an image with all the colors of the color + space*/ + let mut test_renderer: Renderer; + test_renderer = Renderer::new(256, 256, None); + let mut test_img = Img::new_from_u8(0, vec![]); + + match File::open("./resources/test_img.u4i") { + Ok(f) => { + test_img = Img::new_from_File(f); + } + Err(e) => { + print!("{}", e); + } + } + + assert!(test_renderer.test(test_img)); +} + +#[test] +fn test_sprites_and_menus() { + let mut test_renderer = Renderer::new(256, 256, None); + let mut test_sprite = Sprite::new((0, 0), vec![], vec![], 0.0); + let mut button_sprite = Sprite::new((0, 0), vec![], vec![], 0.0); + + match File::open("./resources/test_sprite.u4i") { + Ok(f) => { + test_sprite = Sprite::new_from_File( + f, + 16, + (0, 0), + vec![(0, 12)], + 30.0, + ); + } + Err(e) => { + println!("{}", e); + } + } + + let mut test_object = TestObj::new(test_sprite); + + match File::open("./resources/exit_button.u4i") { + Ok(f) => { + button_sprite = Sprite::new_from_File( + f, + 133, + (0, 0), + vec![(0, 1)], + 1.0, + ); + } + Err(e) => { + println!("{}", e); + } + } + + let mut test_button = Button::new( + button_sprite, + (0, 0), + Box::new(|| { + println!("Clicked button!"); + return MenuSubAction::QuitAction; + }), + ); + + let mut test_scene = MinimalScene::new( + test_button + ); + + let mut scene_manager = SceneManager::new(); + + scene_manager.add_scenes(vec![&mut test_scene]); + + { + let mut disp = Dispatcher::new(1.0); + disp.enter_refs(vec!(&mut test_renderer, &mut test_object, &mut scene_manager));//, &mut test_object)); + + while disp.dispatch() {}; + } +}