tetris/src/tetromino.rs

385 lines
12 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<MinoColor> 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<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_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::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<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::<MinoColor>(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(())
}
}