Compare commits
No commits in common. "765b2655a3c4133d24a88e41a2283ad39eb2e494" and "ab74480bfd6be0701295c1e9940daa2b22a81572" have entirely different histories.
765b2655a3
...
ab74480bfd
8 changed files with 207 additions and 505 deletions
9
.vscode/settings.json
vendored
9
.vscode/settings.json
vendored
|
@ -1,9 +0,0 @@
|
||||||
{
|
|
||||||
"cSpell.words": [
|
|
||||||
"Mino",
|
|
||||||
"PLAYFIELD",
|
|
||||||
"Renderable",
|
|
||||||
"Tickable",
|
|
||||||
"tetromino"
|
|
||||||
]
|
|
||||||
}
|
|
98
src/game.rs
98
src/game.rs
|
@ -19,8 +19,8 @@ pub struct Game {
|
||||||
playfield: PlayField,
|
playfield: PlayField,
|
||||||
rotation_system: SRS,
|
rotation_system: SRS,
|
||||||
level: u8,
|
level: u8,
|
||||||
score: u32,
|
points: u32,
|
||||||
tick: u64,
|
pub 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.score)?;
|
writeln!(f, "level: {}, points: {}", self.level, self.points)?;
|
||||||
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,
|
||||||
score: 0,
|
points: 0,
|
||||||
tick: 0,
|
tick: 0,
|
||||||
next_gravity_tick: 60,
|
next_gravity_tick: 60,
|
||||||
next_lock_tick: 0,
|
next_lock_tick: 0,
|
||||||
|
@ -65,7 +65,17 @@ 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 => {
|
||||||
self.try_lock_tetromino();
|
// 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
t if t == self.next_gravity_tick => {
|
t if t == self.next_gravity_tick => {
|
||||||
self.playfield.tick_gravity();
|
self.playfield.tick_gravity();
|
||||||
|
@ -85,7 +95,7 @@ impl Game {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_gravity_tick(&mut self) {
|
fn update_gravity_tick(&mut self) {
|
||||||
self.next_gravity_tick = (-1 as i64) as u64; //self.tick + TICKS_PER_SECOND as u64;
|
self.next_gravity_tick = self.tick + TICKS_PER_SECOND as u64;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_lock_tick(&mut self) {
|
fn update_lock_tick(&mut self) {
|
||||||
|
@ -103,23 +113,6 @@ 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 {
|
||||||
|
@ -128,8 +121,9 @@ impl Renderable for Game {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Controllable {
|
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);
|
||||||
|
@ -139,55 +133,13 @@ pub trait Controllable {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Controllable for Game {
|
impl Controllable for Game {
|
||||||
fn move_left(&mut self) {
|
fn move_left(&mut self) {}
|
||||||
self.playfield.move_offset(-1, 0);
|
fn move_up(&mut self) {}
|
||||||
self.update_lock_tick();
|
fn move_right(&mut self) {}
|
||||||
}
|
fn move_down(&mut self) {}
|
||||||
fn move_right(&mut self) {
|
fn rotate_left(&mut self) {}
|
||||||
self.playfield.move_offset(1, 0);
|
fn rotate_right(&mut self) {}
|
||||||
self.update_lock_tick();
|
fn hard_drop(&mut self) {}
|
||||||
}
|
|
||||||
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 {
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
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);
|
||||||
|
|
40
src/main.rs
40
src/main.rs
|
@ -1,4 +1,4 @@
|
||||||
use game::{Controllable, Game, Tickable};
|
use game::{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(1000 / TICKS_PER_SECOND as u64));
|
let mut interval = interval(Duration::from_millis(60 / 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,40 +40,6 @@ 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(),
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,6 +51,8 @@ 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(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::graphics::{BORDER_RADIUS, CELL_SIZE, COLOR_BACKGROUND};
|
use crate::graphics::{CELL_SIZE, COLOR_BACKGROUND};
|
||||||
use crate::random::RandomSystem;
|
use crate::random::RandomSystem;
|
||||||
use crate::tetromino::{MinoColor, Position, Tetromino, TetrominoType};
|
use crate::tetromino::Position;
|
||||||
|
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;
|
||||||
|
@ -21,7 +22,7 @@ pub struct PlayField {
|
||||||
can_swap_hold: bool,
|
can_swap_hold: bool,
|
||||||
hold_piece: Option<TetrominoType>,
|
hold_piece: Option<TetrominoType>,
|
||||||
field: Matrix,
|
field: Matrix,
|
||||||
pub active_piece: Option<Tetromino>,
|
active_piece: Option<Tetromino>,
|
||||||
bag: RandomSystem,
|
bag: RandomSystem,
|
||||||
next_pieces: VecDeque<TetrominoType>,
|
next_pieces: VecDeque<TetrominoType>,
|
||||||
last_movement: Movement,
|
last_movement: Movement,
|
||||||
|
@ -52,7 +53,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 as isize, y as isize)) {
|
if occupied_spaces.contains(&Position::new(x, y)) {
|
||||||
write!(f, "#")?;
|
write!(f, "#")?;
|
||||||
} else {
|
} else {
|
||||||
match self.field[y][x] {
|
match self.field[y][x] {
|
||||||
|
@ -79,10 +80,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_tetromino());
|
let active_piece = Tetromino::from(bag.get_tetrino());
|
||||||
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_tetromino());
|
next_pieces.push_back(bag.get_tetrino());
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayField {
|
PlayField {
|
||||||
|
@ -96,43 +97,13 @@ 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_tetromino());
|
self.next_pieces.push_back(self.bag.get_tetrino());
|
||||||
self.can_swap_hold = true;
|
self.can_swap_hold = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,14 +120,10 @@ 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(
|
Some(p.get_next_occupied_spaces().iter().fold(true, |acc, pos| {
|
||||||
p.get_falling_occupied_spaces()
|
acc && pos.y < self.field.len()
|
||||||
.iter()
|
&& self.field[pos.y as usize][pos.x as usize].is_none()
|
||||||
.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)
|
.unwrap_or_else(|| false)
|
||||||
}
|
}
|
||||||
|
@ -167,7 +134,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 as usize][*x as usize] = Some(active_color);
|
self.field[*y][*x] = Some(active_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.active_piece = None;
|
self.active_piece = None;
|
||||||
|
@ -179,23 +146,10 @@ 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) => {
|
Some(active_piece) => active_piece.get_cur_occupied_spaces().iter().all(|Position {x, y}| self.field[*y][*x].is_none()),
|
||||||
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 {
|
||||||
|
@ -215,10 +169,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 + BORDER_RADIUS as i32,
|
CELL_SIZE as i32 * x as i32 + 2,
|
||||||
CELL_SIZE as i32 * y as i32 + BORDER_RADIUS as i32,
|
CELL_SIZE as i32 * y as i32 + 2,
|
||||||
CELL_SIZE - 2 * BORDER_RADIUS,
|
28,
|
||||||
CELL_SIZE - 2 * BORDER_RADIUS,
|
28,
|
||||||
))?;
|
))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ impl RandomSystem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_tetromino(&mut self) -> TetrominoType {
|
pub fn get_tetrino(&mut self) -> TetrominoType {
|
||||||
if self.cur_pos == 0 {
|
if self.cur_pos == 0 {
|
||||||
self.refresh_bag();
|
self.refresh_bag();
|
||||||
}
|
}
|
||||||
|
|
247
src/srs.rs
247
src/srs.rs
|
@ -1,158 +1,123 @@
|
||||||
use crate::playfield::{Matrix, PlayField};
|
use crate::playfield::PlayField;
|
||||||
use crate::tetromino::{Position, RotationState, Tetromino, TetrominoType};
|
use crate::tetromino::{Position, 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 rotate_left(&self, playfield: &PlayField) -> Result<Position, ()>;
|
fn get_rotation_offset(
|
||||||
|
piece: &TetrominoType,
|
||||||
fn rotate_right(&self, playfield: &PlayField) -> Result<Position, ()>;
|
center: &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 {
|
||||||
fn rotate_common(
|
pub fn new() -> Self {
|
||||||
&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 {
|
||||||
let mut jlstz_offset_data = HashMap::with_capacity(4);
|
SRS::new()
|
||||||
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 rotate_left(&self, playfield: &PlayField) -> Result<Position, ()> {
|
fn get_rotation_offset(
|
||||||
self.rotate_common(playfield, RotationDirection::Left)
|
piece: &TetrominoType,
|
||||||
}
|
center: &Position,
|
||||||
|
direction: RotationDirection,
|
||||||
fn rotate_right(&self, playfield: &PlayField) -> Result<Position, ()> {
|
playfield: &PlayField,
|
||||||
self.rotate_common(playfield, RotationDirection::Right)
|
) -> Option<Offset> {
|
||||||
|
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 }],
|
||||||
|
// };
|
||||||
|
|
232
src/tetromino.rs
232
src/tetromino.rs
|
@ -63,22 +63,22 @@ pub enum TetrominoType {
|
||||||
L,
|
L,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<MinoColor> for TetrominoType {
|
impl Into<Color> for TetrominoType {
|
||||||
fn into(self) -> MinoColor {
|
fn into(self) -> Color {
|
||||||
match self {
|
match self {
|
||||||
Self::I => MinoColor::Cyan,
|
Self::I => COLOR_CYAN,
|
||||||
Self::O => MinoColor::Yellow,
|
Self::O => COLOR_YELLOW,
|
||||||
Self::T => MinoColor::Purple,
|
Self::T => COLOR_PURPLE,
|
||||||
Self::S => MinoColor::Green,
|
Self::S => COLOR_GREEN,
|
||||||
Self::Z => MinoColor::Red,
|
Self::Z => COLOR_RED,
|
||||||
Self::J => MinoColor::Blue,
|
Self::J => COLOR_BLUE,
|
||||||
Self::L => MinoColor::Orange,
|
Self::L => COLOR_ORANGE,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
pub enum RotationState {
|
enum RotationState {
|
||||||
O, // initial state
|
O, // initial state
|
||||||
R, // clockwise rotation
|
R, // clockwise rotation
|
||||||
L, // counter-clockwise rotation
|
L, // counter-clockwise rotation
|
||||||
|
@ -93,42 +93,22 @@ impl Default for RotationState {
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct Position {
|
pub struct Position {
|
||||||
pub x: isize,
|
pub x: usize,
|
||||||
pub y: isize,
|
pub y: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Position {
|
impl Position {
|
||||||
pub fn new(x: isize, y: isize) -> Position {
|
pub fn new(x: usize, y: usize) -> Position {
|
||||||
Self {
|
Self {
|
||||||
x: x as isize,
|
x: x as usize,
|
||||||
y: y as isize,
|
y: y as usize,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn offset(&self, x: isize, y: isize) -> Position {
|
fn offset(&self, x: isize, y: isize) -> Position {
|
||||||
Self {
|
Self {
|
||||||
x: x + self.x,
|
x: (x + self.x as isize) as usize,
|
||||||
y: y + self.y,
|
y: (y + self.y as isize) as usize,
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -137,7 +117,7 @@ impl std::ops::Sub for Position {
|
||||||
pub struct Tetromino {
|
pub struct Tetromino {
|
||||||
pub position: Position,
|
pub position: Position,
|
||||||
pub piece_type: TetrominoType,
|
pub piece_type: TetrominoType,
|
||||||
pub rotation_state: RotationState,
|
rotation_state: RotationState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tetromino {
|
impl Tetromino {
|
||||||
|
@ -149,6 +129,14 @@ 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,
|
||||||
|
@ -163,24 +151,13 @@ 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(
|
Position::new(PLAYFIELD_WIDTH / 2 - 1, PLAYFIELD_HEIGHT - 1)
|
||||||
PLAYFIELD_WIDTH as isize / 2 - 1,
|
|
||||||
PLAYFIELD_HEIGHT as isize - 1,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
Position::new(PLAYFIELD_WIDTH as isize / 2 - 1, PLAYFIELD_HEIGHT as isize)
|
Position::new(PLAYFIELD_WIDTH / 2 - 1, PLAYFIELD_HEIGHT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_falling_occupied_spaces(&self) -> Vec<Position> {
|
fn get_occupied_spaces(&self, center: Position) -> 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 {
|
||||||
|
@ -194,16 +171,8 @@ impl Tetromino {
|
||||||
center.offset(0, 1),
|
center.offset(0, 1),
|
||||||
center.offset(0, 2),
|
center.offset(0, 2),
|
||||||
]),
|
]),
|
||||||
RotationState::U => spaces.extend_from_slice(&[
|
RotationState::L => todo!(),
|
||||||
center.offset(-2, 0),
|
RotationState::U => todo!(),
|
||||||
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(&[
|
||||||
|
@ -211,21 +180,9 @@ impl Tetromino {
|
||||||
center.offset(-1, 0),
|
center.offset(-1, 0),
|
||||||
center.offset(1, 0),
|
center.offset(1, 0),
|
||||||
]),
|
]),
|
||||||
RotationState::R => spaces.extend_from_slice(&[
|
RotationState::R => todo!(),
|
||||||
center.offset(0, -1),
|
RotationState::L => todo!(),
|
||||||
center.offset(1, -1),
|
RotationState::U => todo!(),
|
||||||
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(&[
|
||||||
|
@ -233,21 +190,9 @@ impl Tetromino {
|
||||||
center.offset(-1, 0),
|
center.offset(-1, 0),
|
||||||
center.offset(1, 0),
|
center.offset(1, 0),
|
||||||
]),
|
]),
|
||||||
RotationState::R => spaces.extend_from_slice(&[
|
RotationState::R => todo!(),
|
||||||
center.offset(0, -1),
|
RotationState::L => todo!(),
|
||||||
center.offset(0, 1),
|
RotationState::U => todo!(),
|
||||||
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(&[
|
||||||
|
@ -255,21 +200,9 @@ impl Tetromino {
|
||||||
center.offset(1, -1),
|
center.offset(1, -1),
|
||||||
center.offset(1, 0),
|
center.offset(1, 0),
|
||||||
]),
|
]),
|
||||||
RotationState::R => spaces.extend_from_slice(&[
|
RotationState::R => todo!(),
|
||||||
center.offset(1, 0),
|
RotationState::L => todo!(),
|
||||||
center.offset(0, 1),
|
RotationState::U => todo!(),
|
||||||
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(&[
|
||||||
|
@ -277,21 +210,9 @@ impl Tetromino {
|
||||||
center.offset(1, -1),
|
center.offset(1, -1),
|
||||||
center.offset(-1, 0),
|
center.offset(-1, 0),
|
||||||
]),
|
]),
|
||||||
RotationState::R => spaces.extend_from_slice(&[
|
RotationState::R => todo!(),
|
||||||
center.offset(0, -1),
|
RotationState::L => todo!(),
|
||||||
center.offset(1, 0),
|
RotationState::U => todo!(),
|
||||||
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(&[
|
||||||
|
@ -299,21 +220,9 @@ impl Tetromino {
|
||||||
center.offset(-1, 0),
|
center.offset(-1, 0),
|
||||||
center.offset(1, 0),
|
center.offset(1, 0),
|
||||||
]),
|
]),
|
||||||
RotationState::R => spaces.extend_from_slice(&[
|
RotationState::R => todo!(),
|
||||||
center.offset(0, -1),
|
RotationState::L => todo!(),
|
||||||
center.offset(0, 1),
|
RotationState::U => todo!(),
|
||||||
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(&[
|
||||||
|
@ -321,44 +230,14 @@ impl Tetromino {
|
||||||
center.offset(0, -1),
|
center.offset(0, -1),
|
||||||
center.offset(1, 0),
|
center.offset(1, 0),
|
||||||
]),
|
]),
|
||||||
RotationState::R => spaces.extend_from_slice(&[
|
RotationState::R => todo!(),
|
||||||
center.offset(1, -1),
|
RotationState::L => todo!(),
|
||||||
center.offset(1, 0),
|
RotationState::U => todo!(),
|
||||||
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 {
|
||||||
|
@ -370,14 +249,9 @@ 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::<MinoColor>(self.piece_type.into());
|
canvas.set_draw_color(self.piece_type);
|
||||||
let height = y as isize - PLAYFIELD_HEIGHT as isize;
|
let height = y as isize - PLAYFIELD_HEIGHT as isize;
|
||||||
canvas.fill_rect(Rect::new(
|
canvas.fill_rect(Rect::new(32 * x as i32 + 2, 32 * height as i32 + 2, 28, 28))?;
|
||||||
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(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue