From a0068895dc83b2afddc507de536dfeb2c04b012b Mon Sep 17 00:00:00 2001 From: grzi Date: Mon, 5 Apr 2021 23:59:26 +0200 Subject: [PATCH] feat: Implement tetris FIXES #54 --- Cargo.toml | 5 +- assets/tetris/black.png | Bin 0 -> 245 bytes assets/tetris/blue.png | Bin 0 -> 246 bytes assets/tetris/brown.png | Bin 0 -> 246 bytes assets/tetris/cyan.png | Bin 0 -> 246 bytes assets/tetris/green.png | Bin 0 -> 248 bytes assets/tetris/pink.png | Bin 0 -> 254 bytes assets/tetris/red.png | Bin 0 -> 250 bytes assets/tetris/ui_overflow_top.png | Bin 0 -> 210 bytes assets/tetris/yellow.png | Bin 0 -> 254 bytes examples/tetris/components.rs | 177 +++++++++++++++++++++ examples/tetris/layer.rs | 90 +++++++++++ examples/tetris/main.rs | 94 +++++------ examples/tetris/resources.rs | 32 ++++ examples/tetris/systems/mod.rs | 4 + examples/tetris/systems/move_system.rs | 83 ++++++++++ examples/tetris/systems/piece_system.rs | 140 ++++++++++++++++ examples/tetris/systems/rotation_system.rs | 67 ++++++++ examples/tetris/systems/score_system.rs | 69 ++++++++ scion.json | 1 + src/config/window_config.rs | 7 +- src/core/components/maths/transform.rs | 5 + src/core/components/ui/font.rs | 1 + src/core/components/ui/ui_text.rs | 5 + src/core/game_layer.rs | 8 +- src/core/resources/time.rs | 12 ++ 26 files changed, 736 insertions(+), 64 deletions(-) create mode 100644 assets/tetris/black.png create mode 100644 assets/tetris/blue.png create mode 100644 assets/tetris/brown.png create mode 100644 assets/tetris/cyan.png create mode 100644 assets/tetris/green.png create mode 100644 assets/tetris/pink.png create mode 100644 assets/tetris/red.png create mode 100644 assets/tetris/ui_overflow_top.png create mode 100644 assets/tetris/yellow.png create mode 100644 examples/tetris/components.rs create mode 100644 examples/tetris/layer.rs create mode 100644 examples/tetris/resources.rs create mode 100644 examples/tetris/systems/mod.rs create mode 100644 examples/tetris/systems/move_system.rs create mode 100644 examples/tetris/systems/piece_system.rs create mode 100644 examples/tetris/systems/rotation_system.rs create mode 100644 examples/tetris/systems/score_system.rs create mode 100644 scion.json diff --git a/Cargo.toml b/Cargo.toml index cd28169..05fffd8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,4 +47,7 @@ deflate = {opt-level = 3} anyhow = "1.0" fs_extra = "1.1" glob = "0.3" -shaderc = "0.7" \ No newline at end of file +shaderc = "0.7" + +[dev-dependencies] +rand = "0.8.3" \ No newline at end of file diff --git a/assets/tetris/black.png b/assets/tetris/black.png new file mode 100644 index 0000000000000000000000000000000000000000..1efdca0a9db10752fa8a32f375468381b796b4ea GIT binary patch literal 245 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}>pfi@Ln2y} zx0HYVao(VzAyYv&Jpl+B4TX}M0#)wqt#%IXHqWy@ao~W#3?~M*Yk4=_gSlnbehK)| ztj zgoZ0u{8#N`ivNzcc9Z5JYD@<);T3K0RRkPUE2Tv literal 0 HcmV?d00001 diff --git a/assets/tetris/blue.png b/assets/tetris/blue.png new file mode 100644 index 0000000000000000000000000000000000000000..3397585927516ca73ba199855a2c6803f12226fb GIT binary patch literal 246 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}8$4YcLn2y} zO}5VZK0lFx@v=kIxdTAJl)PidjYS?dm4Buz6T82ovcIu0(V&%qXO4aS%w=L-T^tKP zu={g7=qjGk9lyWQebT`rucU1h8~76J8kUCfma4L`u^qBg3cde;mvKgS=U2;}zu59Q z4lt}-IX|S9S&nHjpG4__70et~4k0cai}}>q1XkxCa8B4_CbO3JWt{Y>6JEv!20YK| r7%eO3Nicl2w37HBkRbDMZ!&|_*-*uA--RCl-Ok|Y>gTe~DWM4fC97Gu literal 0 HcmV?d00001 diff --git a/assets/tetris/brown.png b/assets/tetris/brown.png new file mode 100644 index 0000000000000000000000000000000000000000..3540fb8bd33917b82c7913e0d3915de16c951cae GIT binary patch literal 246 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}8$4YcLn2y} zb-rErbKan#AyYv&Jpl+B4Q<})2B^&2SK%7mt$%mV@dF18W;ijhor~Y;7|eatr6u4; zvpP$`QOh$&*WcTtd@>>FEAKo32X+Jb13|0j?Gj>UW=@o!xGMic8^f8SM}EzTscklA zNocro#eda4M*hY?_8zN*D~v3%3TjF%f$hS~9aqgCC>lgb_g!l%SHMOxZ(V&%qXO3NI*D|p#Pw&Ma z*!?*kbQRC&j-NO~t>dbBg%gA9ff@$ywOY4s@$m4p)Vi$NcTk$iKwS8BanV=adjbuN zLBaMbf3exHxXBv4Y6xT(C~26n#l%h4k4GWwenb0#j9iN-=}UXfrnq}6H|w*UIQjVq rL#50!zIg%-M>*Fr=zV6lVPP=p41aBPVA^(|+ZjAv{an^LB{Ts5=@D7= literal 0 HcmV?d00001 diff --git a/assets/tetris/green.png b/assets/tetris/green.png new file mode 100644 index 0000000000000000000000000000000000000000..f8642014d403a9e92616f45f272747a6787a0d6f GIT binary patch literal 248 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}n><|{Ln2z= zh8yxV8}Qhy%HxS$!?Wh*&B)*D_DyQk&2HDI&rs)Y5a1KKA+%33u;k;yi3)&4L8)3U}ba~gqw_1K29qK*@P)O_dk)!nzDx4E$~(V&%q=T0P-aL|Iz)<(X)6yuyh=_CO7T_gc2NBR@MrYMaej678!mo>qzVhA^ zXkZKqwqN;+&4$HI*5Fk`AiF?GL(@Ic9;-PL4r}%?@;6?*Rj|cuNp-Hq+B}9tng1Y5 sC7c`P*!c4_{FdS6so*f^`*F{Z;eKggh^zopr0HbVNm;e9( literal 0 HcmV?d00001 diff --git a/assets/tetris/ui_overflow_top.png b/assets/tetris/ui_overflow_top.png new file mode 100644 index 0000000000000000000000000000000000000000..c0d8dc6890ebd11b41798c1f1da6a4f751c21e42 GIT binary patch literal 210 zcmeAS@N?(olHy`uVBq!ia0y~yU~~bp6*$;{WY!NxJ0Qha9OUlAui+xqiIGuK$@=jqgxzmwKai1Y5hZuYLxyL?B?b=^4_G{^KF%;pFpTBy`On{heqiu)^>bP0l+XkK=$~9# literal 0 HcmV?d00001 diff --git a/examples/tetris/components.rs b/examples/tetris/components.rs new file mode 100644 index 0000000..0541736 --- /dev/null +++ b/examples/tetris/components.rs @@ -0,0 +1,177 @@ +use rand::{thread_rng, Rng}; + +pub const BLOC_SIZE: f32 = 32.0; + +#[derive(Debug)] +pub enum BlocKind { + Moving, + Static, +} + +#[derive(Debug)] +pub struct Bloc { + pub kind: BlocKind, +} + +pub struct NextBloc; + +impl Bloc { + pub fn new(k: BlocKind) -> Bloc { + Bloc { kind: k } + } +} + +#[derive(Debug, Clone)] +pub enum PieceOrientation { + Up, + Right, + Down, + Left, +} + +impl PieceOrientation { + pub fn next_orientation(&self) -> PieceOrientation { + match self { + PieceOrientation::Up => PieceOrientation::Right, + PieceOrientation::Right => PieceOrientation::Down, + PieceOrientation::Down => PieceOrientation::Left, + PieceOrientation::Left => PieceOrientation::Up, + } + } +} + +#[repr(u8)] +#[derive(Debug, Clone)] +pub enum PieceKind { + I, + O, + T, + S, + Z, + J, + L, +} + +impl PieceKind { + pub fn from_int(x: u8) -> Result { + match x { + x if x == PieceKind::I as u8 => Ok(PieceKind::I), + x if x == PieceKind::O as u8 => Ok(PieceKind::O), + x if x == PieceKind::T as u8 => Ok(PieceKind::T), + x if x == PieceKind::S as u8 => Ok(PieceKind::S), + x if x == PieceKind::Z as u8 => Ok(PieceKind::Z), + x if x == PieceKind::J as u8 => Ok(PieceKind::J), + x if x == PieceKind::L as u8 => Ok(PieceKind::L), + _ => Err("Error while convertine u8 to PieceKind"), + } + } + + pub fn get_self_offsets(&self, orientation: &PieceOrientation) -> Vec<(f32, f32)> { + PieceKind::get_offsets(self, &orientation) + } + + pub fn get_offsets(kind: &PieceKind, orientation: &PieceOrientation) -> Vec<(f32, f32)> { + match kind { + PieceKind::I => PieceKind::get_i_offsets(orientation), + PieceKind::O => vec![(1.0, 1.0), (2.0, 1.0), (1.0, 2.0), (2.0, 2.0)], + PieceKind::T => PieceKind::get_t_offsets(orientation), + PieceKind::S => PieceKind::get_s_offsets(orientation), + PieceKind::Z => PieceKind::get_z_offsets(orientation), + PieceKind::J => PieceKind::get_j_offsets(orientation), + PieceKind::L => PieceKind::get_l_offsets(orientation), + } + } + + fn get_i_offsets(orientation: &PieceOrientation) -> Vec<(f32, f32)> { + match orientation { + PieceOrientation::Up => vec![(0.0, 1.0), (1.0, 1.0), (2.0, 1.0), (3.0, 1.0)], + PieceOrientation::Right => vec![(2.0, 0.0), (2.0, 1.0), (2.0, 2.0), (2.0, 3.0)], + PieceOrientation::Down => vec![(0.0, 2.0), (1.0, 2.0), (2.0, 2.0), (3.0, 2.0)], + PieceOrientation::Left => vec![(1.0, 0.0), (1.0, 1.0), (1.0, 2.0), (1.0, 3.0)], + } + } + + fn get_t_offsets(orientation: &PieceOrientation) -> Vec<(f32, f32)> { + match orientation { + PieceOrientation::Up => vec![(1.0, 0.0), (0.0, 1.0), (1.0, 1.0), (2.0, 1.0)], + PieceOrientation::Right => vec![(1.0, 0.0), (1.0, 1.0), (2.0, 1.0), (1.0, 2.0)], + PieceOrientation::Down => vec![(0.0, 1.0), (1.0, 1.0), (2.0, 1.0), (1.0, 2.0)], + PieceOrientation::Left => vec![(1.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 2.0)], + } + } + + fn get_s_offsets(orientation: &PieceOrientation) -> Vec<(f32, f32)> { + match orientation { + PieceOrientation::Up => vec![(1.0, 0.0), (2.0, 0.0), (0.0, 1.0), (1.0, 1.0)], + PieceOrientation::Right => vec![(1.0, 0.0), (1.0, 1.0), (2.0, 1.0), (2.0, 2.0)], + PieceOrientation::Down => vec![(1.0, 1.0), (2.0, 1.0), (0.0, 2.0), (1.0, 2.0)], + PieceOrientation::Left => vec![(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 2.0)], + } + } + + fn get_z_offsets(orientation: &PieceOrientation) -> Vec<(f32, f32)> { + match orientation { + PieceOrientation::Up => vec![(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (2.0, 1.0)], + PieceOrientation::Right => vec![(2.0, 0.0), (1.0, 1.0), (2.0, 1.0), (1.0, 2.0)], + PieceOrientation::Down => vec![(0.0, 1.0), (1.0, 1.0), (1.0, 2.0), (2.0, 2.0)], + PieceOrientation::Left => vec![(1.0, 0.0), (0.0, 1.0), (1.0, 1.0), (0.0, 2.0)], + } + } + + fn get_j_offsets(orientation: &PieceOrientation) -> Vec<(f32, f32)> { + match orientation { + PieceOrientation::Up => vec![(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (2.0, 1.0)], + PieceOrientation::Right => vec![(1.0, 0.0), (2.0, 0.0), (1.0, 1.0), (1.0, 2.0)], + PieceOrientation::Down => vec![(0.0, 1.0), (1.0, 1.0), (2.0, 1.0), (2.0, 2.0)], + PieceOrientation::Left => vec![(1.0, 0.0), (1.0, 1.0), (0.0, 2.0), (1.0, 2.0)], + } + } + + fn get_l_offsets(orientation: &PieceOrientation) -> Vec<(f32, f32)> { + match orientation { + PieceOrientation::Up => vec![(2.0, 0.0), (0.0, 1.0), (1.0, 1.0), (2.0, 1.0)], + PieceOrientation::Right => vec![(1.0, 0.0), (1.0, 1.0), (1.0, 2.0), (2.0, 2.0)], + PieceOrientation::Down => vec![(0.0, 1.0), (1.0, 1.0), (2.0, 1.0), (0.0, 2.0)], + PieceOrientation::Left => vec![(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (1.0, 2.0)], + } + } +} + +#[derive(Debug, Clone)] +pub struct Piece { + pub orientation: PieceOrientation, + pub kind: PieceKind, + pub color: usize, +} + +impl Piece { + pub fn new(o: PieceOrientation, k: PieceKind, c: usize) -> Piece { + Piece { + orientation: o, + kind: k, + color: c, + } + } + + pub fn get_current_offsets(&self) -> Vec<(f32, f32)> { + self.kind.get_self_offsets(&self.orientation) + } + + pub fn rotate(&mut self) { + self.orientation = self.orientation.next_orientation(); + } + + pub fn random_new() -> Piece { + let random_piece_nb: u8 = thread_rng().gen_range(0..7); + let piece_kind = PieceKind::from_int(random_piece_nb).unwrap(); + Piece { + orientation: PieceOrientation::Right, + kind: piece_kind, + color: thread_rng().gen_range(0..7) + } + } +} + +pub const BOARD_HEIGHT: u32 = 20; +pub const BOARD_WIDTH: u32 = 10; +pub const BOARD_OFFSET: (f32, f32) = (32., 32.); \ No newline at end of file diff --git a/examples/tetris/layer.rs b/examples/tetris/layer.rs new file mode 100644 index 0000000..2091617 --- /dev/null +++ b/examples/tetris/layer.rs @@ -0,0 +1,90 @@ +use scion::core::game_layer::SimpleGameLayer; +use scion::legion::{World, Resources, Entity}; +use scion::core::components::maths::transform::{Transform2D}; +use scion::core::components::ui::ui_image::UiImage; +use scion::core::components::maths::camera::Camera2D; +use scion::utils::file::app_base_path; + +use scion::core::components::ui::font::Font; +use scion::core::components::ui::ui_text::UiText; + + +use scion::core::resources::time::{Timers, TimerType}; +use crate::resources::TetrisResource; +use crate::asset_path; + + +#[derive(Default)] +pub struct TetrisLayer{ + score: Option +} + +impl SimpleGameLayer for TetrisLayer { + fn on_start(&mut self, world: &mut World, resources: &mut Resources) { + add_main_ui_mask(world); + add_ui_top_overflow(world); + self.score = Some(add_score_ui(world)); + resources.insert(Camera2D::new(544., 704., 10.)); + let _r = resources.get_mut::().unwrap().add_timer("piece", TimerType::Cyclic, 0.5); + let _r = resources.get_mut::().unwrap().add_timer("action_reset_timer", TimerType::Manual, 0.2); + resources.insert(TetrisResource::default()); + } + + fn update(&mut self, world: &mut World, resources: &mut Resources) { + let tetris = resources.get::().unwrap(); + world.entry(self.score.unwrap()).unwrap().get_component_mut::().unwrap().set_text(format!("{:05}", tetris.score)) + } +} + +fn add_score_ui(world: &mut World) -> Entity{ + // First we add an UiText to the world + let font = Font::Bitmap { + texture_path: app_base_path() + .expect("") + .join("assets") + .join("tetris") + .join("font.png").to_str().expect("").to_string(), + chars: "0123456789ACEOPRSULI".to_string(), + texture_columns: 20., + texture_lines: 1., + width: 21., + height: 27., + }; + + let txt = UiText::new("SCORE".to_string(), font.clone()); + let mut transform = Transform2D::default(); + transform.append_translation(394., 250.); + transform.set_layer(2); + + world.push((txt, transform)); + + let txt = UiText::new("".to_string(), font.clone()); + let mut transform = Transform2D::default(); + transform.append_translation(394., 290.); + transform.set_layer(2); + world.push((txt, transform)) +} + + + +fn add_main_ui_mask(world: &mut World) { + // TODO : optimize this because too much boilerplate + let path = asset_path().join("ui.png") + .to_str().expect("").to_string(); + let image = UiImage::new(544., 704., path); + + let mut t = Transform2D::default(); + t.set_layer(0); + world.push((image, t)); +} + +fn add_ui_top_overflow(world: &mut World) { + let path = asset_path().join("ui_overflow_top.png") + .to_str().expect("").to_string(); + let image = UiImage::new(324., 32., path); + + let mut t = Transform2D::default(); + t.set_layer(2); + t.append_translation(32., 0.); + world.push((image, t)); +} \ No newline at end of file diff --git a/examples/tetris/main.rs b/examples/tetris/main.rs index 7110766..61f175a 100644 --- a/examples/tetris/main.rs +++ b/examples/tetris/main.rs @@ -1,74 +1,52 @@ use scion::{ core::{ - components::{ - maths::{camera::Camera2D, transform::Transform2D}, - ui::ui_image::UiImage, - }, - game_layer::{GameLayer, SimpleGameLayer}, - resources::time::{TimerType, Timers}, + game_layer::GameLayer, }, - legion::{system, Resources, World}, utils::file::app_base_path, Scion, }; -use scion::core::components::ui::font::Font; -use scion::core::components::ui::ui_text::UiText; -use scion::core::resources::inputs::Inputs; -use scion::core::inputs::keycode::KeyCode; -#[system] -fn test(#[resource] inputs: &Inputs){ - if inputs.keyboard().key_event(&KeyCode::Right) { - log::info!("right events pressed"); - } -} -#[derive(Default)] -struct LayerA; -impl SimpleGameLayer for LayerA { - fn on_start(&mut self, world: &mut World, resource: &mut Resources) { - let path = app_base_path() - .expect("") - .join("assets") - .join("tetris") - .join("ui.png") - .to_str() - .expect("") - .to_string(); - let mut t = Transform2D::default(); - t.set_layer(0); - let image = UiImage::new(544., 704., path); - world.push((image, t)); - resource.insert(Camera2D::new(544., 704., 10.)); +use scion::config::scion_config::{ScionConfig, ScionConfigBuilder}; +use scion::config::window_config::WindowConfigBuilder; +use crate::layer::TetrisLayer; +use std::path::PathBuf; +use crate::systems::piece_system::piece_update_system; +use crate::systems::move_system::move_piece_system; +use crate::systems::rotation_system::piece_rotation_system; +use crate::systems::score_system::score_system; - // First we add an UiText to the world - let font = Font::Bitmap { - texture_path: app_base_path() - .expect("") - .join("assets") - .join("tetris") - .join("font.png").to_str().expect("").to_string(), - chars: "0123456789ACEOPRSULI".to_string(), - texture_columns: 20., - texture_lines: 1., - width: 21., - height: 27., - }; +mod layer; +mod components; +mod systems; +pub mod resources; - let txt = UiText::new("009287".to_string(), font); - let mut transform = Transform2D::default(); - transform.append_translation(382., 250.); - transform.set_layer(2); +fn main() { + Scion::app_with_config(app_config()) + .with_game_layer(GameLayer::weak::()) + .with_system(piece_update_system()) + .with_system(move_piece_system()) + .with_system(piece_rotation_system()) + .with_system(score_system()) + .run(); +} - world.push((txt, transform)); - } +fn app_config() -> ScionConfig { + ScionConfigBuilder::new() + .with_app_name("Tetris".to_string()) + .with_window_config( + WindowConfigBuilder::new() + .with_dimensions((544, 704)) + .with_resizable(true) + .get() + ) + .get() } -fn main() { - Scion::app() - .with_game_layer(GameLayer::weak::()) - .with_system(test_system()) - .run(); +pub fn asset_path() -> PathBuf { + app_base_path() + .expect("base_path is mandatory to run the game") + .join("assets").join("tetris") } diff --git a/examples/tetris/resources.rs b/examples/tetris/resources.rs new file mode 100644 index 0000000..9b57c5a --- /dev/null +++ b/examples/tetris/resources.rs @@ -0,0 +1,32 @@ +use crate::components::Piece; + +pub enum TetrisState { + MOVING(u32, u32), + WAITING, +} + +pub struct TetrisResource{ + pub state: TetrisState, + pub active_piece: Piece, + pub next_piece: Piece, + pub score: usize +} + +impl Default for TetrisResource{ + fn default() -> Self { + Self{ + state: TetrisState::WAITING, + active_piece: Piece::random_new(), + next_piece: Piece::random_new(), + score: 0 + } + } +} + +impl TetrisResource { + pub fn switch_to_next_piece(&mut self) { + self.state = TetrisState::MOVING(4, 0); + self.active_piece = self.next_piece.clone(); // TODO : Is there something I can do about this ? + self.next_piece = Piece::random_new(); + } +} \ No newline at end of file diff --git a/examples/tetris/systems/mod.rs b/examples/tetris/systems/mod.rs new file mode 100644 index 0000000..96049f6 --- /dev/null +++ b/examples/tetris/systems/mod.rs @@ -0,0 +1,4 @@ +pub mod piece_system; +pub mod move_system; +pub mod rotation_system; +pub mod score_system; \ No newline at end of file diff --git a/examples/tetris/systems/move_system.rs b/examples/tetris/systems/move_system.rs new file mode 100644 index 0000000..7779609 --- /dev/null +++ b/examples/tetris/systems/move_system.rs @@ -0,0 +1,83 @@ +use scion::core::resources::inputs::Inputs; +use scion::core::inputs::keycode::KeyCode; +use scion::core::resources::time::Timers; +use crate::components::{BLOC_SIZE, Bloc, BlocKind, BOARD_WIDTH}; +use scion::legion::{system, Query}; +use scion::core::components::maths::transform::Transform2D; +use scion::legion::world::SubWorld; +use crate::resources::{TetrisState, TetrisResource}; + +#[system] +pub fn move_piece(#[resource] inputs: &Inputs, + #[resource] timers: &mut Timers, + #[resource] tetris: &mut TetrisResource, + world: &mut SubWorld, + query: &mut Query<(&mut Bloc, &mut Transform2D)>){ + handle_acceleration(inputs, timers); + + let movement_timer = timers.get_timer("action_reset_timer") + .expect("Missing a mandatory timer in the game : action_reset_timer"); + + let movement = read_movements_actions(inputs); + if movement_timer.ended() { + let should_move = movement != 0 && { + let mut res = true; + let mut static_values: Vec<(i32, i32)> = Vec::new(); + let mut piece_values: Vec<(i32, i32)> = Vec::new(); + for (bloc, transform) in query.iter_mut(world) { + let t = ( + (transform.coords().x() / BLOC_SIZE) as i32, + (transform.coords().y() / BLOC_SIZE) as i32, + ); + match bloc.kind { + BlocKind::Moving => piece_values.push(t), + _ => static_values.push(t), + }; + } + + for (x, y) in piece_values.iter() { + for (xx, yy) in static_values.iter() { + if y == yy && *x == (xx - movement) as i32{ + res = false; + break; + } + } + if x + movement == 0 || x + movement == (BOARD_WIDTH +1) as i32{ + res = false; + break; + } + } + + res + }; + + if should_move { + log::info!("move true {}", movement_timer.ended()); + movement_timer.reset(); + if let TetrisState::MOVING(x, y) = tetris.state { + tetris.state = TetrisState::MOVING((x as i32 + movement as i32) as u32, y); + }; + for (bloc, transform) in query.iter_mut(world) { + match bloc.kind { + BlocKind::Moving => { + transform.append_translation(movement as f32 * BLOC_SIZE, 0.); + } + _ => {} + }; + } + } + } +} + +fn handle_acceleration(input: &Inputs, timers: &mut Timers) { + if input.keyboard().key_pressed(&KeyCode::Down) { + timers.get_timer("piece").expect("Missing a mandatory timer in the game : piece").change_cycle(0.025); + }else{ + timers.get_timer("piece").expect("Missing a mandatory timer in the game : piece").change_cycle(0.5); + } +} + +fn read_movements_actions(input: &Inputs) -> i32 { + ({ if input.keyboard().key_pressed(&KeyCode::Left) { -1 } else { 0 } }) + + ({ if input.keyboard().key_pressed(&KeyCode::Right) { 1 } else { 0 } }) +} \ No newline at end of file diff --git a/examples/tetris/systems/piece_system.rs b/examples/tetris/systems/piece_system.rs new file mode 100644 index 0000000..a9eb2cb --- /dev/null +++ b/examples/tetris/systems/piece_system.rs @@ -0,0 +1,140 @@ +use scion::core::resources::time::Timers; +use crate::resources::{TetrisResource, TetrisState}; +use scion::core::components::maths::transform::{Transform2D, Coordinates}; +use crate::components::{BLOC_SIZE, Bloc, BlocKind, BOARD_OFFSET, BOARD_HEIGHT, NextBloc}; +use scion::legion::systems::CommandBuffer; +use scion::core::components::Square; +use scion::core::components::material::Material2D; +use crate::asset_path; +use scion::legion::{system, Query, Entity}; +use scion::legion::world::SubWorld; + +#[system] +pub fn piece_update(cmd: &mut CommandBuffer, + #[resource] timers: &mut Timers, + #[resource] tetris: &mut TetrisResource, + world: &mut SubWorld, + query: &mut Query<(&mut Bloc, &mut Transform2D)>, + query2: &mut Query<(Entity, &NextBloc)>) { + let timer = timers.get_timer("piece").expect("Missing a mandatory timer in the game : piece timer"); + if timer.cycle() { + match tetris.state { + TetrisState::WAITING => { + tetris.switch_to_next_piece(); + query2.for_each(world, |(e, _)|{ + cmd.remove(*e); + }); + let offsets = tetris.next_piece.get_current_offsets(); + for offset in offsets { + initialize_next_bloc(&offset, cmd, tetris.next_piece.color, 12., 2.); + } + + let offsets = tetris.active_piece.get_current_offsets(); + for offset in offsets { + initialize_bloc(&offset, cmd, tetris.active_piece.color, 4., 0.); + } + } + TetrisState::MOVING(x, y) => { + let mut static_values: Vec<(u32, u32)> = Vec::new(); + let mut piece_values: Vec<(u32, u32)> = Vec::new(); + for (bloc, transform) in query.iter_mut(world) { + let t = ( + ((transform.coords().x() - BOARD_OFFSET.0 ) / BLOC_SIZE) as u32, + ((transform.coords().y() - BOARD_OFFSET.1 ) / BLOC_SIZE) as u32, + ); + match bloc.kind { + BlocKind::Moving => piece_values.push(t), + _ => static_values.push(t), + }; + } + let should_move_piece = { + let mut res = true; + for (x, y) in piece_values.iter() { + for (xx, yy) in static_values.iter() { + if x == xx && y == &(yy - 1) { + res = false; + } + } + if y == &(BOARD_HEIGHT -1) { + res = false; + } + } + res + }; + if should_move_piece { + for (bloc, transform) in query.iter_mut(world) { + match bloc.kind { + BlocKind::Moving => { + transform.move_down(BLOC_SIZE); + tetris.state = TetrisState::MOVING(x, y+1); + } + _ => {} + }; + } + } else { + for (mut bloc, _) in query.iter_mut(world) + { + match bloc.kind { + BlocKind::Moving => { + bloc.kind = BlocKind::Static; + tetris.state = TetrisState::WAITING; + } + _ => {} + } + } + } + } + } + } +} + +pub fn initialize_bloc(offset: &(f32, f32), cmd: &mut CommandBuffer, color: usize, coord_x: f32, coord_y: f32, ) { + let mut bloc_transform = Transform2D::default(); + bloc_transform.append_translation( + coord_x * BLOC_SIZE + offset.0 * BLOC_SIZE, + coord_y * BLOC_SIZE + offset.1 * BLOC_SIZE, + ); + bloc_transform.set_layer(1); + cmd.push(( + Bloc::new(BlocKind::Moving), + bloc_transform, + Square::new(Coordinates::new(0., 0.), 32., Some([ + Coordinates::new(0., 0.), + Coordinates::new(0., 1.), + Coordinates::new(1., 1.), + Coordinates::new(1., 0.), + ])), + Material2D::Texture(asset_path().join(get_color_skin(color).as_str()).as_path().to_str().unwrap().to_string()))); +} + +pub fn initialize_next_bloc(offset: &(f32, f32), cmd: &mut CommandBuffer, color: usize, coord_x: f32, coord_y: f32, ) { + let mut bloc_transform = Transform2D::default(); + bloc_transform.append_translation( + coord_x * BLOC_SIZE + offset.0 * BLOC_SIZE, + coord_y * BLOC_SIZE + offset.1 * BLOC_SIZE, + ); + bloc_transform.set_layer(1); + cmd.push(( + NextBloc, + bloc_transform, + Square::new(Coordinates::new(0., 0.), 32., Some([ + Coordinates::new(0., 0.), + Coordinates::new(0., 1.), + Coordinates::new(1., 1.), + Coordinates::new(1., 0.), + ])), + Material2D::Texture(asset_path().join(get_color_skin(color).as_str()).as_path().to_str().unwrap().to_string()))); +} + +fn get_color_skin(color: usize) -> String { + let color = match color { + 0 => "blue.png", + 1 => "brown.png", + 2 => "cyan.png", + 3 => "green.png", + 4 => "pink.png", + 5 => "red.png", + _ => "yellow.png", + }; + color.to_string() +} \ No newline at end of file diff --git a/examples/tetris/systems/rotation_system.rs b/examples/tetris/systems/rotation_system.rs new file mode 100644 index 0000000..5984a92 --- /dev/null +++ b/examples/tetris/systems/rotation_system.rs @@ -0,0 +1,67 @@ +use scion::core::resources::inputs::Inputs; +use scion::core::inputs::keycode::KeyCode; +use scion::core::resources::time::Timers; +use crate::resources::{TetrisResource, TetrisState}; +use crate::components::{PieceKind, BlocKind, BLOC_SIZE, Bloc, BOARD_WIDTH, BOARD_HEIGHT}; +use scion::legion::world::SubWorld; +use scion::legion::{system, Query, Entity}; +use scion::core::components::maths::transform::Transform2D; +use scion::legion::systems::CommandBuffer; +use crate::systems::piece_system::initialize_bloc; + +#[system] +pub fn piece_rotation(cmd: &mut CommandBuffer, + #[resource] inputs: &Inputs, + #[resource] timers: &mut Timers, + #[resource] tetris: &mut TetrisResource, + world: &mut SubWorld, + query: &mut Query<(Entity, &mut Bloc, &mut Transform2D)>) { + let rotation = inputs.keyboard().key_pressed(&KeyCode::Up); + let movement_timer = timers.get_timer("action_reset_timer") + .expect("Missing a mandatory timer in the game : action_reset_timer"); + if movement_timer.ended() && rotation { + let rotation_offsets = { + let next_orientation = tetris.active_piece.orientation.next_orientation(); + PieceKind::get_offsets(&tetris.active_piece.kind, &next_orientation) + }; + + if let TetrisState::MOVING(x, y) = tetris.state { + let mut should_rotate_piece = true; + for offset in rotation_offsets.iter() { + if x as f32 + offset.0 == 0. + || x as f32 + offset.0 == (BOARD_WIDTH + 1) as f32 + || y as f32 + offset.1 == (BOARD_HEIGHT + 1) as f32 { + should_rotate_piece = false; + } else { + for (_, bloc, transform) in query.iter_mut(world) { + match bloc.kind { + BlocKind::Moving => {} + _ => { + let translation = transform.coords(); + if translation.x() / BLOC_SIZE == x as f32 + offset.0 + && translation.y() / BLOC_SIZE == y as f32 + offset.1 { + should_rotate_piece = false; + break; + } + } + } + } + } + } + if should_rotate_piece { + for (entity, bloc, _transform) in query.iter_mut(world) { + match bloc.kind { + BlocKind::Moving => { cmd.remove(*entity); }, + _ => {} + } + } + tetris.active_piece.rotate(); + let offsets = tetris.active_piece.get_current_offsets(); + for offset in offsets { + initialize_bloc(&offset, cmd, tetris.active_piece.color, x as f32, y as f32); + } + movement_timer.reset(); + } + } + } +} diff --git a/examples/tetris/systems/score_system.rs b/examples/tetris/systems/score_system.rs new file mode 100644 index 0000000..d193ee2 --- /dev/null +++ b/examples/tetris/systems/score_system.rs @@ -0,0 +1,69 @@ +use crate::resources::TetrisResource; +use scion::legion::world::SubWorld; +use scion::legion::{system, Query, Entity}; +use crate::components::{Bloc, BlocKind, BLOC_SIZE, BOARD_HEIGHT}; +use scion::core::components::maths::transform::Transform2D; +use std::collections::HashMap; +use scion::legion::systems::CommandBuffer; + +#[system] +pub fn score(cmd: &mut CommandBuffer, + #[resource] tetris: &mut TetrisResource, + world: &mut SubWorld, + query: &mut Query<(Entity, &Bloc, &mut Transform2D)>){ + let mut lines = HashMap::new(); + for i in 1..=BOARD_HEIGHT{ + lines.insert(i as usize, 0); + } + for (_, bloc, transform) in query.iter_mut(world) { + match bloc.kind { + BlocKind::Static => { + let key = (transform.coords().y() / BLOC_SIZE) as usize; + let new_val = match lines.get(&key) { + Some(val) => val + 1, + None => 1 + }; + lines.insert(key, new_val); + }, + _ => {} + } + } + + let lines2 = { + let mut full_lines = Vec::new(); + for (key, val) in lines.iter() { + if val == &10 { + tetris.score += 1; + full_lines.push(*key); + } + } + full_lines.sort(); + full_lines + }; + + if lines2.len() > 0 { + for (entity, bloc, transform) in query.iter_mut(world) { + match bloc.kind { + BlocKind::Static => { + let line = (transform.coords().y() / BLOC_SIZE) as usize; + if lines2.contains(&line) { + cmd.remove(*entity); + } + }, + _ => {} + }; + } + for (index, line) in lines2.iter().enumerate() { + for (_, bloc, transform) in query.iter_mut(world) { + match bloc.kind { + BlocKind::Static => { + if (*line - index as usize) > (transform.coords().y() / BLOC_SIZE) as usize { + transform.move_down(BLOC_SIZE); + } + }, + _ => {} + } + } + } + } +} diff --git a/scion.json b/scion.json new file mode 100644 index 0000000..770f7da --- /dev/null +++ b/scion.json @@ -0,0 +1 @@ +{"app_name":"Scion game","logger_config":{"level_filter":"INFO"},"window_config":{"title":"Default Scion game","fullscreen":false,"dimensions":[1024,768],"min_dimensions":[640,480],"max_dimensions":null,"visibility":true,"icon":null,"always_on_top":false,"decorations":true,"maximized":false,"resizable":true,"transparent":false}} \ No newline at end of file diff --git a/src/config/window_config.rs b/src/config/window_config.rs index 5b4d044..e792e75 100644 --- a/src/config/window_config.rs +++ b/src/config/window_config.rs @@ -42,7 +42,7 @@ impl Default for WindowConfig { title: "Default Scion game".to_string(), fullscreen: false, dimensions: Some((1024, 768)), - min_dimensions: Some((640, 480)), + min_dimensions: Some((500, 480)), max_dimensions: None, visibility: true, icon: None, @@ -71,6 +71,11 @@ impl WindowConfigBuilder { self } + pub fn with_resizable(mut self, resizable: bool) -> Self { + self.config.resizable = resizable; + self + } + pub fn get(self) -> WindowConfig { self.config } diff --git a/src/core/components/maths/transform.rs b/src/core/components/maths/transform.rs index 718e46c..cb278f7 100644 --- a/src/core/components/maths/transform.rs +++ b/src/core/components/maths/transform.rs @@ -75,6 +75,11 @@ impl Transform2D { self.coords.y += y; } + /// Move this transform down + pub fn move_down(&mut self, y: f32){ + self.coords.y += y; + } + /// Append an angle to this transform's angle pub fn append_angle(&mut self, angle: f32) { self.angle += angle; diff --git a/src/core/components/ui/font.rs b/src/core/components/ui/font.rs index 93ef352..72e9a51 100644 --- a/src/core/components/ui/font.rs +++ b/src/core/components/ui/font.rs @@ -1,4 +1,5 @@ /// [`Font`] represents the different fonts available in `Scion` +#[derive(Clone)] pub enum Font{ /// Texture based font Bitmap { diff --git a/src/core/components/ui/ui_text.rs b/src/core/components/ui/ui_text.rs index 4c34cdc..ab1cf61 100644 --- a/src/core/components/ui/ui_text.rs +++ b/src/core/components/ui/ui_text.rs @@ -26,6 +26,11 @@ impl UiText{ &self.text } + pub fn set_text(&mut self, text: String){ + self.text = text; + self.dirty = true; + } + pub fn font(&self)-> &Font{ &self.font } diff --git a/src/core/game_layer.rs b/src/core/game_layer.rs index 91fbb3d..8714d8a 100644 --- a/src/core/game_layer.rs +++ b/src/core/game_layer.rs @@ -6,13 +6,13 @@ use legion::{Resources, World}; /// Trait to implement in order to define a `GameLayer`. pub trait SimpleGameLayer { /// Will be called once before the new game loop iteration. Useful to initialize resources and add everything you need in the world. - fn on_start(&mut self, _world: &mut World, _resource: &mut Resources) {} + fn on_start(&mut self, _world: &mut World, _resources: &mut Resources) {} /// Will be called each game loop, before the systems execution - fn update(&mut self, _world: &mut World, _resource: &mut Resources) {} + fn update(&mut self, _world: &mut World, _resources: &mut Resources) {} /// Will be called each game loop, after the systems execution - fn late_update(&mut self, _world: &mut World, _resource: &mut Resources) {} + fn late_update(&mut self, _world: &mut World, _resources: &mut Resources) {} /// Will be called for deleted layer at the end of the frame where it was deleted - fn on_stop(&mut self, _world: &mut World, _resource: &mut Resources) {} + fn on_stop(&mut self, _world: &mut World, _resources: &mut Resources) {} } pub(crate) enum LayerAction { diff --git a/src/core/resources/time.rs b/src/core/resources/time.rs index ecf3113..980b041 100644 --- a/src/core/resources/time.rs +++ b/src/core/resources/time.rs @@ -116,6 +116,18 @@ mod timer { !self.running } + /// reset the timer end start it + pub fn reset(&mut self) { + self.running = true; + self.current_duration = 0.; + self.dirty = false; + } + + /// changes the total duration of this timer + pub fn change_cycle(&mut self, new_cycle: f32){ + self.total_duration = new_cycle; + } + /// Returns whether or not the timer has finished a cycle in the current frame pub fn cycle(&self) -> bool { self.dirty