// 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; use crate::tetromino::Position; use crate::Renderable; use crate::TICKS_PER_SECOND; use log::trace; use sdl2::{render::Canvas, video::Window}; use std::fmt; // 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 { playfield: PlayField, rotation_system: SRS, level: u8, score: u32, tick: u64, 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 { writeln!(f, "level: {}, points: {}", self.level, self.score)?; writeln!(f, "tick: {}", self.tick)?; write!(f, "{:?}", self.playfield) } } impl Default for Game { fn default() -> Self { Game { playfield: PlayField::new(), rotation_system: SRS::default(), level: 1, score: 0, tick: 0, next_gravity_tick: 60, next_lock_tick: 0, next_spawn_tick: 0, is_game_over: false, } } } 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 => { self.try_lock_tetromino(); } 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) { self.next_gravity_tick = (-1 as i64) as u64; //self.tick + TICKS_PER_SECOND as u64; } 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; } 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 } } } impl Renderable for Game { fn render(&self, canvas: &mut Canvas) -> Result<(), String> { self.playfield.render(canvas) } } pub trait Controllable { 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); } impl Controllable for Game { fn move_left(&mut self) { self.playfield.move_offset(-1, 0); } fn move_right(&mut self) { self.playfield.move_offset(1, 0); } fn move_down(&mut self) { if self.playfield.move_offset(0, 1) { self.score += 1; self.update_gravity_tick(); self.update_lock_tick(); } } fn rotate_left(&mut self) { 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(_) => (), } } fn rotate_right(&mut self) { 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(_) => (), } } fn hard_drop(&mut self) { 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!"); } } fn hold(&mut self) { // 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; // } } }