diff --git a/src/engine/mod.rs b/src/engine/mod.rs index 7bd8e90..b6cc9f7 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -15,19 +15,20 @@ impl Plugin for UBSGEngine{ add_state::(). add_state::(). insert_resource(Engine::default()). - add_systems(Startup, init_engine). - add_systems(Update, receive_input). - add_systems(Update, das_and_arr). - add_systems(FixedUpdate, gameloop.run_if(in_state(GameloopStates::Falling))). + add_systems(Startup, init_engine.run_if(in_state(GameStates::Gameplay))). + add_systems(Update, receive_input.run_if(in_state(GameStates::Gameplay))). + add_systems(Update, das_and_arr.run_if(in_state(GameStates::Gameplay))). + add_systems(FixedUpdate, gameloop.run_if(in_state(GameStates::Gameplay)).run_if(in_state(GameloopStates::Falling))). add_systems(OnEnter(GameloopStates::AfterLocking), after_locking_routine). + add_systems(OnEnter(GameloopStates::Spawn), spawn_routine). add_systems(Update, draw_board); } } #[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash, States)] pub enum GameStates{ - #[default] Init, + #[default] Gameplay, Pause, GameOver diff --git a/src/engine/randomizers.rs b/src/engine/randomizers.rs index c248b07..9e2df39 100644 --- a/src/engine/randomizers.rs +++ b/src/engine/randomizers.rs @@ -4,20 +4,18 @@ use rand::thread_rng; use super::{rotation_systems::PiecesData, resources::Piece}; pub trait Randomizer{ - fn populate_next(&self, pieces_data: PiecesData) -> Vec; + fn populate_next(&self, pieces_data: &PiecesData) -> Vec; } pub struct Bag {} impl Randomizer for Bag { - fn populate_next(&self, pieces_data: PiecesData) -> Vec { + fn populate_next(&self, pieces_data: &PiecesData) -> Vec { let mut bag = vec![]; let mut id: usize = 0; - for _ in pieces_data.pieces{ - let final_position = (3+pieces_data.spawn_offsets[id].0, 20+pieces_data.spawn_offsets[id].1); - let element = Piece { id: id, position: final_position, rotation: 0 }; + for _ in &pieces_data.pieces{ + bag.insert(id, Piece::create(pieces_data, id)); id += 1; - bag.insert(0, element); } let mut rng = thread_rng(); bag.shuffle(&mut rng); diff --git a/src/engine/resources.rs b/src/engine/resources.rs index 82946de..0184978 100644 --- a/src/engine/resources.rs +++ b/src/engine/resources.rs @@ -1,7 +1,8 @@ -use bevy::prelude::*; -use rand::random; +use std::mem::swap; -use super::{rotation_systems::{PiecesData, ROTATION_SYSTEMS}, components::Mino}; +use bevy::prelude::*; + +use super::{rotation_systems::{PiecesData, ROTATION_SYSTEMS}, components::Mino, randomizers::{Randomizer, Bag}}; #[derive(Clone, Copy)] pub struct Piece{ @@ -10,29 +11,37 @@ pub struct Piece{ pub rotation: usize } +impl Piece { + pub fn create(pieces_data: &PiecesData, id: usize) -> Piece{ + let final_position = (3+pieces_data.spawn_offsets[id].0, 20+pieces_data.spawn_offsets[id].1); + Piece { id: id, position: final_position, rotation: 0 } + } +} + pub struct Board{ pub width: u8, pub height: u8, pub buffer_height: u8, pub show_grid: bool, pub show_shadow: bool, - // X axis - from left to right; Y axis - from bottom to top (grid[y][x]) - pub grid: Vec>> + pub show_next: u8, + // X axis - from left to right; Y axis - from bottom to top (board[y][x]) + pub board: Vec>> } impl Board{ - pub fn create(width: u8, height: u8, buffer_height: u8, show_grid: bool, show_shadow: bool) -> Board { - let grid: Vec>> = vec![vec![None; width as usize]; (height+buffer_height) as usize]; - Board { width: width, height: height, buffer_height: buffer_height, show_grid: show_grid, show_shadow: show_shadow, grid: grid } + pub fn create(width: u8, height: u8, buffer_height: u8, show_grid: bool, show_shadow: bool, show_next: u8) -> Board { + let board: Vec>> = vec![vec![None; width as usize]; (height+buffer_height) as usize]; + Board { width: width, height: height, buffer_height: buffer_height, show_grid: show_grid, show_shadow: show_shadow, show_next: show_next, board: board } } pub fn clear_full_lines(&mut self) { let mut lines_cleared: usize = 0; - for row in 0..self.grid.len(){ - if self.grid[row-lines_cleared].iter().all(|l| l.is_some()){ - self.grid.remove(row-lines_cleared); + for row in 0..self.board.len(){ + if self.board[row-lines_cleared].iter().all(|l| l.is_some()){ + self.board.remove(row-lines_cleared); let empty_row: Vec> = vec![None; self.width as usize]; - self.grid.push(empty_row); + self.board.push(empty_row); lines_cleared += 1; } } @@ -119,9 +128,11 @@ pub struct Engine { pub board: Board, pub handling: Handling, pub rotation_system: PiecesData, + pub randomizer: Box, pub next_queue: Vec, pub hold: Option, - pub can_hold: bool, + pub can_hold: bool, // anti-abuse + pub hold_enabled: bool, // game rule pub g: f32, pub g_bucket: f32, pub lock_delay: u8, @@ -134,28 +145,65 @@ impl Default for Engine { fn default() -> Engine { Engine { current_piece: None, - board: Board::create(10, 20, 20, true, true), + board: Board::create(10, 20, 20, true, true, 3), handling: Handling::create(200.0, 33.0, 20.0), rotation_system: ROTATION_SYSTEMS["SRS"].clone(), next_queue: vec![], hold: None, can_hold: true, + hold_enabled: true, g: 1.0/60.0, g_bucket: 0.0, lock_delay: 30, lock_delay_left: 30, lock_delay_resets: 15, - lock_delay_resets_left: 15 + lock_delay_resets_left: 15, + randomizer: Box::new(Bag{}), } } } impl Engine { - pub fn temporary_random(&mut self){ - let piece_id = random::() % self.rotation_system.pieces.len(); - let final_position = (3+self.rotation_system.spawn_offsets[piece_id].0, 20+self.rotation_system.spawn_offsets[piece_id].1); - self.current_piece = Some(Piece { id: piece_id, position: final_position, rotation: 0 }); + fn from_next_to_current(&mut self){ + self.current_piece = self.next_queue.first().copied(); + self.next_queue.remove(0); + } + + pub fn init(&mut self, rotation_system: &str){ + self.rotation_system = ROTATION_SYSTEMS[rotation_system].clone(); + self.next_queue.append(&mut self.randomizer.populate_next(&self.rotation_system)); + self.from_next_to_current(); + } + + pub fn spawn_sequence(&mut self) -> bool { + if self.next_queue.len() <= self.board.show_next as usize { + self.next_queue.append(&mut self.randomizer.populate_next(&self.rotation_system)); + } + self.from_next_to_current(); + if !self.position_is_valid(self.current_piece.as_ref().unwrap().position, self.current_piece.as_ref().unwrap().rotation){ + return false; + } + self.can_hold = true; if self.g >= 20.0 { self.current_piece.as_mut().unwrap().position.1 = self.lowest_point_under_current_piece() } + true + } + + pub fn hold_current_piece(&mut self) -> bool { + if !self.hold_enabled || !self.can_hold { + return false; + } + self.current_piece.as_mut().unwrap().rotation = 0; + match self.hold { + Some(_) => { + swap(&mut self.current_piece, &mut self.hold); + } + None => { + self.hold = self.current_piece; + self.from_next_to_current(); + }, + } + self.can_hold = false; + true } pub fn lock_current_piece(&mut self) -> bool { @@ -167,7 +215,7 @@ impl Engine { for mino in minos_to_write{ let x = (self.current_piece.as_ref().unwrap().position.0 + mino.0 as isize) as usize; let y = (self.current_piece.as_ref().unwrap().position.1 + mino.1 as isize) as usize; - self.board.grid[y][x] = Some(Mino{ skin_index: color_index }); + self.board.board[y][x] = Some(Mino{ skin_index: color_index }); } self.current_piece = None; return true; @@ -225,7 +273,7 @@ impl Engine { pub fn position_is_valid(&self, future_position: (isize, isize), future_rotation: usize) -> bool { for mino in &self.rotation_system.pieces[self.current_piece.as_ref().unwrap().id][future_rotation]{ - match self.board.grid.get((future_position.1 + mino.1 as isize) as usize) { + match self.board.board.get((future_position.1 + mino.1 as isize) as usize) { Some(line) => match line.get((future_position.0 + mino.0 as isize) as usize) { Some(cell) => match cell { Some(_) => return false, diff --git a/src/engine/systems.rs b/src/engine/systems.rs index a860733..18f62aa 100644 --- a/src/engine/systems.rs +++ b/src/engine/systems.rs @@ -1,6 +1,6 @@ use bevy::{prelude::*, sprite::MaterialMesh2dBundle}; use crate::engine::components::*; -use super::{resources::Engine, GameloopStates}; +use super::{resources::Engine, GameloopStates, GameStates}; const MINO_SIZE: f32 = 20.0; @@ -20,7 +20,7 @@ pub fn init_engine( }, BoardVisual{} )); - engine.temporary_random(); + engine.init("SRS"); next_state.set(GameloopStates::Falling); } @@ -37,7 +37,7 @@ pub fn draw_board( let mut y: f32 = 0.0; // draw board - for row in &engine.board.grid { + for row in &engine.board.board { for mino in row { match mino { Some(mino) => { @@ -104,6 +104,70 @@ pub fn draw_board( }, None => {}, } + + // draw next queue + if engine.board.show_next > 0 { + y = 8.0; + for i in 0..engine.board.show_next as usize{ + for mino in &engine.rotation_system.pieces[engine.next_queue[i].id][engine.next_queue[i].rotation]{ + commands.spawn(( + SpriteBundle { + transform: Transform::from_xyz( + 11.0*MINO_SIZE - (engine.board.width as f32)/2.0*MINO_SIZE + MINO_SIZE/2.0 + mino.0 as f32 * MINO_SIZE, + y* MINO_SIZE + MINO_SIZE/2.0 + mino.1 as f32 * MINO_SIZE, + 0.0 + ), + texture: asset_server.load("skin.png"), + sprite: Sprite { + rect: Some( + Rect{ + min: Vec2 { x: 00.0+(64.0*engine.rotation_system.skin_index[engine.next_queue[i].id] as f32), y: 00.0 }, + max: Vec2 { x: 63.0+(64.0*engine.rotation_system.skin_index[engine.next_queue[i].id] as f32), y: 63.0 }, + } + ), + custom_size: Some(Vec2 {x: MINO_SIZE, y: MINO_SIZE}), + ..default() + }, + ..default() + }, + Mino{skin_index: engine.rotation_system.skin_index[engine.next_queue[i].id]}, + )); + } + y -= 4.0; + } + } + + // draw hold + match engine.hold.as_ref() { + Some(piece) => { + for mino in &engine.rotation_system.pieces[piece.id][piece.rotation]{ + commands.spawn(( + SpriteBundle { + transform: Transform::from_xyz( + -5.0*MINO_SIZE - (engine.board.width as f32)/2.0*MINO_SIZE + MINO_SIZE/2.0 + mino.0 as f32 * MINO_SIZE, + -2.0*MINO_SIZE + (engine.board.height as f32)/2.0*MINO_SIZE + MINO_SIZE/2.0 + mino.1 as f32 * MINO_SIZE, + 0.0 + ), + texture: asset_server.load("skin.png"), + sprite: Sprite { + rect: Some( + Rect{ + min: Vec2 { x: 00.0+(64.0*engine.rotation_system.skin_index[piece.id] as f32), y: 00.0 }, + max: Vec2 { x: 63.0+(64.0*engine.rotation_system.skin_index[piece.id] as f32), y: 63.0 }, + } + ), + custom_size: Some(Vec2 {x: MINO_SIZE, y: MINO_SIZE}), + ..default() + }, + ..default() + }, + Mino{skin_index: engine.rotation_system.skin_index[piece.id]}, + )); + } + }, + None => {}, + } + } pub fn receive_input( @@ -118,6 +182,9 @@ pub fn receive_input( if keyboard_input.just_pressed(KeyCode::Z) && state.get() == &GameloopStates::Falling { engine.rotate_current_piece(-1); } + if keyboard_input.just_pressed(KeyCode::C) && state.get() == &GameloopStates::Falling { + engine.hold_current_piece(); + } if keyboard_input.just_pressed(KeyCode::Left) { if state.get() == &GameloopStates::Falling { engine.move_current_piece((-1, 0)); @@ -165,7 +232,7 @@ pub fn gameloop( mut engine: ResMut, mut next_state: ResMut>, ) { - info!("{:?}", clocks); + //info!("{:?}", clocks); match engine.current_piece { Some(piece) => { engine.g_bucket += engine.g; @@ -197,6 +264,17 @@ pub fn after_locking_routine( engine.board.clear_full_lines(); engine.lock_delay_left = engine.lock_delay; engine.lock_delay_resets_left = engine.lock_delay_resets; - engine.temporary_random(); - next_state.set(GameloopStates::Falling); + next_state.set(GameloopStates::Spawn); +} + +pub fn spawn_routine( + mut engine: ResMut, + mut next_state: ResMut>, + mut game_next_state: ResMut> +){ + if engine.spawn_sequence(){ + next_state.set(GameloopStates::Falling); + }else{ + game_next_state.set(GameStates::GameOver); + } } \ No newline at end of file