Compare commits

...

3 commits

Author SHA1 Message Date
765b2655a3
add lock reset for infinity 2020-03-22 02:50:08 -04:00
7a71a41784
rotation system implemented 2020-03-22 02:47:17 -04:00
9c4b208b8b
movement 2020-03-21 00:37:02 -04:00
8 changed files with 505 additions and 207 deletions

9
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,9 @@
{
"cSpell.words": [
"Mino",
"PLAYFIELD",
"Renderable",
"Tickable",
"tetromino"
]
}

View file

@ -19,8 +19,8 @@ pub struct Game {
playfield: PlayField, playfield: PlayField,
rotation_system: SRS, rotation_system: SRS,
level: u8, level: u8,
points: u32, score: u32,
pub tick: u64, tick: u64,
next_gravity_tick: u64, next_gravity_tick: u64,
next_lock_tick: u64, next_lock_tick: u64,
next_spawn_tick: u64, next_spawn_tick: u64,
@ -29,7 +29,7 @@ pub struct Game {
impl fmt::Debug for Game { impl fmt::Debug for Game {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "level: {}, points: {}", self.level, self.points)?; writeln!(f, "level: {}, points: {}", self.level, self.score)?;
writeln!(f, "tick: {}", self.tick)?; writeln!(f, "tick: {}", self.tick)?;
write!(f, "{:?}", self.playfield) write!(f, "{:?}", self.playfield)
} }
@ -41,7 +41,7 @@ impl Default for Game {
playfield: PlayField::new(), playfield: PlayField::new(),
rotation_system: SRS::default(), rotation_system: SRS::default(),
level: 1, level: 1,
points: 0, score: 0,
tick: 0, tick: 0,
next_gravity_tick: 60, next_gravity_tick: 60,
next_lock_tick: 0, next_lock_tick: 0,
@ -65,17 +65,7 @@ impl Tickable for Game {
match self.tick { match self.tick {
t if t == self.next_spawn_tick => self.spawn_tetromino(), t if t == self.next_spawn_tick => self.spawn_tetromino(),
t if t == self.next_lock_tick => { t if t == self.next_lock_tick => {
// It's possible that the player moved the piece in the meantime. self.try_lock_tetromino();
if !self.playfield.can_active_piece_move_down() {
let positions = self.playfield.lock_active_piece();
self.is_game_over =
self.is_game_over || positions.iter().all(|Position { x: _, y }| *y < 20);
if self.clear_lines() {
self.next_spawn_tick = self.tick + LINE_CLEAR_DELAY;
} else {
self.spawn_tetromino();
}
}
} }
t if t == self.next_gravity_tick => { t if t == self.next_gravity_tick => {
self.playfield.tick_gravity(); self.playfield.tick_gravity();
@ -95,7 +85,7 @@ impl Game {
} }
fn update_gravity_tick(&mut self) { fn update_gravity_tick(&mut self) {
self.next_gravity_tick = self.tick + TICKS_PER_SECOND as u64; self.next_gravity_tick = (-1 as i64) as u64; //self.tick + TICKS_PER_SECOND as u64;
} }
fn update_lock_tick(&mut self) { fn update_lock_tick(&mut self) {
@ -113,6 +103,23 @@ impl Game {
// todo: award points based on how lines were cleared // todo: award points based on how lines were cleared
return false; return false;
} }
fn try_lock_tetromino(&mut self) -> bool {
// It's possible that the player moved the piece in the meantime.
if !self.playfield.can_active_piece_move_down() {
let positions = self.playfield.lock_active_piece();
self.is_game_over =
self.is_game_over || positions.iter().all(|Position { x: _, y }| *y < 20);
if self.clear_lines() {
self.next_spawn_tick = self.tick + LINE_CLEAR_DELAY;
} else {
self.spawn_tetromino();
}
true
} else {
false
}
}
} }
impl Renderable for Game { impl Renderable for Game {
@ -121,9 +128,8 @@ impl Renderable for Game {
} }
} }
trait Controllable { pub trait Controllable {
fn move_left(&mut self); fn move_left(&mut self);
fn move_up(&mut self);
fn move_right(&mut self); fn move_right(&mut self);
fn move_down(&mut self); fn move_down(&mut self);
fn rotate_left(&mut self); fn rotate_left(&mut self);
@ -133,13 +139,55 @@ trait Controllable {
} }
impl Controllable for Game { impl Controllable for Game {
fn move_left(&mut self) {} fn move_left(&mut self) {
fn move_up(&mut self) {} self.playfield.move_offset(-1, 0);
fn move_right(&mut self) {} self.update_lock_tick();
fn move_down(&mut self) {} }
fn rotate_left(&mut self) {} fn move_right(&mut self) {
fn rotate_right(&mut self) {} self.playfield.move_offset(1, 0);
fn hard_drop(&mut self) {} self.update_lock_tick();
}
fn move_down(&mut self) {
if self.playfield.move_offset(0, 1) {
self.score += 1;
self.update_gravity_tick();
self.update_lock_tick();
}
}
fn rotate_left(&mut self) {
match self.rotation_system.rotate_left(&self.playfield) {
Ok(Position { x, y }) => {
let mut active_piece = self.playfield.active_piece.unwrap().clone();
active_piece.position = active_piece.position.offset(x, y);
active_piece.rotate_left();
self.playfield.active_piece = Some(active_piece);
self.update_lock_tick();
}
Err(_) => (),
}
}
fn rotate_right(&mut self) {
match self.rotation_system.rotate_right(&self.playfield) {
Ok(Position { x, y }) => {
let mut active_piece = self.playfield.active_piece.unwrap().clone();
active_piece.position = active_piece.position.offset(x, y);
active_piece.rotate_right();
self.playfield.active_piece = Some(active_piece);
self.update_lock_tick();
}
Err(_) => (),
}
}
fn hard_drop(&mut self) {
while self.playfield.can_active_piece_move_down() {
self.score += 2;
self.playfield.move_offset(0, 1);
}
if !self.try_lock_tetromino() {
println!("couldn't lock tetromino despite hard dropping!");
}
}
fn hold(&mut self) { fn hold(&mut self) {
// if self.can_swap_hold { // if self.can_swap_hold {

View file

@ -1,6 +1,8 @@
use sdl2::pixels::Color; use sdl2::pixels::Color;
pub const CELL_SIZE: u32 = 32; pub const CELL_SIZE: u32 = 32;
pub const BORDER_RADIUS: u32 = 1;
pub static COLOR_BACKGROUND: Color = Color::RGB(60, 60, 60); pub static COLOR_BACKGROUND: Color = Color::RGB(60, 60, 60);
pub static COLOR_CYAN: Color = Color::RGB(0, 255, 255); pub static COLOR_CYAN: Color = Color::RGB(0, 255, 255);
pub static COLOR_YELLOW: Color = Color::RGB(255, 255, 0); pub static COLOR_YELLOW: Color = Color::RGB(255, 255, 0);

View file

@ -1,4 +1,4 @@
use game::{Game, Tickable}; use game::{Controllable, Game, Tickable};
use graphics::COLOR_BACKGROUND; use graphics::COLOR_BACKGROUND;
use sdl2::event::Event; use sdl2::event::Event;
use sdl2::keyboard::Keycode; use sdl2::keyboard::Keycode;
@ -30,7 +30,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut canvas = window.into_canvas().build()?; let mut canvas = window.into_canvas().build()?;
let mut event_pump = sdl_context.event_pump()?; let mut event_pump = sdl_context.event_pump()?;
let mut game = Game::default(); let mut game = Game::default();
let mut interval = interval(Duration::from_millis(60 / TICKS_PER_SECOND as u64)); let mut interval = interval(Duration::from_millis(1000 / TICKS_PER_SECOND as u64));
'running: while !game.is_game_over() { 'running: while !game.is_game_over() {
for event in event_pump.poll_iter() { for event in event_pump.poll_iter() {
@ -40,6 +40,40 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
keycode: Some(Keycode::Escape), keycode: Some(Keycode::Escape),
.. ..
} => break 'running, } => break 'running,
Event::KeyDown {
keycode: Some(Keycode::Left),
..
} => {
game.move_left();
}
Event::KeyDown {
keycode: Some(Keycode::Right),
..
} => game.move_right(),
Event::KeyDown {
keycode: Some(Keycode::Down),
..
} => game.move_down(),
Event::KeyDown {
keycode: Some(Keycode::Z),
..
} => game.rotate_left(),
Event::KeyDown {
keycode: Some(Keycode::X),
..
} => game.rotate_right(),
Event::KeyDown {
keycode: Some(Keycode::Space),
..
}
| Event::KeyDown {
keycode: Some(Keycode::Up),
..
} => game.hard_drop(),
Event::KeyDown {
keycode: Some(Keycode::LShift),
..
} => game.hold(),
_ => {} _ => {}
} }
} }
@ -51,8 +85,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
interval.tick().await; interval.tick().await;
} }
println!("Game over! Final game state:");
dbg!(game); dbg!(game);
Ok(()) Ok(())
} }

View file

@ -1,7 +1,6 @@
use crate::graphics::{CELL_SIZE, COLOR_BACKGROUND}; use crate::graphics::{BORDER_RADIUS, CELL_SIZE, COLOR_BACKGROUND};
use crate::random::RandomSystem; use crate::random::RandomSystem;
use crate::tetromino::Position; use crate::tetromino::{MinoColor, Position, Tetromino, TetrominoType};
use crate::tetromino::{MinoColor, Tetromino, TetrominoType};
use crate::Renderable; use crate::Renderable;
use sdl2::{pixels::Color, rect::Rect, render::Canvas, video::Window}; use sdl2::{pixels::Color, rect::Rect, render::Canvas, video::Window};
use std::collections::VecDeque; use std::collections::VecDeque;
@ -22,7 +21,7 @@ pub struct PlayField {
can_swap_hold: bool, can_swap_hold: bool,
hold_piece: Option<TetrominoType>, hold_piece: Option<TetrominoType>,
field: Matrix, field: Matrix,
active_piece: Option<Tetromino>, pub active_piece: Option<Tetromino>,
bag: RandomSystem, bag: RandomSystem,
next_pieces: VecDeque<TetrominoType>, next_pieces: VecDeque<TetrominoType>,
last_movement: Movement, last_movement: Movement,
@ -53,7 +52,7 @@ impl fmt::Debug for PlayField {
('a' as usize - PLAYFIELD_HEIGHT + y) as u8 as char ('a' as usize - PLAYFIELD_HEIGHT + y) as u8 as char
)?; )?;
for x in 0..PLAYFIELD_WIDTH { for x in 0..PLAYFIELD_WIDTH {
if occupied_spaces.contains(&Position::new(x, y)) { if occupied_spaces.contains(&Position::new(x as isize, y as isize)) {
write!(f, "#")?; write!(f, "#")?;
} else { } else {
match self.field[y][x] { match self.field[y][x] {
@ -80,10 +79,10 @@ impl fmt::Debug for PlayField {
impl PlayField { impl PlayField {
pub fn new() -> Self { pub fn new() -> Self {
let mut bag = RandomSystem::new(); let mut bag = RandomSystem::new();
let active_piece = Tetromino::from(bag.get_tetrino()); let active_piece = Tetromino::from(bag.get_tetromino());
let mut next_pieces = VecDeque::with_capacity(3); let mut next_pieces = VecDeque::with_capacity(3);
for _ in 0..next_pieces.capacity() { for _ in 0..next_pieces.capacity() {
next_pieces.push_back(bag.get_tetrino()); next_pieces.push_back(bag.get_tetromino());
} }
PlayField { PlayField {
@ -97,13 +96,43 @@ impl PlayField {
} }
} }
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) { pub fn spawn_tetromino(&mut self) {
self.active_piece = Some(Tetromino::from( self.active_piece = Some(Tetromino::from(
self.next_pieces self.next_pieces
.pop_front() .pop_front()
.expect("visible queue to be populated"), .expect("visible queue to be populated"),
)); ));
self.next_pieces.push_back(self.bag.get_tetrino()); self.next_pieces.push_back(self.bag.get_tetromino());
self.can_swap_hold = true; self.can_swap_hold = true;
} }
@ -120,10 +149,14 @@ impl PlayField {
pub fn can_active_piece_move_down(&self) -> bool { pub fn can_active_piece_move_down(&self) -> bool {
self.active_piece self.active_piece
.and_then(|p| { .and_then(|p| {
Some(p.get_next_occupied_spaces().iter().fold(true, |acc, pos| { Some(
acc && pos.y < self.field.len() 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() && self.field[pos.y as usize][pos.x as usize].is_none()
})) }),
)
}) })
.unwrap_or_else(|| false) .unwrap_or_else(|| false)
} }
@ -134,7 +167,7 @@ impl PlayField {
let active_color = active_piece.get_color(); let active_color = active_piece.get_color();
let new_pieces = active_piece.get_cur_occupied_spaces(); let new_pieces = active_piece.get_cur_occupied_spaces();
for Position { x, y } in &new_pieces { for Position { x, y } in &new_pieces {
self.field[*y][*x] = Some(active_color); self.field[*y as usize][*x as usize] = Some(active_color);
} }
self.active_piece = None; self.active_piece = None;
@ -146,10 +179,23 @@ impl PlayField {
pub fn is_active_piece_in_valid_position(&self) -> bool { pub fn is_active_piece_in_valid_position(&self) -> bool {
match self.active_piece { match self.active_piece {
Some(active_piece) => active_piece.get_cur_occupied_spaces().iter().all(|Position {x, y}| self.field[*y][*x].is_none()), 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") 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 { impl Renderable for PlayField {
@ -169,10 +215,10 @@ impl Renderable for PlayField {
None => canvas.set_draw_color(COLOR_BACKGROUND), None => canvas.set_draw_color(COLOR_BACKGROUND),
} }
canvas.fill_rect(Rect::new( canvas.fill_rect(Rect::new(
CELL_SIZE as i32 * x as i32 + 2, CELL_SIZE as i32 * x as i32 + BORDER_RADIUS as i32,
CELL_SIZE as i32 * y as i32 + 2, CELL_SIZE as i32 * y as i32 + BORDER_RADIUS as i32,
28, CELL_SIZE - 2 * BORDER_RADIUS,
28, CELL_SIZE - 2 * BORDER_RADIUS,
))?; ))?;
} }
} }

View file

@ -17,7 +17,7 @@ impl RandomSystem {
} }
} }
pub fn get_tetrino(&mut self) -> TetrominoType { pub fn get_tetromino(&mut self) -> TetrominoType {
if self.cur_pos == 0 { if self.cur_pos == 0 {
self.refresh_bag(); self.refresh_bag();
} }

View file

@ -1,123 +1,158 @@
use crate::playfield::PlayField; use crate::playfield::{Matrix, PlayField};
use crate::tetromino::{Position, TetrominoType}; use crate::tetromino::{Position, RotationState, Tetromino, TetrominoType};
use std::collections::HashMap;
#[derive(Copy, Clone)]
pub struct Offset {
x: i8,
y: i8,
}
pub enum RotationDirection {
Clockwise,
AntiClockwise,
}
pub trait RotationSystem { pub trait RotationSystem {
fn default() -> Self; fn default() -> Self;
fn get_rotation_offset( fn rotate_left(&self, playfield: &PlayField) -> Result<Position, ()>;
piece: &TetrominoType,
center: &Position, fn rotate_right(&self, playfield: &PlayField) -> Result<Position, ()>;
direction: RotationDirection,
playfield: &PlayField,
) -> Option<Offset>;
} }
pub struct SRS {} pub struct SRS {
jlstz_offset_data: HashMap<RotationState, Vec<Position>>,
i_offset_data: HashMap<RotationState, Vec<Position>>,
o_offset_data: HashMap<RotationState, Vec<Position>>,
}
enum RotationDirection {
Left,
Right,
}
impl SRS { impl SRS {
pub fn new() -> Self { fn rotate_common(
Self {} &self,
playfield: &PlayField,
rotation: RotationDirection,
) -> Result<Position, ()> {
let active_piece = match playfield.active_piece {
Some(piece) => piece,
None => return Err(()),
};
let offset_data = match active_piece.piece_type {
TetrominoType::I => &self.i_offset_data,
TetrominoType::O => &self.o_offset_data,
_ => &self.jlstz_offset_data,
};
let prev_offsets = offset_data.get(&active_piece.rotation_state).unwrap();
let mut test_tetromino = active_piece.clone();
match rotation {
RotationDirection::Left => test_tetromino.rotate_left(),
RotationDirection::Right => test_tetromino.rotate_right(),
}
let cur_offsets = offset_data.get(&test_tetromino.rotation_state).unwrap();
let mut offsets = Vec::with_capacity(cur_offsets.len());
for i in 0..cur_offsets.len() {
offsets.push(prev_offsets[i] - cur_offsets[i]);
}
for offset in offsets {
let x = offset.x;
let y = offset.y;
let test_position = active_piece.position.offset(x, y);
test_tetromino.position = test_position;
if playfield.can_piece_be_at_position(&test_tetromino) {
return Ok(offset);
}
}
Err(())
} }
} }
impl RotationSystem for SRS { impl RotationSystem for SRS {
fn default() -> Self { fn default() -> Self {
SRS::new() let mut jlstz_offset_data = HashMap::with_capacity(4);
let mut i_offset_data = HashMap::with_capacity(4);
let mut o_offset_data = HashMap::with_capacity(4);
jlstz_offset_data.insert(RotationState::O, vec![Position { x: 0, y: 0 }].repeat(5));
jlstz_offset_data.insert(
RotationState::R,
vec![
Position { x: 0, y: 0 },
Position { x: 1, y: 0 },
Position { x: 1, y: 1 },
Position { x: 0, y: -2 },
Position { x: 1, y: -2 },
],
);
jlstz_offset_data.insert(RotationState::U, vec![Position { x: 0, y: 0 }].repeat(5));
jlstz_offset_data.insert(
RotationState::L,
vec![
Position { x: 0, y: 0 },
Position { x: -1, y: 0 },
Position { x: -1, y: 1 },
Position { x: 0, y: -2 },
Position { x: -1, y: -2 },
],
);
i_offset_data.insert(
RotationState::O,
vec![
Position { x: 0, y: 0 },
Position { x: -1, y: 0 },
Position { x: 2, y: 0 },
Position { x: -1, y: 0 },
Position { x: 2, y: 0 },
],
);
i_offset_data.insert(
RotationState::R,
vec![
Position { x: -1, y: 0 },
Position { x: 0, y: 0 },
Position { x: 0, y: 0 },
Position { x: 0, y: -1 },
Position { x: 0, y: 2 },
],
);
i_offset_data.insert(
RotationState::U,
vec![
Position { x: -1, y: -1 },
Position { x: 1, y: -1 },
Position { x: -2, y: -1 },
Position { x: 1, y: 0 },
Position { x: -2, y: 0 },
],
);
i_offset_data.insert(
RotationState::L,
vec![
Position { x: 0, y: -1 },
Position { x: 0, y: -1 },
Position { x: 0, y: -1 },
Position { x: 0, y: 1 },
Position { x: 0, y: -2 },
],
);
o_offset_data.insert(RotationState::O, vec![Position { x: 0, y: 0 }]);
o_offset_data.insert(RotationState::R, vec![Position { x: 0, y: 1 }]);
o_offset_data.insert(RotationState::U, vec![Position { x: -1, y: 1 }]);
o_offset_data.insert(RotationState::L, vec![Position { x: -1, y: 0 }]);
SRS {
jlstz_offset_data,
i_offset_data,
o_offset_data,
}
} }
fn get_rotation_offset( fn rotate_left(&self, playfield: &PlayField) -> Result<Position, ()> {
piece: &TetrominoType, self.rotate_common(playfield, RotationDirection::Left)
center: &Position, }
direction: RotationDirection,
playfield: &PlayField, fn rotate_right(&self, playfield: &PlayField) -> Result<Position, ()> {
) -> Option<Offset> { self.rotate_common(playfield, RotationDirection::Right)
None
} }
} }
#[derive(PartialEq, Eq, Hash)]
enum Rotation {
O, // Spawn state
R, // Right rotation: clockwise rotation from spawn state
U, // Upside-down rotation: rotation after 2 left or right rotations from spawn state
L, // Left rotation: counterclockwise rotation from spawn state
}
struct OffsetData {
// O: &[Offset],
// R: &[Offset],
// U: &[Offset],
// L: &[Offset],
}
impl OffsetData {
pub fn apply_right_rotation() {}
}
// static JLSTZOffsetData: OffsetData = OffsetData {
// O: vec![Offset { x: 0, y: 0 }],
// R: vec![
// Offset { x: 0, y: 0 },
// Offset { x: 1, y: 0 },
// Offset { x: 1, y: -1 },
// Offset { x: 0, y: 2 },
// Offset { x: 1, y: 2 },
// ],
// U: vec![Offset { x: 0, y: 0 }],
// L: vec![
// Offset { x: 0, y: 0 },
// Offset { x: -1, y: 0 },
// Offset { x: -1, y: -1 },
// Offset { x: 0, y: 2 },
// Offset { x: -1, y: 2 },
// ],
// };
// static IOffsetData: OffsetData = OffsetData {
// O: vec![
// Offset { x: 0, y: 0 },
// Offset { x: -1, y: 0 },
// Offset { x: 2, y: 0 },
// Offset { x: -1, y: 0 },
// Offset { x: 2, y: 0 },
// ],
// R: vec![
// Offset { x: -1, y: 0 },
// Offset { x: 0, y: 0 },
// Offset { x: 0, y: 0 },
// Offset { x: 0, y: 1 },
// Offset { x: 0, y: -2 },
// ],
// U: vec![
// Offset { x: -1, y: 1 },
// Offset { x: 1, y: 1 },
// Offset { x: -2, y: 1 },
// Offset { x: 1, y: 0 },
// Offset { x: -2, y: 0 },
// ],
// L: vec![
// Offset { x: 0, y: 1 },
// Offset { x: 0, y: 1 },
// Offset { x: 0, y: 1 },
// Offset { x: 0, y: -1 },
// Offset { x: 0, y: 2 },
// ],
// };
// static OOffsetData: OffsetData = OffsetData {
// O: vec![Offset { x: 0, y: 0 }],
// R: vec![Offset { x: 0, y: -1 }],
// U: vec![Offset { x: -1, y: -1 }],
// L: vec![Offset { x: -1, y: 0 }],
// };

View file

@ -63,22 +63,22 @@ pub enum TetrominoType {
L, L,
} }
impl Into<Color> for TetrominoType { impl Into<MinoColor> for TetrominoType {
fn into(self) -> Color { fn into(self) -> MinoColor {
match self { match self {
Self::I => COLOR_CYAN, Self::I => MinoColor::Cyan,
Self::O => COLOR_YELLOW, Self::O => MinoColor::Yellow,
Self::T => COLOR_PURPLE, Self::T => MinoColor::Purple,
Self::S => COLOR_GREEN, Self::S => MinoColor::Green,
Self::Z => COLOR_RED, Self::Z => MinoColor::Red,
Self::J => COLOR_BLUE, Self::J => MinoColor::Blue,
Self::L => COLOR_ORANGE, Self::L => MinoColor::Orange,
} }
} }
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
enum RotationState { pub enum RotationState {
O, // initial state O, // initial state
R, // clockwise rotation R, // clockwise rotation
L, // counter-clockwise rotation L, // counter-clockwise rotation
@ -93,22 +93,42 @@ impl Default for RotationState {
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Position { pub struct Position {
pub x: usize, pub x: isize,
pub y: usize, pub y: isize,
} }
impl Position { impl Position {
pub fn new(x: usize, y: usize) -> Position { pub fn new(x: isize, y: isize) -> Position {
Self { Self {
x: x as usize, x: x as isize,
y: y as usize, y: y as isize,
} }
} }
fn offset(&self, x: isize, y: isize) -> Position { pub fn offset(&self, x: isize, y: isize) -> Position {
Self { Self {
x: (x + self.x as isize) as usize, x: x + self.x,
y: (y + self.y as isize) as usize, 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,
} }
} }
} }
@ -117,7 +137,7 @@ impl Position {
pub struct Tetromino { pub struct Tetromino {
pub position: Position, pub position: Position,
pub piece_type: TetrominoType, pub piece_type: TetrominoType,
rotation_state: RotationState, pub rotation_state: RotationState,
} }
impl Tetromino { impl Tetromino {
@ -129,14 +149,6 @@ impl Tetromino {
} }
} }
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 { pub fn get_color(&self) -> MinoColor {
match self.piece_type { match self.piece_type {
TetrominoType::I => MinoColor::Cyan, TetrominoType::I => MinoColor::Cyan,
@ -151,13 +163,24 @@ impl Tetromino {
fn get_start_position(tetromino_type: TetrominoType) -> Position { fn get_start_position(tetromino_type: TetrominoType) -> Position {
if tetromino_type == TetrominoType::I { if tetromino_type == TetrominoType::I {
Position::new(PLAYFIELD_WIDTH / 2 - 1, PLAYFIELD_HEIGHT - 1) Position::new(
PLAYFIELD_WIDTH as isize / 2 - 1,
PLAYFIELD_HEIGHT as isize - 1,
)
} else { } else {
Position::new(PLAYFIELD_WIDTH / 2 - 1, PLAYFIELD_HEIGHT) Position::new(PLAYFIELD_WIDTH as isize / 2 - 1, PLAYFIELD_HEIGHT as isize)
} }
} }
fn get_occupied_spaces(&self, center: Position) -> Vec<Position> { 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]; let mut spaces = vec![center];
match self.piece_type { match self.piece_type {
TetrominoType::I => match self.rotation_state { TetrominoType::I => match self.rotation_state {
@ -171,8 +194,16 @@ impl Tetromino {
center.offset(0, 1), center.offset(0, 1),
center.offset(0, 2), center.offset(0, 2),
]), ]),
RotationState::L => todo!(), RotationState::U => spaces.extend_from_slice(&[
RotationState::U => todo!(), 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 { TetrominoType::J => match self.rotation_state {
RotationState::O => spaces.extend_from_slice(&[ RotationState::O => spaces.extend_from_slice(&[
@ -180,9 +211,21 @@ impl Tetromino {
center.offset(-1, 0), center.offset(-1, 0),
center.offset(1, 0), center.offset(1, 0),
]), ]),
RotationState::R => todo!(), RotationState::R => spaces.extend_from_slice(&[
RotationState::L => todo!(), center.offset(0, -1),
RotationState::U => todo!(), 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 { TetrominoType::L => match self.rotation_state {
RotationState::O => spaces.extend_from_slice(&[ RotationState::O => spaces.extend_from_slice(&[
@ -190,9 +233,21 @@ impl Tetromino {
center.offset(-1, 0), center.offset(-1, 0),
center.offset(1, 0), center.offset(1, 0),
]), ]),
RotationState::R => todo!(), RotationState::R => spaces.extend_from_slice(&[
RotationState::L => todo!(), center.offset(0, -1),
RotationState::U => todo!(), 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 { TetrominoType::O => match self.rotation_state {
RotationState::O => spaces.extend_from_slice(&[ RotationState::O => spaces.extend_from_slice(&[
@ -200,9 +255,21 @@ impl Tetromino {
center.offset(1, -1), center.offset(1, -1),
center.offset(1, 0), center.offset(1, 0),
]), ]),
RotationState::R => todo!(), RotationState::R => spaces.extend_from_slice(&[
RotationState::L => todo!(), center.offset(1, 0),
RotationState::U => todo!(), 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 { TetrominoType::S => match self.rotation_state {
RotationState::O => spaces.extend_from_slice(&[ RotationState::O => spaces.extend_from_slice(&[
@ -210,9 +277,21 @@ impl Tetromino {
center.offset(1, -1), center.offset(1, -1),
center.offset(-1, 0), center.offset(-1, 0),
]), ]),
RotationState::R => todo!(), RotationState::R => spaces.extend_from_slice(&[
RotationState::L => todo!(), center.offset(0, -1),
RotationState::U => todo!(), 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 { TetrominoType::T => match self.rotation_state {
RotationState::O => spaces.extend_from_slice(&[ RotationState::O => spaces.extend_from_slice(&[
@ -220,9 +299,21 @@ impl Tetromino {
center.offset(-1, 0), center.offset(-1, 0),
center.offset(1, 0), center.offset(1, 0),
]), ]),
RotationState::R => todo!(), RotationState::R => spaces.extend_from_slice(&[
RotationState::L => todo!(), center.offset(0, -1),
RotationState::U => todo!(), 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 { TetrominoType::Z => match self.rotation_state {
RotationState::O => spaces.extend_from_slice(&[ RotationState::O => spaces.extend_from_slice(&[
@ -230,14 +321,44 @@ impl Tetromino {
center.offset(0, -1), center.offset(0, -1),
center.offset(1, 0), center.offset(1, 0),
]), ]),
RotationState::R => todo!(), RotationState::R => spaces.extend_from_slice(&[
RotationState::L => todo!(), center.offset(1, -1),
RotationState::U => todo!(), 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 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 { impl From<TetrominoType> for Tetromino {
@ -249,9 +370,14 @@ impl From<TetrominoType> for Tetromino {
impl Renderable for Tetromino { impl Renderable for Tetromino {
fn render(&self, canvas: &mut Canvas<Window>) -> Result<(), String> { fn render(&self, canvas: &mut Canvas<Window>) -> Result<(), String> {
for Position { x, y } in self.get_cur_occupied_spaces() { for Position { x, y } in self.get_cur_occupied_spaces() {
canvas.set_draw_color(self.piece_type); canvas.set_draw_color::<MinoColor>(self.piece_type.into());
let height = y as isize - PLAYFIELD_HEIGHT as isize; 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))?; 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(()) Ok(())
} }