use crate::random::RandomSystem; use crate::tetromino::{Position, Tetromino, TetrominoType}; use std::collections::VecDeque; use std::fmt; pub const PLAYFIELD_HEIGHT: usize = 20; pub const PLAYFIELD_WIDTH: usize = 10; pub type Matrix = Vec>>; #[derive(Clone, Copy, PartialEq, Eq, Debug)] enum Movement { Rotation, Gravity, Translation, } #[derive(Clone)] pub struct PlayField { can_swap_hold: bool, hold_piece: Option, field: Matrix, pub active_piece: Option, bag: RandomSystem, next_pieces: VecDeque, last_movement: Movement, } impl fmt::Debug for PlayField { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.active_piece { Some(active_piece) => { writeln!( f, "current piece: {:?} @ {}, {}", active_piece.piece_type, active_piece.position.x, active_piece.position.y )?; } None => (), } writeln!(f, "next pieces: {:?}", self.next_pieces)?; let occupied_spaces = self .active_piece .and_then(|t| Some(t.get_cur_occupied_spaces())) .unwrap_or_default(); for y in PLAYFIELD_HEIGHT..self.field.len() { write!( f, "{} │", ('a' as usize - PLAYFIELD_HEIGHT + y) as u8 as char )?; for x in 0..PLAYFIELD_WIDTH { if occupied_spaces.contains(&Position::new(x as isize, y as isize)) { write!(f, "#")?; } else { match self.field[y][x] { Some(t) => write!(f, "{:?}", t)?, None => write!(f, " ")?, } } } writeln!(f, "│")?; } writeln!(f, " └{}┘", "─".repeat(PLAYFIELD_WIDTH))?; write!( f, " {}", (0..PLAYFIELD_WIDTH) .map(|e| e.to_string()) .collect::>() .join("") )?; Ok(()) } } impl PlayField { pub fn new() -> Self { let mut bag = RandomSystem::new(); let active_piece = Tetromino::from(bag.get_tetromino()); let mut next_pieces = VecDeque::with_capacity(3); for _ in 0..next_pieces.capacity() { next_pieces.push_back(bag.get_tetromino()); } let row = [None; PLAYFIELD_WIDTH].to_vec(); let mut field = Vec::with_capacity(2 * PLAYFIELD_HEIGHT); for _ in 0..2 * PLAYFIELD_HEIGHT { field.push(row.clone()); } PlayField { can_swap_hold: true, hold_piece: None, field, active_piece: Some(active_piece), bag, next_pieces, last_movement: Movement::Gravity, } } pub fn move_offset(&mut self, x: isize, y: isize) -> bool { if self.can_move_offset(x, y) { match self.active_piece { Some(mut piece) => { piece.position.x += x; piece.position.y += y; self.active_piece = Some(piece); true } None => panic!("Active piece missing!"), } } else { false } } fn can_move_offset(&self, x: isize, y: isize) -> bool { match self.active_piece { Some(piece) => piece .get_occupied_spaces(piece.position.offset(x, y)) .iter() .fold(true, |acc, pos| { acc && (pos.y as usize) < self.field.len() && (pos.x as usize) < PLAYFIELD_WIDTH && self.field[pos.y as usize][pos.x as usize].is_none() }), None => false, } } pub fn spawn_tetromino(&mut self) { self.active_piece = Some(Tetromino::from( self.next_pieces .pop_front() .expect("visible queue to be populated"), )); self.next_pieces.push_back(self.bag.get_tetromino()); self.can_swap_hold = true; self.tick_gravity(); } pub fn tick_gravity(&mut self) { match &self.active_piece { Some(mut active_piece) if self.can_active_piece_move_down() => { active_piece.position.y += 1; self.active_piece = Some(active_piece); } _ => (), } } pub fn can_active_piece_move_down(&self) -> bool { self.active_piece .expect("Tried to request the status of a non-existing active piece!") .get_falling_occupied_spaces() .iter() .fold(true, |acc, pos| { acc && (pos.y as usize) < self.field.len() && self.field[pos.y as usize][pos.x as usize].is_none() }) } pub fn lock_active_piece(&mut self) -> Vec { match &self.active_piece { Some(active_piece) => { let new_pieces = active_piece.get_cur_occupied_spaces(); for Position { x, y } in &new_pieces { self.field[*y as usize][*x as usize] = Some(active_piece.piece_type); } new_pieces } None => panic!("Tried to lock a piece that wasn't active"), } } pub fn is_active_piece_in_valid_position(&self) -> Option { self.active_piece .and_then(|t| self.can_piece_be_at_position(&t)) } pub fn can_piece_be_at_position(&self, tetromino: &Tetromino) -> Option { for Position { x, y } in tetromino.get_cur_occupied_spaces() { if (y as usize) >= self.field.len() || (x as usize) >= PLAYFIELD_WIDTH || self.field[y as usize][x as usize].is_some() { return Some(Position { x, y }); } } None } pub fn try_swap_hold(&mut self) -> Result<(), ()> { if self.can_swap_hold { match self.active_piece { Some(piece) => { match self.hold_piece { Some(hold) => self.next_pieces.push_front(hold), None => (), } self.hold_piece = Some(piece.piece_type); self.spawn_tetromino(); self.can_swap_hold = false; } None => return Err(()), } } Err(()) } pub fn can_swap(&self) -> bool { self.can_swap_hold } pub fn try_clear_row(&mut self, row: usize) -> Result<(), ()> { if self.field[row].iter().all(|cell| cell.is_some()) { self.field[row] = [None; PLAYFIELD_WIDTH].to_vec(); for y in (1..=row).rev() { self.field[y] = self.field[y - 1].clone(); } Ok(()) } else { Err(()) } } pub fn field(&self) -> &Matrix { &self.field } pub fn hold_piece(&self) -> Option<&TetrominoType> { self.hold_piece.as_ref() } }