use crate::{ graphics::*, playfield::{PLAYFIELD_HEIGHT, PLAYFIELD_WIDTH}, Renderable, }; use sdl2::{pixels::Color, rect::Rect, render::Canvas, video::Window}; use std::fmt; #[derive(Copy, Clone)] pub enum MinoColor { Cyan, Yellow, Purple, Green, Red, Blue, Orange, Gray, } impl fmt::Debug for MinoColor { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{}", match self { Self::Cyan => "c", Self::Yellow => "y", Self::Purple => "p", Self::Green => "g", Self::Red => "r", Self::Blue => "b", Self::Orange => "o", Self::Gray => "x", } ) } } impl Into for MinoColor { fn into(self) -> Color { match self { Self::Cyan => COLOR_CYAN, Self::Yellow => COLOR_YELLOW, Self::Purple => COLOR_PURPLE, Self::Green => COLOR_GREEN, Self::Red => COLOR_RED, Self::Blue => COLOR_BLUE, Self::Orange => COLOR_ORANGE, Self::Gray => COLOR_GRAY, } } } #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum TetrominoType { I, O, T, S, Z, J, L, } impl Into for TetrominoType { fn into(self) -> MinoColor { match self { Self::I => MinoColor::Cyan, Self::O => MinoColor::Yellow, Self::T => MinoColor::Purple, Self::S => MinoColor::Green, Self::Z => MinoColor::Red, Self::J => MinoColor::Blue, Self::L => MinoColor::Orange, } } } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum RotationState { O, // initial state R, // clockwise rotation L, // counter-clockwise rotation U, // 180 deg rotation } impl Default for RotationState { fn default() -> Self { RotationState::O } } #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Position { pub x: isize, pub y: isize, } impl Position { pub fn new(x: isize, y: isize) -> Position { Self { x: x as isize, y: y as isize, } } pub fn offset(&self, x: isize, y: isize) -> Position { Self { x: x + self.x, y: y + self.y, } } } impl std::ops::Add for Position { type Output = Self; fn add(self, other: Self) -> Self { Self { x: self.x + other.x, y: self.y + other.y, } } } impl std::ops::Sub for Position { type Output = Self; fn sub(self, other: Self) -> Self { Self { x: self.x - other.x, y: self.y - other.y, } } } #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Tetromino { pub position: Position, pub piece_type: TetrominoType, pub rotation_state: RotationState, } impl Tetromino { pub fn new(tetromino_type: TetrominoType) -> Self { Self { position: Self::get_start_position(tetromino_type), piece_type: tetromino_type, rotation_state: RotationState::default(), } } pub fn get_color(&self) -> MinoColor { match self.piece_type { TetrominoType::I => MinoColor::Cyan, TetrominoType::O => MinoColor::Yellow, TetrominoType::T => MinoColor::Purple, TetrominoType::S => MinoColor::Green, TetrominoType::Z => MinoColor::Red, TetrominoType::J => MinoColor::Blue, TetrominoType::L => MinoColor::Orange, } } fn get_start_position(tetromino_type: TetrominoType) -> Position { if tetromino_type == TetrominoType::I { Position::new( PLAYFIELD_WIDTH as isize / 2 - 1, PLAYFIELD_HEIGHT as isize - 1, ) } else { Position::new(PLAYFIELD_WIDTH as isize / 2 - 1, PLAYFIELD_HEIGHT as isize) } } pub fn get_falling_occupied_spaces(&self) -> Vec { self.get_occupied_spaces(self.position.offset(0, 1)) } pub fn get_cur_occupied_spaces(&self) -> Vec { self.get_occupied_spaces(self.position) } pub fn get_occupied_spaces(&self, center: Position) -> Vec { let mut spaces = vec![center]; match self.piece_type { TetrominoType::I => match self.rotation_state { RotationState::O => spaces.extend_from_slice(&[ center.offset(-1, 0), center.offset(1, 0), center.offset(2, 0), ]), RotationState::R => spaces.extend_from_slice(&[ center.offset(0, -1), center.offset(0, 1), center.offset(0, 2), ]), RotationState::U => spaces.extend_from_slice(&[ center.offset(-2, 0), center.offset(-1, 0), center.offset(1, 0), ]), RotationState::L => spaces.extend_from_slice(&[ center.offset(0, -2), center.offset(0, -1), center.offset(0, 1), ]), }, TetrominoType::J => match self.rotation_state { RotationState::O => spaces.extend_from_slice(&[ center.offset(-1, -1), center.offset(-1, 0), center.offset(1, 0), ]), RotationState::R => spaces.extend_from_slice(&[ center.offset(0, -1), center.offset(1, -1), center.offset(0, 1), ]), RotationState::U => spaces.extend_from_slice(&[ center.offset(-1, 0), center.offset(1, 0), center.offset(1, 1), ]), RotationState::L => spaces.extend_from_slice(&[ center.offset(0, -1), center.offset(-1, 1), center.offset(0, 1), ]), }, TetrominoType::L => match self.rotation_state { RotationState::O => spaces.extend_from_slice(&[ center.offset(1, -1), center.offset(-1, 0), center.offset(1, 0), ]), RotationState::R => spaces.extend_from_slice(&[ center.offset(0, -1), center.offset(0, 1), center.offset(1, 1), ]), RotationState::U => spaces.extend_from_slice(&[ center.offset(-1, 0), center.offset(1, 0), center.offset(-1, 1), ]), RotationState::L => spaces.extend_from_slice(&[ center.offset(-1, -1), center.offset(0, -1), center.offset(0, 1), ]), }, TetrominoType::O => match self.rotation_state { RotationState::O => spaces.extend_from_slice(&[ center.offset(0, -1), center.offset(1, -1), center.offset(1, 0), ]), RotationState::R => spaces.extend_from_slice(&[ center.offset(1, 0), center.offset(0, 1), center.offset(1, 1), ]), RotationState::U => spaces.extend_from_slice(&[ center.offset(-1, 0), center.offset(-1, 1), center.offset(0, 1), ]), RotationState::L => spaces.extend_from_slice(&[ center.offset(-1, -1), center.offset(0, -1), center.offset(-1, 0), ]), }, TetrominoType::S => match self.rotation_state { RotationState::O => spaces.extend_from_slice(&[ center.offset(0, -1), center.offset(1, -1), center.offset(-1, 0), ]), RotationState::R => spaces.extend_from_slice(&[ center.offset(0, -1), center.offset(1, 0), center.offset(1, 1), ]), RotationState::U => spaces.extend_from_slice(&[ center.offset(0, 1), center.offset(1, 0), center.offset(-1, 1), ]), RotationState::L => spaces.extend_from_slice(&[ center.offset(-1, -1), center.offset(-1, 0), center.offset(0, 1), ]), }, TetrominoType::T => match self.rotation_state { RotationState::O => spaces.extend_from_slice(&[ center.offset(0, -1), center.offset(-1, 0), center.offset(1, 0), ]), RotationState::R => spaces.extend_from_slice(&[ center.offset(0, -1), center.offset(0, 1), center.offset(1, 0), ]), RotationState::U => spaces.extend_from_slice(&[ center.offset(0, 1), center.offset(-1, 0), center.offset(1, 0), ]), RotationState::L => spaces.extend_from_slice(&[ center.offset(0, -1), center.offset(-1, 0), center.offset(0, 1), ]), }, TetrominoType::Z => match self.rotation_state { RotationState::O => spaces.extend_from_slice(&[ center.offset(-1, -1), center.offset(0, -1), center.offset(1, 0), ]), RotationState::R => spaces.extend_from_slice(&[ center.offset(1, -1), center.offset(1, 0), center.offset(0, 1), ]), RotationState::U => spaces.extend_from_slice(&[ center.offset(-1, 0), center.offset(0, 1), center.offset(1, 1), ]), RotationState::L => spaces.extend_from_slice(&[ center.offset(0, -1), center.offset(-1, 0), center.offset(-1, 1), ]), }, } spaces } pub fn rotate_left(&mut self) { self.rotation_state = match self.rotation_state { RotationState::O => RotationState::L, RotationState::L => RotationState::U, RotationState::U => RotationState::R, RotationState::R => RotationState::O, } } pub fn rotate_right(&mut self) { self.rotation_state = match self.rotation_state { RotationState::O => RotationState::R, RotationState::R => RotationState::U, RotationState::U => RotationState::L, RotationState::L => RotationState::O, } } } impl From for Tetromino { fn from(tetromino_type: TetrominoType) -> Self { Self::new(tetromino_type) } } impl Renderable for Tetromino { fn render(&self, canvas: &mut Canvas) -> Result<(), String> { for Position { x, y } in self.get_cur_occupied_spaces() { canvas.set_draw_color::(self.piece_type.into()); let height = y as isize - PLAYFIELD_HEIGHT as isize; canvas.fill_rect(Rect::new( CELL_SIZE as i32 * x as i32 + BORDER_RADIUS as i32, CELL_SIZE as i32 * height as i32 + BORDER_RADIUS as i32, CELL_SIZE - 2 * BORDER_RADIUS, CELL_SIZE - 2 * BORDER_RADIUS, ))?; } Ok(()) } }