tetris/src/playfield.rs

239 lines
7.2 KiB
Rust
Raw Permalink Normal View History

2020-03-19 15:51:43 -07:00
use crate::random::RandomSystem;
2020-03-30 09:28:53 -07:00
use crate::tetromino::{Position, Tetromino, TetrominoType};
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;
2020-03-30 09:28:53 -07:00
pub type Matrix = Vec<Vec<Option<TetrominoType>>>;
2020-03-19 15:51:43 -07:00
2020-03-30 09:28:53 -07:00
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
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,
}
2020-03-30 09:28:53 -07:00
#[derive(Clone)]
2020-03-19 15:51:43 -07:00
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
}
2020-03-30 09:28:53 -07:00
let row = [None; PLAYFIELD_WIDTH].to_vec();
let mut field = Vec::with_capacity(2 * PLAYFIELD_HEIGHT);
for _ in 0..2 * PLAYFIELD_HEIGHT {
field.push(row.clone());
}
2020-03-19 15:51:43 -07:00
PlayField {
can_swap_hold: true,
hold_piece: None,
2020-03-30 09:28:53 -07:00
field,
2020-03-20 21:05:39 -07:00
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
2020-03-28 17:57:39 -07:00
.expect("Tried to request the status of a non-existing active piece!")
.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()
2020-03-20 21:05:39 -07:00
})
}
pub fn lock_active_piece(&mut self) -> Vec<Position> {
match &self.active_piece {
Some(active_piece) => {
let new_pieces = active_piece.get_cur_occupied_spaces();
for Position { x, y } in &new_pieces {
2020-03-30 09:28:53 -07:00
self.field[*y as usize][*x as usize] = Some(active_piece.piece_type);
2020-03-20 21:05:39 -07:00
}
new_pieces
}
2020-03-28 17:57:39 -07:00
None => panic!("Tried to lock a piece that wasn't active"),
2020-03-20 21:05:39 -07:00
}
}
2020-03-28 17:57:39 -07:00
pub fn is_active_piece_in_valid_position(&self) -> Option<Position> {
self.active_piece
.and_then(|t| self.can_piece_be_at_position(&t))
2020-03-20 21:05:39 -07:00
}
2020-03-21 23:47:17 -07:00
2020-03-28 17:57:39 -07:00
pub fn can_piece_be_at_position(&self, tetromino: &Tetromino) -> Option<Position> {
for Position { x, y } in tetromino.get_cur_occupied_spaces() {
if (y as usize) >= self.field.len()
|| (x as usize) >= PLAYFIELD_WIDTH
|| self.field[y as usize][x as usize].is_some()
{
return Some(Position { x, y });
}
}
None
2020-03-21 23:47:17 -07:00
}
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-22 18:56:11 -07:00
2020-03-30 09:28:53 -07:00
pub fn can_swap(&self) -> bool {
self.can_swap_hold
}
2020-03-22 18:56:11 -07:00
pub fn try_clear_row(&mut self, row: usize) -> Result<(), ()> {
if self.field[row].iter().all(|cell| cell.is_some()) {
2020-03-30 09:28:53 -07:00
self.field[row] = [None; PLAYFIELD_WIDTH].to_vec();
2020-03-22 18:56:11 -07:00
for y in (1..=row).rev() {
2020-03-30 09:28:53 -07:00
self.field[y] = self.field[y - 1].clone();
2020-03-22 18:56:11 -07:00
}
Ok(())
} else {
Err(())
}
}
2020-03-20 21:05:39 -07:00
2020-03-30 09:28:53 -07:00
pub fn field(&self) -> &Matrix {
&self.field
}
2020-03-20 21:05:39 -07:00
2020-03-30 09:28:53 -07:00
pub fn hold_piece(&self) -> Option<&TetrominoType> {
self.hold_piece.as_ref()
2020-03-19 15:51:43 -07:00
}
}