hold + next queue + game over condition
This commit is contained in:
parent
e9e2a21f14
commit
87d2459690
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue