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::<GameloopStates>().
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

View File

@ -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<Piece>;
fn populate_next(&self, pieces_data: &PiecesData) -> Vec<Piece>;
}
pub struct 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 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);

View File

@ -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<Vec<Option<Mino>>>
pub show_next: u8,
// X axis - from left to right; Y axis - from bottom to top (board[y][x])
pub board: Vec<Vec<Option<Mino>>>
}
impl Board{
pub fn create(width: u8, height: u8, buffer_height: u8, show_grid: bool, show_shadow: bool) -> Board {
let grid: 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 }
pub fn create(width: u8, height: u8, buffer_height: u8, show_grid: bool, show_shadow: bool, show_next: u8) -> Board {
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, 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<Option<Mino>> = 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<dyn Randomizer + Sync + Send>,
pub next_queue: Vec<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_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::<usize>() % 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,

View File

@ -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<Engine>,
mut next_state: ResMut<NextState<GameloopStates>>,
) {
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<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);
}
}