hold + next queue + game over condition

This commit is contained in:
dan63047 2023-11-10 22:53:33 +03:00
parent e9e2a21f14
commit 87d2459690
4 changed files with 163 additions and 38 deletions

View File

@ -15,19 +15,20 @@ impl Plugin for UBSGEngine{
add_state::<GameStates>(). add_state::<GameStates>().
add_state::<GameloopStates>(). add_state::<GameloopStates>().
insert_resource(Engine::default()). insert_resource(Engine::default()).
add_systems(Startup, init_engine). add_systems(Startup, init_engine.run_if(in_state(GameStates::Gameplay))).
add_systems(Update, receive_input). add_systems(Update, receive_input.run_if(in_state(GameStates::Gameplay))).
add_systems(Update, das_and_arr). add_systems(Update, das_and_arr.run_if(in_state(GameStates::Gameplay))).
add_systems(FixedUpdate, gameloop.run_if(in_state(GameloopStates::Falling))). 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::AfterLocking), after_locking_routine).
add_systems(OnEnter(GameloopStates::Spawn), spawn_routine).
add_systems(Update, draw_board); add_systems(Update, draw_board);
} }
} }
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash, States)] #[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash, States)]
pub enum GameStates{ pub enum GameStates{
#[default]
Init, Init,
#[default]
Gameplay, Gameplay,
Pause, Pause,
GameOver GameOver

View File

@ -4,20 +4,18 @@ use rand::thread_rng;
use super::{rotation_systems::PiecesData, resources::Piece}; use super::{rotation_systems::PiecesData, resources::Piece};
pub trait Randomizer{ pub trait Randomizer{
fn populate_next(&self, pieces_data: PiecesData) -> Vec<Piece>; fn populate_next(&self, pieces_data: &PiecesData) -> Vec<Piece>;
} }
pub struct Bag {} pub struct Bag {}
impl Randomizer for Bag { impl Randomizer for Bag {
fn populate_next(&self, pieces_data: PiecesData) -> Vec<Piece> { fn populate_next(&self, pieces_data: &PiecesData) -> Vec<Piece> {
let mut bag = vec![]; let mut bag = vec![];
let mut id: usize = 0; let mut id: usize = 0;
for _ in pieces_data.pieces{ for _ in &pieces_data.pieces{
let final_position = (3+pieces_data.spawn_offsets[id].0, 20+pieces_data.spawn_offsets[id].1); bag.insert(id, Piece::create(pieces_data, id));
let element = Piece { id: id, position: final_position, rotation: 0 };
id += 1; id += 1;
bag.insert(0, element);
} }
let mut rng = thread_rng(); let mut rng = thread_rng();
bag.shuffle(&mut rng); bag.shuffle(&mut rng);

View File

@ -1,7 +1,8 @@
use bevy::prelude::*; use std::mem::swap;
use rand::random;
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)] #[derive(Clone, Copy)]
pub struct Piece{ pub struct Piece{
@ -10,29 +11,37 @@ pub struct Piece{
pub rotation: usize 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 struct Board{
pub width: u8, pub width: u8,
pub height: u8, pub height: u8,
pub buffer_height: u8, pub buffer_height: u8,
pub show_grid: bool, pub show_grid: bool,
pub show_shadow: bool, pub show_shadow: bool,
// X axis - from left to right; Y axis - from bottom to top (grid[y][x]) pub show_next: u8,
pub grid: Vec<Vec<Option<Mino>>> // X axis - from left to right; Y axis - from bottom to top (board[y][x])
pub board: Vec<Vec<Option<Mino>>>
} }
impl Board{ impl Board{
pub fn create(width: u8, height: u8, buffer_height: u8, show_grid: bool, show_shadow: bool) -> Board { pub fn create(width: u8, height: u8, buffer_height: u8, show_grid: bool, show_shadow: bool, show_next: u8) -> Board {
let grid: Vec<Vec<Option<Mino>>> = vec![vec![None; width as usize]; (height+buffer_height) as usize]; let board: Vec<Vec<Option<Mino>>> = 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 } 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) { pub fn clear_full_lines(&mut self) {
let mut lines_cleared: usize = 0; let mut lines_cleared: usize = 0;
for row in 0..self.grid.len(){ for row in 0..self.board.len(){
if self.grid[row-lines_cleared].iter().all(|l| l.is_some()){ if self.board[row-lines_cleared].iter().all(|l| l.is_some()){
self.grid.remove(row-lines_cleared); self.board.remove(row-lines_cleared);
let empty_row: Vec<Option<Mino>> = vec![None; self.width as usize]; let empty_row: Vec<Option<Mino>> = vec![None; self.width as usize];
self.grid.push(empty_row); self.board.push(empty_row);
lines_cleared += 1; lines_cleared += 1;
} }
} }
@ -119,9 +128,11 @@ pub struct Engine {
pub board: Board, pub board: Board,
pub handling: Handling, pub handling: Handling,
pub rotation_system: PiecesData, pub rotation_system: PiecesData,
pub randomizer: Box<dyn Randomizer + Sync + Send>,
pub next_queue: Vec<Piece>, pub next_queue: Vec<Piece>,
pub hold: Option<Piece>, pub hold: Option<Piece>,
pub can_hold: bool, pub can_hold: bool, // anti-abuse
pub hold_enabled: bool, // game rule
pub g: f32, pub g: f32,
pub g_bucket: f32, pub g_bucket: f32,
pub lock_delay: u8, pub lock_delay: u8,
@ -134,28 +145,65 @@ impl Default for Engine {
fn default() -> Engine { fn default() -> Engine {
Engine { Engine {
current_piece: None, 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), handling: Handling::create(200.0, 33.0, 20.0),
rotation_system: ROTATION_SYSTEMS["SRS"].clone(), rotation_system: ROTATION_SYSTEMS["SRS"].clone(),
next_queue: vec![], next_queue: vec![],
hold: None, hold: None,
can_hold: true, can_hold: true,
hold_enabled: true,
g: 1.0/60.0, g: 1.0/60.0,
g_bucket: 0.0, g_bucket: 0.0,
lock_delay: 30, lock_delay: 30,
lock_delay_left: 30, lock_delay_left: 30,
lock_delay_resets: 15, lock_delay_resets: 15,
lock_delay_resets_left: 15 lock_delay_resets_left: 15,
randomizer: Box::new(Bag{}),
} }
} }
} }
impl Engine { impl Engine {
pub fn temporary_random(&mut self){ fn from_next_to_current(&mut self){
let piece_id = random::<usize>() % self.rotation_system.pieces.len(); self.current_piece = self.next_queue.first().copied();
let final_position = (3+self.rotation_system.spawn_offsets[piece_id].0, 20+self.rotation_system.spawn_offsets[piece_id].1); self.next_queue.remove(0);
self.current_piece = Some(Piece { id: piece_id, position: final_position, rotation: 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() } 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 { pub fn lock_current_piece(&mut self) -> bool {
@ -167,7 +215,7 @@ impl Engine {
for mino in minos_to_write{ for mino in minos_to_write{
let x = (self.current_piece.as_ref().unwrap().position.0 + mino.0 as isize) as usize; 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; 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; self.current_piece = None;
return true; return true;
@ -225,7 +273,7 @@ impl Engine {
pub fn position_is_valid(&self, future_position: (isize, isize), future_rotation: usize) -> bool { 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]{ 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(line) => match line.get((future_position.0 + mino.0 as isize) as usize) {
Some(cell) => match cell { Some(cell) => match cell {
Some(_) => return false, Some(_) => return false,

View File

@ -1,6 +1,6 @@
use bevy::{prelude::*, sprite::MaterialMesh2dBundle}; use bevy::{prelude::*, sprite::MaterialMesh2dBundle};
use crate::engine::components::*; use crate::engine::components::*;
use super::{resources::Engine, GameloopStates}; use super::{resources::Engine, GameloopStates, GameStates};
const MINO_SIZE: f32 = 20.0; const MINO_SIZE: f32 = 20.0;
@ -20,7 +20,7 @@ pub fn init_engine(
}, },
BoardVisual{} BoardVisual{}
)); ));
engine.temporary_random(); engine.init("SRS");
next_state.set(GameloopStates::Falling); next_state.set(GameloopStates::Falling);
} }
@ -37,7 +37,7 @@ pub fn draw_board(
let mut y: f32 = 0.0; let mut y: f32 = 0.0;
// draw board // draw board
for row in &engine.board.grid { for row in &engine.board.board {
for mino in row { for mino in row {
match mino { match mino {
Some(mino) => { Some(mino) => {
@ -104,6 +104,70 @@ pub fn draw_board(
}, },
None => {}, 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( pub fn receive_input(
@ -118,6 +182,9 @@ pub fn receive_input(
if keyboard_input.just_pressed(KeyCode::Z) && state.get() == &GameloopStates::Falling { if keyboard_input.just_pressed(KeyCode::Z) && state.get() == &GameloopStates::Falling {
engine.rotate_current_piece(-1); 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 keyboard_input.just_pressed(KeyCode::Left) {
if state.get() == &GameloopStates::Falling { if state.get() == &GameloopStates::Falling {
engine.move_current_piece((-1, 0)); engine.move_current_piece((-1, 0));
@ -165,7 +232,7 @@ pub fn gameloop(
mut engine: ResMut<Engine>, mut engine: ResMut<Engine>,
mut next_state: ResMut<NextState<GameloopStates>>, mut next_state: ResMut<NextState<GameloopStates>>,
) { ) {
info!("{:?}", clocks); //info!("{:?}", clocks);
match engine.current_piece { match engine.current_piece {
Some(piece) => { Some(piece) => {
engine.g_bucket += engine.g; engine.g_bucket += engine.g;
@ -197,6 +264,17 @@ pub fn after_locking_routine(
engine.board.clear_full_lines(); engine.board.clear_full_lines();
engine.lock_delay_left = engine.lock_delay; engine.lock_delay_left = engine.lock_delay;
engine.lock_delay_resets_left = engine.lock_delay_resets; engine.lock_delay_resets_left = engine.lock_delay_resets;
engine.temporary_random(); next_state.set(GameloopStates::Spawn);
next_state.set(GameloopStates::Falling); }
pub fn spawn_routine(
mut engine: ResMut<Engine>,
mut next_state: ResMut<NextState<GameloopStates>>,
mut game_next_state: ResMut<NextState<GameStates>>
){
if engine.spawn_sequence(){
next_state.set(GameloopStates::Falling);
}else{
game_next_state.set(GameStates::GameOver);
}
} }