tetris/src/tetromino.rs

259 lines
7.5 KiB
Rust

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<Color> 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<Color> for TetrominoType {
fn into(self) -> Color {
match self {
Self::I => COLOR_CYAN,
Self::O => COLOR_YELLOW,
Self::T => COLOR_PURPLE,
Self::S => COLOR_GREEN,
Self::Z => COLOR_RED,
Self::J => COLOR_BLUE,
Self::L => COLOR_ORANGE,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
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: usize,
pub y: usize,
}
impl Position {
pub fn new(x: usize, y: usize) -> Position {
Self {
x: x as usize,
y: y as usize,
}
}
fn offset(&self, x: isize, y: isize) -> Position {
Self {
x: (x + self.x as isize) as usize,
y: (y + self.y as isize) as usize,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Tetromino {
pub position: Position,
pub piece_type: TetrominoType,
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_next_occupied_spaces(&self) -> Vec<Position> {
self.get_occupied_spaces(self.position.offset(0, 1))
}
pub fn get_cur_occupied_spaces(&self) -> Vec<Position> {
self.get_occupied_spaces(self.position)
}
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 / 2 - 1, PLAYFIELD_HEIGHT - 1)
} else {
Position::new(PLAYFIELD_WIDTH / 2 - 1, PLAYFIELD_HEIGHT)
}
}
fn get_occupied_spaces(&self, center: Position) -> Vec<Position> {
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::L => todo!(),
RotationState::U => todo!(),
},
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 => todo!(),
RotationState::L => todo!(),
RotationState::U => todo!(),
},
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 => todo!(),
RotationState::L => todo!(),
RotationState::U => todo!(),
},
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 => todo!(),
RotationState::L => todo!(),
RotationState::U => todo!(),
},
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 => todo!(),
RotationState::L => todo!(),
RotationState::U => todo!(),
},
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 => todo!(),
RotationState::L => todo!(),
RotationState::U => todo!(),
},
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 => todo!(),
RotationState::L => todo!(),
RotationState::U => todo!(),
},
}
spaces
}
}
impl From<TetrominoType> for Tetromino {
fn from(tetromino_type: TetrominoType) -> Self {
Self::new(tetromino_type)
}
}
impl Renderable for Tetromino {
fn render(&self, canvas: &mut Canvas<Window>) -> Result<(), String> {
for Position { x, y } in self.get_cur_occupied_spaces() {
canvas.set_draw_color(self.piece_type);
let height = y as isize - PLAYFIELD_HEIGHT as isize;
canvas.fill_rect(Rect::new(32 * x as i32 + 2, 32 * height as i32 + 2, 28, 28))?;
}
Ok(())
}
}