pause/game over
This commit is contained in:
parent
63a8c0bd78
commit
fc6ebcdbc2
@ -5,4 +5,7 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
rand = "0.9.0"
|
||||
sdl2 = "0.37.0"
|
||||
|
||||
[dependencies.sdl2]
|
||||
version = "0.37.0"
|
||||
features = ["ttf"]
|
||||
|
77
src/game.rs
77
src/game.rs
@ -22,6 +22,7 @@ impl CurrentTetromino {
|
||||
}
|
||||
|
||||
pub struct Game {
|
||||
pub game_over: bool,
|
||||
pub board: Board,
|
||||
pub next_tetrominos: [Tetromino; 3],
|
||||
pub current_tetromino: CurrentTetromino,
|
||||
@ -31,11 +32,18 @@ pub struct Game {
|
||||
pub ticks: usize,
|
||||
}
|
||||
|
||||
struct Score {
|
||||
level: usize,
|
||||
points: usize,
|
||||
lines: usize,
|
||||
combo: usize,
|
||||
pub enum SoundEffect {
|
||||
HardDrop,
|
||||
LineClear(usize),
|
||||
Move,
|
||||
Rotation,
|
||||
}
|
||||
|
||||
pub struct Score {
|
||||
pub level: usize,
|
||||
pub points: usize,
|
||||
pub lines: usize,
|
||||
pub combo: usize,
|
||||
back_to_back: bool,
|
||||
}
|
||||
|
||||
@ -81,6 +89,7 @@ impl Score {
|
||||
impl Game {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
game_over: false,
|
||||
board: Board::new(),
|
||||
next_tetrominos: std::array::from_fn(|_| Tetromino::random()),
|
||||
current_tetromino: CurrentTetromino::new(Tetromino::random()),
|
||||
@ -97,7 +106,7 @@ impl Game {
|
||||
last
|
||||
}
|
||||
|
||||
fn hard_drop(&mut self, actions: &ActionsHeld) {
|
||||
fn try_hard_drop(&mut self, actions: &ActionsHeld, effects: &mut Vec<SoundEffect>) {
|
||||
if !actions.just_pressed(self.ticks, &Action::HardDrop) {
|
||||
return;
|
||||
}
|
||||
@ -110,12 +119,12 @@ impl Game {
|
||||
self.current_tetromino.y -= 1;
|
||||
self.score.points += (self.current_tetromino.y - start_y) as usize * 2;
|
||||
self.place_current_tetromino();
|
||||
self.check_line_clears();
|
||||
self.check_line_clears(effects);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fn soft_drop(&mut self, actions: &ActionsHeld) {
|
||||
fn soft_drop(&mut self, actions: &ActionsHeld, effects: &mut Vec<SoundEffect>) {
|
||||
let mut delay = 32 - self.score.level * 2;
|
||||
if actions.contains_key(&Action::SoftDrop) {
|
||||
delay /= 10;
|
||||
@ -129,13 +138,13 @@ impl Game {
|
||||
if self.board.colliding(&self.current_tetromino) {
|
||||
self.current_tetromino.y -= 1;
|
||||
self.place_current_tetromino();
|
||||
self.check_line_clears();
|
||||
self.check_line_clears(effects);
|
||||
} else if actions.contains_key(&Action::SoftDrop) {
|
||||
self.score.points += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn move_horizontally(&mut self, actions: &ActionsHeld) {
|
||||
fn try_move_horizontally(&mut self, actions: &ActionsHeld, effects: &mut Vec<SoundEffect>) {
|
||||
for key in [Action::Left, Action::Right] {
|
||||
let just_pressed = actions.just_pressed(self.ticks, &key);
|
||||
let long_press = actions.held_for(self.ticks, &key, |held_for| held_for > 15);
|
||||
@ -150,11 +159,13 @@ impl Game {
|
||||
self.current_tetromino.x += offset;
|
||||
if self.board.colliding(&self.current_tetromino) {
|
||||
self.current_tetromino.x -= offset;
|
||||
} else {
|
||||
effects.push(SoundEffect::Move);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_line_clears(&mut self) {
|
||||
fn check_line_clears(&mut self, effects: &mut Vec<SoundEffect>) {
|
||||
let lines_cleared = self.board.lines_cleared();
|
||||
|
||||
self.score.level_up(lines_cleared);
|
||||
@ -177,20 +188,24 @@ impl Game {
|
||||
|
||||
if lines_cleared > 0 {
|
||||
self.score.combo += 1;
|
||||
// play_line_clears_sound();
|
||||
effects.push(SoundEffect::LineClear(lines_cleared));
|
||||
} else {
|
||||
self.score.combo = 0;
|
||||
// play_hard_drop_sound();
|
||||
effects.push(SoundEffect::HardDrop);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn step(&mut self, actions: &ActionsHeld) {
|
||||
self.hard_drop(actions);
|
||||
self.soft_drop(actions);
|
||||
self.move_horizontally(actions);
|
||||
pub fn step(&mut self, actions: &ActionsHeld) -> Vec<SoundEffect> {
|
||||
if self.game_over {
|
||||
panic!("should check if game is over before stepping");
|
||||
}
|
||||
let mut effects = Vec::new();
|
||||
self.try_hard_drop(actions, &mut effects);
|
||||
self.soft_drop(actions, &mut effects);
|
||||
self.try_move_horizontally(actions, &mut effects);
|
||||
|
||||
if actions.just_pressed(self.ticks, &Action::Swap) {
|
||||
self.try_swap_tetromino();
|
||||
self.try_swap_tetromino(&mut effects);
|
||||
}
|
||||
|
||||
for (control, direction) in [
|
||||
@ -200,16 +215,18 @@ impl Game {
|
||||
if !actions.just_pressed(self.ticks, &control) {
|
||||
continue;
|
||||
}
|
||||
self.try_rotate(direction);
|
||||
self.try_rotate(direction, &mut effects);
|
||||
}
|
||||
self.ticks += 1;
|
||||
effects
|
||||
}
|
||||
|
||||
fn try_rotate(&mut self, diff: DirectionDiff) -> bool {
|
||||
fn try_rotate(&mut self, diff: DirectionDiff, effects: &mut Vec<SoundEffect>) {
|
||||
let rotated = self.current_tetromino.direction.rotate(&diff);
|
||||
let old_direction = std::mem::replace(&mut self.current_tetromino.direction, rotated);
|
||||
if !self.board.colliding(&self.current_tetromino) {
|
||||
return true;
|
||||
effects.push(SoundEffect::Rotation);
|
||||
return;
|
||||
}
|
||||
let wall_kicks = self
|
||||
.current_tetromino
|
||||
@ -220,14 +237,14 @@ impl Game {
|
||||
self.current_tetromino.x += x;
|
||||
self.current_tetromino.y += y;
|
||||
if !(self.board.colliding(&self.current_tetromino)) {
|
||||
return true;
|
||||
effects.push(SoundEffect::Rotation);
|
||||
return;
|
||||
}
|
||||
self.current_tetromino.x -= x;
|
||||
self.current_tetromino.y -= y;
|
||||
}
|
||||
|
||||
self.current_tetromino.direction = old_direction;
|
||||
false
|
||||
}
|
||||
|
||||
fn place_current_tetromino(&mut self) {
|
||||
@ -235,6 +252,10 @@ impl Game {
|
||||
let current = std::mem::replace(&mut self.current_tetromino, next);
|
||||
let pattern = current.tetromino.direction_pattern(¤t.direction);
|
||||
|
||||
if current.y <= 0 {
|
||||
self.game_over = true;
|
||||
}
|
||||
|
||||
for (y, row) in pattern.iter().enumerate() {
|
||||
for x in row
|
||||
.iter()
|
||||
@ -242,7 +263,11 @@ impl Game {
|
||||
.filter(|(_, exists)| **exists)
|
||||
.map(|(x, _)| x)
|
||||
{
|
||||
let y = (current.y + y as i8) as usize;
|
||||
let y = current.y + y as i8;
|
||||
if y < 0 {
|
||||
continue;
|
||||
}
|
||||
let y = y as usize;
|
||||
let x = (current.x + x as i8) as usize;
|
||||
self.board[y][x] = Some(current.tetromino.clone());
|
||||
}
|
||||
@ -251,7 +276,7 @@ impl Game {
|
||||
self.has_swapped_held = false;
|
||||
}
|
||||
|
||||
fn try_swap_tetromino(&mut self) {
|
||||
fn try_swap_tetromino(&mut self, effects: &mut Vec<SoundEffect>) {
|
||||
if self.has_swapped_held {
|
||||
return;
|
||||
}
|
||||
@ -263,6 +288,7 @@ impl Game {
|
||||
let current_tetromino = CurrentTetromino::new(held_or_first_in_bag_tetromino);
|
||||
let old_tetromino = std::mem::replace(&mut self.current_tetromino, current_tetromino);
|
||||
self.held_tetromino.replace(old_tetromino.tetromino);
|
||||
effects.push(SoundEffect::Rotation);
|
||||
}
|
||||
}
|
||||
|
||||
@ -273,6 +299,7 @@ mod test {
|
||||
#[test]
|
||||
fn advance_bag() {
|
||||
let mut game = Game {
|
||||
game_over: false,
|
||||
board: Board::new(),
|
||||
score: Score::new(),
|
||||
next_tetrominos: [Tetromino::I, Tetromino::J, Tetromino::O],
|
||||
|
189
src/sdl_impl.rs
189
src/sdl_impl.rs
@ -1,31 +1,130 @@
|
||||
use crate::actions::{Action, ActionsHeld};
|
||||
use crate::board::Board;
|
||||
use crate::game::Game;
|
||||
use crate::game::{CurrentTetromino, Game};
|
||||
use crate::Rgb;
|
||||
use sdl2::event::Event;
|
||||
use sdl2::keyboard::Keycode;
|
||||
use sdl2::pixels::Color;
|
||||
use sdl2::rect::Rect;
|
||||
use sdl2::render::WindowCanvas;
|
||||
use sdl2::render::{Canvas, RenderTarget, Texture, TextureCreator, WindowCanvas};
|
||||
use sdl2::rwops::RWops;
|
||||
use sdl2::ttf::Sdl2TtfContext;
|
||||
use std::time::Duration;
|
||||
|
||||
fn draw_board(canvas: &mut WindowCanvas, width: usize, height: usize, color: Rgb) {
|
||||
fn draw_board_tile(canvas: &mut WindowCanvas, width: usize, height: usize, color: Rgb) {
|
||||
canvas.set_draw_color(Color::RGB(color.0, color.1, color.2));
|
||||
canvas
|
||||
.fill_rect(Rect::new(
|
||||
(800 - 24 * Board::WIDTH as i32) / 2 + width as i32 * 24,
|
||||
(600 - 24 * Board::HEIGHT as i32) / 2 + height as i32 * 24,
|
||||
center(24 * Board::WIDTH as i32, 800) + width as i32 * 24,
|
||||
center(24 * Board::HEIGHT as i32, 600) + height as i32 * 24,
|
||||
24,
|
||||
24,
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn center(length: i32, max: i32) -> i32 {
|
||||
(max - length) / 2
|
||||
}
|
||||
|
||||
fn draw_board(canvas: &mut WindowCanvas, board: &Board, current: &CurrentTetromino) {
|
||||
canvas.set_draw_color(Color::WHITE);
|
||||
canvas
|
||||
.draw_rect(Rect::new(
|
||||
center(24 * Board::WIDTH as i32, 800) - 1,
|
||||
center(24 * Board::HEIGHT as i32, 600) - 1,
|
||||
24 * Board::WIDTH as u32 + 2,
|
||||
24 * Board::HEIGHT as u32 + 2,
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
for (y, row) in board.iter().enumerate() {
|
||||
for (x, piece) in row.iter().enumerate() {
|
||||
let color = match piece {
|
||||
Some(t) => Rgb::from_tetromino(t),
|
||||
None => Rgb(0, 0, 0),
|
||||
};
|
||||
draw_board_tile(canvas, x, y, color)
|
||||
}
|
||||
}
|
||||
|
||||
let pattern = current.tetromino.direction_pattern(¤t.direction);
|
||||
|
||||
for (y, row) in pattern.iter().enumerate() {
|
||||
for x in row
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, exists)| **exists)
|
||||
.map(|(x, _)| x)
|
||||
{
|
||||
let x = x as i8 + current.x;
|
||||
let y = y as i8 + current.y;
|
||||
|
||||
if y < 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
draw_board_tile(
|
||||
canvas,
|
||||
x as usize,
|
||||
y as usize,
|
||||
Rgb::from_tetromino(¤t.tetromino),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn font_texture<'font, 'a, C>(
|
||||
text: &'a str,
|
||||
ttf_context: &'a Sdl2TtfContext,
|
||||
texture_creator: &'font TextureCreator<C>,
|
||||
) -> Texture<'font> {
|
||||
let font = ttf_context
|
||||
.load_font_from_rwops(
|
||||
RWops::from_bytes(include_bytes!("res/josenfin_sans_regular.ttf")).unwrap(),
|
||||
24,
|
||||
)
|
||||
.unwrap();
|
||||
let game_over_text = font.render(text).solid(Color::RGB(255, 255, 255)).unwrap();
|
||||
let texture = texture_creator
|
||||
.create_texture_from_surface(game_over_text)
|
||||
.unwrap();
|
||||
|
||||
texture
|
||||
}
|
||||
|
||||
fn draw_important_text(
|
||||
text: &str,
|
||||
canvas: &mut WindowCanvas,
|
||||
ttf_context: &Sdl2TtfContext,
|
||||
) -> Result<(), String> {
|
||||
let texture_creator = canvas.texture_creator();
|
||||
let texture = font_texture(text, &ttf_context, &texture_creator);
|
||||
|
||||
let size = texture.query();
|
||||
let width = size.width;
|
||||
let height = size.height;
|
||||
|
||||
let x = center(width as i32, 800);
|
||||
let y = center(height as i32, 600);
|
||||
|
||||
canvas.set_draw_color(Color::WHITE);
|
||||
canvas.draw_rect(Rect::new(x - 9, y - 9, width + 18, height + 18))?;
|
||||
|
||||
canvas.set_draw_color(Color::RGB(16, 16, 16));
|
||||
canvas.fill_rect(Rect::new(x - 8, y - 8, width + 16, height + 16))?;
|
||||
canvas.copy(&texture, None, Some(Rect::new(x, y, width, height)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn start_game() {
|
||||
let mut game = Game::new();
|
||||
let mut actions = ActionsHeld::new();
|
||||
let mut paused = false;
|
||||
|
||||
let sdl_context = sdl2::init().unwrap();
|
||||
let ttf_context = sdl2::ttf::init().unwrap();
|
||||
let video_subsystem = sdl_context.video().unwrap();
|
||||
|
||||
let window = video_subsystem
|
||||
@ -51,6 +150,14 @@ pub fn start_game() {
|
||||
..
|
||||
} => {
|
||||
let keycode = match keycode {
|
||||
Keycode::Return if !paused && game.game_over => {
|
||||
game = Game::new();
|
||||
continue;
|
||||
}
|
||||
Keycode::P => {
|
||||
paused = !paused;
|
||||
continue;
|
||||
}
|
||||
Keycode::Left | Keycode::A => Action::Left,
|
||||
Keycode::Right | Keycode::D => Action::Right,
|
||||
Keycode::Down | Keycode::S => Action::SoftDrop,
|
||||
@ -82,64 +189,26 @@ pub fn start_game() {
|
||||
}
|
||||
}
|
||||
|
||||
canvas.set_draw_color(Color::WHITE);
|
||||
canvas
|
||||
.draw_rect(Rect::new(
|
||||
(800 - 24 * Board::WIDTH as i32) / 2 - 1,
|
||||
(600 - 24 * Board::HEIGHT as i32) / 2 - 1,
|
||||
24 * Board::WIDTH as u32 + 2,
|
||||
24 * Board::HEIGHT as u32 + 2,
|
||||
))
|
||||
draw_board(&mut canvas, &game.board, &game.current_tetromino);
|
||||
|
||||
if paused {
|
||||
draw_important_text(
|
||||
"game paused o_o... press [p] to unpause !!",
|
||||
&mut canvas,
|
||||
&ttf_context,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
for (y, row) in game.board.iter().enumerate() {
|
||||
for (x, piece) in row.iter().enumerate() {
|
||||
let color = match piece {
|
||||
Some(t) => Rgb::from_tetromino(t),
|
||||
None => Rgb(0, 0, 0),
|
||||
};
|
||||
draw_board(&mut canvas, x, y, color)
|
||||
}
|
||||
} else if game.game_over {
|
||||
draw_important_text(
|
||||
"game over T_T... press [enter] 2 restart :D",
|
||||
&mut canvas,
|
||||
&ttf_context,
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
game.step(&actions);
|
||||
}
|
||||
|
||||
let pattern = game
|
||||
.current_tetromino
|
||||
.tetromino
|
||||
.direction_pattern(&game.current_tetromino.direction);
|
||||
|
||||
for (y, row) in pattern.iter().enumerate() {
|
||||
for x in row
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, exists)| **exists)
|
||||
.map(|(x, _)| x)
|
||||
{
|
||||
let x = x as i8 + game.current_tetromino.x;
|
||||
let y = y as i8 + game.current_tetromino.y;
|
||||
|
||||
if y < 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if y >= Board::HEIGHT as i8 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if x < 0 || x >= Board::WIDTH as i8 {
|
||||
continue;
|
||||
}
|
||||
|
||||
draw_board(
|
||||
&mut canvas,
|
||||
x as usize,
|
||||
y as usize,
|
||||
Rgb::from_tetromino(&game.current_tetromino.tetromino),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
game.step(&actions);
|
||||
|
||||
canvas.present();
|
||||
::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 60));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user