tetris/src/game.rs

215 lines
6.1 KiB
Rust
Raw Normal View History

2020-03-19 15:51:43 -07:00
// A game is a self-contained struct that holds everything that an instance of
// Tetris needs to run, except for something to tick the time forward.
use crate::playfield::PlayField;
use crate::srs::RotationSystem;
use crate::srs::SRS;
2020-03-20 21:05:39 -07:00
use crate::tetromino::Position;
use crate::Renderable;
use crate::TICKS_PER_SECOND;
use log::trace;
use sdl2::{render::Canvas, video::Window};
use std::fmt;
2020-03-19 15:51:43 -07:00
2020-03-20 21:05:39 -07:00
// I think this was correct, can't find source
const LINE_CLEAR_DELAY: u64 = TICKS_PER_SECOND as u64 * 41 / 60;
// Logic is based on 60 ticks / second
pub struct Game {
2020-03-19 15:51:43 -07:00
playfield: PlayField,
2020-03-20 21:05:39 -07:00
rotation_system: SRS,
2020-03-19 15:51:43 -07:00
level: u8,
2020-03-21 23:47:17 -07:00
score: u32,
2020-03-20 21:37:02 -07:00
tick: u64,
2020-03-20 21:05:39 -07:00
next_gravity_tick: u64,
next_lock_tick: u64,
next_spawn_tick: u64,
is_game_over: bool,
}
impl fmt::Debug for Game {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2020-03-21 23:47:17 -07:00
writeln!(f, "level: {}, points: {}", self.level, self.score)?;
2020-03-20 21:05:39 -07:00
writeln!(f, "tick: {}", self.tick)?;
write!(f, "{:?}", self.playfield)
}
2020-03-19 15:51:43 -07:00
}
2020-03-20 21:05:39 -07:00
impl Default for Game {
2020-03-19 16:47:50 -07:00
fn default() -> Self {
Game {
playfield: PlayField::new(),
2020-03-20 21:05:39 -07:00
rotation_system: SRS::default(),
level: 1,
2020-03-21 23:47:17 -07:00
score: 0,
2020-03-20 21:05:39 -07:00
tick: 0,
next_gravity_tick: 60,
next_lock_tick: 0,
next_spawn_tick: 0,
is_game_over: false,
2020-03-19 16:47:50 -07:00
}
}
}
2020-03-20 21:05:39 -07:00
pub trait Tickable {
fn tick(&mut self);
}
impl Tickable for Game {
fn tick(&mut self) {
if self.is_game_over() {
return;
}
self.tick += 1;
match self.tick {
t if t == self.next_spawn_tick => self.spawn_tetromino(),
t if t == self.next_lock_tick => {
2020-03-21 23:47:17 -07:00
self.try_lock_tetromino();
2020-03-20 21:05:39 -07:00
}
t if t == self.next_gravity_tick => {
self.playfield.tick_gravity();
if !self.playfield.can_active_piece_move_down() {
self.update_lock_tick();
}
self.update_gravity_tick();
}
_ => (),
}
}
}
impl Game {
pub fn is_game_over(&self) -> bool {
self.is_game_over || !self.playfield.is_active_piece_in_valid_position()
}
fn update_gravity_tick(&mut self) {
2020-03-22 14:33:38 -07:00
// self.next_gravity_tick = (-1 as i64) as u64;
self.next_gravity_tick = self.tick + TICKS_PER_SECOND as u64;
2020-03-20 21:05:39 -07:00
}
fn update_lock_tick(&mut self) {
self.next_lock_tick = self.tick + TICKS_PER_SECOND as u64 / 2;
}
fn spawn_tetromino(&mut self) {
self.playfield.spawn_tetromino();
self.playfield.tick_gravity();
self.update_gravity_tick();
}
/// Returns if some lines were cleared
fn clear_lines(&mut self) -> bool {
// todo: award points based on how lines were cleared
return false;
}
2020-03-21 23:47:17 -07:00
fn try_lock_tetromino(&mut self) -> bool {
// It's possible that the player moved the piece in the meantime.
if !self.playfield.can_active_piece_move_down() {
let positions = self.playfield.lock_active_piece();
self.is_game_over =
self.is_game_over || positions.iter().all(|Position { x: _, y }| *y < 20);
if self.clear_lines() {
self.next_spawn_tick = self.tick + LINE_CLEAR_DELAY;
} else {
self.spawn_tetromino();
}
true
} else {
false
}
}
2020-03-20 21:05:39 -07:00
}
impl Renderable for Game {
fn render(&self, canvas: &mut Canvas<Window>) -> Result<(), String> {
self.playfield.render(canvas)
}
}
2020-03-20 21:37:02 -07:00
pub trait Controllable {
2020-03-19 15:51:43 -07:00
fn move_left(&mut self);
fn move_right(&mut self);
fn move_down(&mut self);
fn rotate_left(&mut self);
fn rotate_right(&mut self);
fn hard_drop(&mut self);
fn hold(&mut self);
}
2020-03-20 21:05:39 -07:00
impl Controllable for Game {
2020-03-20 21:37:02 -07:00
fn move_left(&mut self) {
self.playfield.move_offset(-1, 0);
2020-03-22 14:33:38 -07:00
if !self.playfield.can_active_piece_move_down() {
self.update_lock_tick();
}
2020-03-20 21:37:02 -07:00
}
fn move_right(&mut self) {
self.playfield.move_offset(1, 0);
2020-03-22 14:33:38 -07:00
if !self.playfield.can_active_piece_move_down() {
self.update_lock_tick();
}
2020-03-20 21:37:02 -07:00
}
fn move_down(&mut self) {
2020-03-21 23:47:17 -07:00
if self.playfield.move_offset(0, 1) {
self.score += 1;
self.update_gravity_tick();
self.update_lock_tick();
}
2020-03-20 21:37:02 -07:00
}
fn rotate_left(&mut self) {
2020-03-21 23:47:17 -07:00
match self.rotation_system.rotate_left(&self.playfield) {
Ok(Position { x, y }) => {
let mut active_piece = self.playfield.active_piece.unwrap().clone();
active_piece.position = active_piece.position.offset(x, y);
active_piece.rotate_left();
self.playfield.active_piece = Some(active_piece);
self.update_lock_tick();
}
Err(_) => (),
}
2020-03-20 21:37:02 -07:00
}
fn rotate_right(&mut self) {
2020-03-21 23:47:17 -07:00
match self.rotation_system.rotate_right(&self.playfield) {
Ok(Position { x, y }) => {
let mut active_piece = self.playfield.active_piece.unwrap().clone();
active_piece.position = active_piece.position.offset(x, y);
active_piece.rotate_right();
self.playfield.active_piece = Some(active_piece);
self.update_lock_tick();
}
Err(_) => (),
}
2020-03-20 21:37:02 -07:00
}
fn hard_drop(&mut self) {
2020-03-21 23:47:17 -07:00
while self.playfield.can_active_piece_move_down() {
self.score += 2;
self.playfield.move_offset(0, 1);
}
if !self.try_lock_tetromino() {
println!("couldn't lock tetromino despite hard dropping!");
}
2020-03-20 21:37:02 -07:00
}
2020-03-19 15:51:43 -07:00
fn hold(&mut self) {
2020-03-22 14:33:38 -07:00
if self.can_swap_hold {
match self.hold_piece {
None => {
self.hold_piece = Some(self.active_piece);
self.get_new_piece();
}
Some(piece) => {
self.hold_piece = Some(self.active_piece);
self.active_piece = piece;
self.reset_position();
}
}
self.can_swap_hold = false;
}
2020-03-19 15:51:43 -07:00
}
}