2020-03-21 23:47:17 -07:00
|
|
|
use crate::graphics::{BORDER_RADIUS, CELL_SIZE, COLOR_BACKGROUND};
|
2020-03-19 15:51:43 -07:00
|
|
|
use crate::random::RandomSystem;
|
2020-03-21 23:47:17 -07:00
|
|
|
use crate::tetromino::{MinoColor, Position, Tetromino, TetrominoType};
|
2020-03-20 21:05:39 -07:00
|
|
|
use crate::Renderable;
|
|
|
|
use sdl2::{pixels::Color, rect::Rect, render::Canvas, video::Window};
|
2020-03-19 15:51:43 -07:00
|
|
|
use std::collections::VecDeque;
|
2020-03-20 21:05:39 -07:00
|
|
|
use std::fmt;
|
2020-03-19 15:51:43 -07:00
|
|
|
|
2020-03-20 21:05:39 -07:00
|
|
|
pub const PLAYFIELD_HEIGHT: usize = 20;
|
|
|
|
pub const PLAYFIELD_WIDTH: usize = 10;
|
|
|
|
|
|
|
|
pub type Matrix = [[Option<MinoColor>; PLAYFIELD_WIDTH]; 2 * PLAYFIELD_HEIGHT];
|
2020-03-19 15:51:43 -07:00
|
|
|
|
|
|
|
enum Movement {
|
2020-03-20 21:05:39 -07:00
|
|
|
Rotation,
|
2020-03-19 15:51:43 -07:00
|
|
|
Gravity,
|
|
|
|
Translation,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct PlayField {
|
|
|
|
can_swap_hold: bool,
|
2020-03-20 21:05:39 -07:00
|
|
|
hold_piece: Option<TetrominoType>,
|
|
|
|
field: Matrix,
|
2020-03-21 23:47:17 -07:00
|
|
|
pub active_piece: Option<Tetromino>,
|
2020-03-19 15:51:43 -07:00
|
|
|
bag: RandomSystem,
|
2020-03-20 21:05:39 -07:00
|
|
|
next_pieces: VecDeque<TetrominoType>,
|
2020-03-19 15:51:43 -07:00
|
|
|
last_movement: Movement,
|
|
|
|
}
|
|
|
|
|
2020-03-20 21:05:39 -07:00
|
|
|
impl fmt::Debug for PlayField {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
match &self.active_piece {
|
|
|
|
Some(active_piece) => {
|
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
"current piece: {:?} @ {}, {}",
|
|
|
|
active_piece.piece_type, active_piece.position.x, active_piece.position.y
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
None => (),
|
|
|
|
}
|
|
|
|
|
|
|
|
writeln!(f, "next pieces: {:?}", self.next_pieces)?;
|
|
|
|
let occupied_spaces = self
|
|
|
|
.active_piece
|
|
|
|
.and_then(|t| Some(t.get_cur_occupied_spaces()))
|
|
|
|
.unwrap_or_default();
|
|
|
|
for y in PLAYFIELD_HEIGHT..self.field.len() {
|
|
|
|
write!(
|
|
|
|
f,
|
|
|
|
"{} │",
|
|
|
|
('a' as usize - PLAYFIELD_HEIGHT + y) as u8 as char
|
|
|
|
)?;
|
|
|
|
for x in 0..PLAYFIELD_WIDTH {
|
2020-03-21 23:47:17 -07:00
|
|
|
if occupied_spaces.contains(&Position::new(x as isize, y as isize)) {
|
2020-03-20 21:05:39 -07:00
|
|
|
write!(f, "#")?;
|
|
|
|
} else {
|
|
|
|
match self.field[y][x] {
|
|
|
|
Some(t) => write!(f, "{:?}", t)?,
|
|
|
|
None => write!(f, " ")?,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
writeln!(f, "│")?;
|
|
|
|
}
|
|
|
|
writeln!(f, " └{}┘", "─".repeat(PLAYFIELD_WIDTH))?;
|
|
|
|
write!(
|
|
|
|
f,
|
|
|
|
" {}",
|
|
|
|
(0..PLAYFIELD_WIDTH)
|
|
|
|
.map(|e| e.to_string())
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
.join("")
|
|
|
|
)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-19 15:51:43 -07:00
|
|
|
impl PlayField {
|
|
|
|
pub fn new() -> Self {
|
|
|
|
let mut bag = RandomSystem::new();
|
2020-03-21 23:47:17 -07:00
|
|
|
let active_piece = Tetromino::from(bag.get_tetromino());
|
2020-03-19 15:51:43 -07:00
|
|
|
let mut next_pieces = VecDeque::with_capacity(3);
|
2020-03-20 21:05:39 -07:00
|
|
|
for _ in 0..next_pieces.capacity() {
|
2020-03-21 23:47:17 -07:00
|
|
|
next_pieces.push_back(bag.get_tetromino());
|
2020-03-19 15:51:43 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
PlayField {
|
|
|
|
can_swap_hold: true,
|
|
|
|
hold_piece: None,
|
2020-03-20 21:05:39 -07:00
|
|
|
field: [[None; PLAYFIELD_WIDTH]; 2 * PLAYFIELD_HEIGHT],
|
|
|
|
active_piece: Some(active_piece),
|
2020-03-19 15:51:43 -07:00
|
|
|
bag,
|
|
|
|
next_pieces,
|
|
|
|
last_movement: Movement::Gravity,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-20 21:37:02 -07:00
|
|
|
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) => {
|
2020-03-21 23:47:17 -07:00
|
|
|
piece.position.x += x;
|
|
|
|
piece.position.y += y;
|
2020-03-20 21:37:02 -07:00
|
|
|
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| {
|
2020-03-21 23:47:17 -07:00
|
|
|
acc && (pos.y as usize) < self.field.len()
|
|
|
|
&& (pos.x as usize) < PLAYFIELD_WIDTH
|
2020-03-20 21:37:02 -07:00
|
|
|
&& self.field[pos.y as usize][pos.x as usize].is_none()
|
|
|
|
}),
|
|
|
|
None => false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-20 21:05:39 -07:00
|
|
|
pub fn spawn_tetromino(&mut self) {
|
|
|
|
self.active_piece = Some(Tetromino::from(
|
|
|
|
self.next_pieces
|
|
|
|
.pop_front()
|
|
|
|
.expect("visible queue to be populated"),
|
|
|
|
));
|
2020-03-21 23:47:17 -07:00
|
|
|
self.next_pieces.push_back(self.bag.get_tetromino());
|
2020-03-19 15:51:43 -07:00
|
|
|
self.can_swap_hold = true;
|
2020-03-22 15:21:48 -07:00
|
|
|
self.tick_gravity();
|
2020-03-19 15:51:43 -07:00
|
|
|
}
|
|
|
|
|
2020-03-20 21:05:39 -07:00
|
|
|
pub fn tick_gravity(&mut self) {
|
|
|
|
match &self.active_piece {
|
|
|
|
Some(mut active_piece) if self.can_active_piece_move_down() => {
|
|
|
|
active_piece.position.y += 1;
|
|
|
|
self.active_piece = Some(active_piece);
|
|
|
|
}
|
|
|
|
_ => (),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn can_active_piece_move_down(&self) -> bool {
|
|
|
|
self.active_piece
|
|
|
|
.and_then(|p| {
|
2020-03-20 21:37:02 -07:00
|
|
|
Some(
|
|
|
|
p.get_falling_occupied_spaces()
|
|
|
|
.iter()
|
|
|
|
.fold(true, |acc, pos| {
|
2020-03-21 23:47:17 -07:00
|
|
|
acc && (pos.y as usize) < self.field.len()
|
2020-03-20 21:37:02 -07:00
|
|
|
&& self.field[pos.y as usize][pos.x as usize].is_none()
|
|
|
|
}),
|
|
|
|
)
|
2020-03-20 21:05:39 -07:00
|
|
|
})
|
|
|
|
.unwrap_or_else(|| false)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn lock_active_piece(&mut self) -> Vec<Position> {
|
|
|
|
match &self.active_piece {
|
|
|
|
Some(active_piece) => {
|
|
|
|
let active_color = active_piece.get_color();
|
|
|
|
let new_pieces = active_piece.get_cur_occupied_spaces();
|
|
|
|
for Position { x, y } in &new_pieces {
|
2020-03-21 23:47:17 -07:00
|
|
|
self.field[*y as usize][*x as usize] = Some(active_color);
|
2020-03-20 21:05:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
self.active_piece = None;
|
|
|
|
new_pieces
|
|
|
|
}
|
|
|
|
None => panic!("Tried to lock active piece while active piece doesn't exist!"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_active_piece_in_valid_position(&self) -> bool {
|
|
|
|
match self.active_piece {
|
2020-03-21 23:47:17 -07:00
|
|
|
Some(active_piece) => {
|
|
|
|
self.can_piece_be_at_position(&active_piece)
|
|
|
|
},
|
2020-03-20 21:05:39 -07:00
|
|
|
None => panic!("Tried checking if active piece is in a valid position but active piece doesn't exist")
|
|
|
|
}
|
|
|
|
}
|
2020-03-21 23:47:17 -07:00
|
|
|
|
|
|
|
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()
|
|
|
|
})
|
|
|
|
}
|
2020-03-22 15:21:48 -07:00
|
|
|
|
|
|
|
pub fn try_swap_hold(&mut self) -> Result<(), ()> {
|
|
|
|
if self.can_swap_hold {
|
|
|
|
match self.active_piece {
|
|
|
|
Some(piece) => {
|
|
|
|
match self.hold_piece {
|
|
|
|
Some(hold) => self.next_pieces.push_front(hold),
|
|
|
|
None => (),
|
|
|
|
}
|
|
|
|
self.hold_piece = Some(piece.piece_type);
|
|
|
|
self.spawn_tetromino();
|
|
|
|
self.can_swap_hold = false;
|
|
|
|
}
|
|
|
|
None => return Err(()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Err(())
|
|
|
|
}
|
2020-03-20 21:05:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Renderable for PlayField {
|
|
|
|
fn render(&self, canvas: &mut Canvas<Window>) -> Result<(), String> {
|
|
|
|
for y in 0..PLAYFIELD_HEIGHT {
|
|
|
|
for x in 0..PLAYFIELD_WIDTH {
|
|
|
|
canvas.set_draw_color(Color::RGB(0, 0, 0));
|
|
|
|
canvas.fill_rect(Rect::new(
|
|
|
|
CELL_SIZE as i32 * x as i32,
|
|
|
|
CELL_SIZE as i32 * y as i32,
|
|
|
|
CELL_SIZE,
|
|
|
|
CELL_SIZE,
|
|
|
|
))?;
|
|
|
|
|
|
|
|
match self.field[y + PLAYFIELD_HEIGHT][x] {
|
|
|
|
Some(mino) => canvas.set_draw_color(mino),
|
|
|
|
None => canvas.set_draw_color(COLOR_BACKGROUND),
|
|
|
|
}
|
|
|
|
canvas.fill_rect(Rect::new(
|
2020-03-21 23:47:17 -07:00
|
|
|
CELL_SIZE as i32 * x as i32 + BORDER_RADIUS as i32,
|
|
|
|
CELL_SIZE as i32 * y as i32 + BORDER_RADIUS as i32,
|
|
|
|
CELL_SIZE - 2 * BORDER_RADIUS,
|
|
|
|
CELL_SIZE - 2 * BORDER_RADIUS,
|
2020-03-20 21:05:39 -07:00
|
|
|
))?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
match self.active_piece {
|
|
|
|
Some(piece) => piece.render(canvas)?,
|
|
|
|
None => (),
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
2020-03-19 15:51:43 -07:00
|
|
|
}
|
|
|
|
}
|