use crate::graphics::{BORDER_RADIUS, CELL_SIZE, COLOR_BACKGROUND}; use crate::random::RandomSystem; use crate::tetromino::{MinoColor, Position, Tetromino, TetrominoType}; use crate::Renderable; use sdl2::{pixels::Color, rect::Rect, render::Canvas, video::Window}; use std::collections::VecDeque; use std::fmt; pub const PLAYFIELD_HEIGHT: usize = 20; pub const PLAYFIELD_WIDTH: usize = 10; pub type Matrix = [[Option; PLAYFIELD_WIDTH]; 2 * PLAYFIELD_HEIGHT]; enum Movement { Rotation, Gravity, Translation, } 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()); } PlayField { can_swap_hold: true, hold_piece: None, field: [[None; PLAYFIELD_WIDTH]; 2 * PLAYFIELD_HEIGHT], 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; } 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 .and_then(|p| { Some( p.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() }), ) }) .unwrap_or_else(|| false) } pub fn lock_active_piece(&mut self) -> Vec { match &self.active_piece { Some(active_piece) => { let active_color = active_piece.get_color(); 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_color); } self.active_piece = None; new_pieces } None => panic!("Tried to lock active piece while active piece doesn't exist!"), } } pub fn is_active_piece_in_valid_position(&self) -> bool { match self.active_piece { Some(active_piece) => { self.can_piece_be_at_position(&active_piece) }, None => panic!("Tried checking if active piece is in a valid position but active piece doesn't exist") } } pub fn can_piece_be_at_position(&self, tetromino: &Tetromino) -> bool { tetromino .get_cur_occupied_spaces() .iter() .all(|Position { x, y }| { (*y as usize) < self.field.len() && (*x as usize) < PLAYFIELD_WIDTH && self.field[*y as usize][*x as usize].is_none() }) } } impl Renderable for PlayField { fn render(&self, canvas: &mut Canvas) -> Result<(), String> { for y in 0..PLAYFIELD_HEIGHT { for x in 0..PLAYFIELD_WIDTH { canvas.set_draw_color(Color::RGB(0, 0, 0)); canvas.fill_rect(Rect::new( CELL_SIZE as i32 * x as i32, CELL_SIZE as i32 * y as i32, CELL_SIZE, CELL_SIZE, ))?; match self.field[y + PLAYFIELD_HEIGHT][x] { Some(mino) => canvas.set_draw_color(mino), None => canvas.set_draw_color(COLOR_BACKGROUND), } canvas.fill_rect(Rect::new( CELL_SIZE as i32 * x as i32 + BORDER_RADIUS as i32, CELL_SIZE as i32 * y as i32 + BORDER_RADIUS as i32, CELL_SIZE - 2 * BORDER_RADIUS, CELL_SIZE - 2 * BORDER_RADIUS, ))?; } } match self.active_piece { Some(piece) => piece.render(canvas)?, None => (), } Ok(()) } }