// 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, points: u32, pub 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.points)?; 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, points: 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 => { // 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(); } } } 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 = 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; } } impl Renderable for Game { fn render(&self, canvas: &mut Canvas) -> Result<(), String> { self.playfield.render(canvas) } } trait Controllable { fn move_left(&mut self); fn move_up(&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) {} fn move_up(&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) { // 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; // } } }