use block::Block;
use color::Color;
use coord::{Coord, Dir, Dirs, RotateDir};
use elapsed::Elapsed;
use field::Field;
use rand::{thread_rng, Rng, ThreadRng};
use std::collections::HashMap;
use tetromino::{Tetromino, Tetrominos, N_TETROS};
struct Random<R: Rng> {
rng: R,
tetros: Tetrominos,
dirs: Dirs,
}
impl<R> Random<R>
where
R: Rng,
{
pub fn new(rng: R) -> Self {
Random {
rng,
tetros: Tetromino::all(),
dirs: Dir::all(),
}
}
pub fn random_tetro(&mut self) -> Tetromino {
*self.rng.choose(&self.tetros).unwrap()
}
pub fn random_tetro_dir(&mut self) -> Dir {
*self.rng.choose(&self.dirs).unwrap()
}
pub fn random_tetro_pos(&mut self, width: usize) -> Coord {
Coord(self.rng.gen_range(0, width as i8), 0)
}
}
pub struct Play {
random: Random<ThreadRng>,
block_map: HashMap<Tetromino, Block>,
tetro: Tetromino,
next_tetro: Tetromino,
tetro_dir: Dir,
tetro_stopped: bool,
tetro_pos: Coord,
deletables: Option<Vec<usize>>,
field: Field,
elapsed: Elapsed,
score: usize,
}
impl Play {
pub fn new() -> Self {
let mut random = Random::new(thread_rng());
let next_tetro = random.random_tetro();
let mut play = Play {
random,
block_map: Play::default_block_map(),
tetro: Tetromino::I, next_tetro,
tetro_dir: Default::default(),
tetro_stopped: false,
tetro_pos: Default::default(),
deletables: None,
field: Field::new(16, 16),
elapsed: Elapsed::new(),
score: 0,
};
play.drop_tetro();
play
}
fn default_block_map() -> HashMap<Tetromino, Block> {
let bm = HashMap::with_capacity(N_TETROS);
Tetromino::all().into_iter().fold(bm, |mut bm, &t| {
bm.insert(t, t.default_block());
bm
})
}
fn drop_tetro(&mut self) {
self.tetro = self.next_tetro;
self.next_tetro = self.random.random_tetro();
self.tetro_dir = self.random.random_tetro_dir();
self.tetro_pos = self.random.random_tetro_pos(self.field.width());
let dir = if self.tetro_pos.x() < (self.field.width() as i8) / 2 {
Dir::Right
} else {
Dir::Left
};
let adjustment = dir.to_coord();
loop {
let coords = self.tetro.make_coords(self.tetro_pos, self.tetro_dir);
if self.field.is_movable(&coords) {
let block = self.block();
self.field.render_blocks(block, &coords);
break;
} else {
self.tetro_pos += adjustment;
}
}
}
fn block(&self) -> Block {
*self.block_map.get(&self.tetro).unwrap()
}
pub fn next_tetro_hint(&self) -> Block {
*self.block_map.get(&self.next_tetro).unwrap()
}
pub fn field(&self) -> &Field {
&self.field
}
pub fn elapsed(&self) -> &Elapsed {
&self.elapsed
}
pub fn score(&self) -> usize {
self.score
}
pub fn update(&mut self) -> Result<(), ()> {
if let Some(deletables) = self.deletables.take() {
for &i in deletables.iter() {
self.field.delete_line(i);
}
self.score += deletables.len();
self.deletables = None;
self.drop_tetro();
}
if self.tetro_stopped {
self.tetro_stopped = false;
return Ok(());
}
match self.move_tetro(Dir::Down) {
Ok(_) => {}
Err(_) => {
if self.field.is_reached() {
return Err(());
}
self.tetro_stopped = true;
match self.mark_deletable_lines() {
None => {
self.drop_tetro();
}
some => {
self.deletables = some;
}
};
}
};
Ok(())
}
pub fn tick(&mut self) {
self.elapsed.add_secs(1);
}
pub fn slide_tetro(&mut self, dir: Dir) {
if dir != Dir::Up {
let _ = self.move_tetro(dir);
}
}
pub fn rotate_tetro(&mut self, rotate_dir: RotateDir) {
let current_coords = self.tetro.make_coords(self.tetro_pos, self.tetro_dir);
self.field.clear_blocks(¤t_coords);
let dir = rotate_dir.rotate(self.tetro_dir);
let coords = self.tetro.make_coords(self.tetro_pos, dir);
let block = self.block();
if self.field.is_movable(&coords) {
self.tetro_dir = dir;
self.field.render_blocks(block, &coords);
} else {
self.field.render_blocks(block, ¤t_coords);
}
}
fn move_tetro(&mut self, dir: Dir) -> Result<(), ()> {
if self.tetro_stopped {
return Ok(());
}
let current_coords = self.tetro.make_coords(self.tetro_pos, self.tetro_dir);
self.field.clear_blocks(¤t_coords);
let new_pos = self.tetro_pos + dir.to_coord();
let coords = self.tetro.make_coords(new_pos, self.tetro_dir);
let block = self.block();
if self.field.is_movable(&coords) {
self.field.render_blocks(block, &coords);
self.tetro_pos = new_pos;
Ok(())
} else {
self.field.render_blocks(block, ¤t_coords);
Err(())
}
}
fn mark_deletable_lines(&mut self) -> Option<Vec<usize>> {
let targets: Vec<usize> = self
.field
.lines_iter()
.enumerate()
.filter(|(_i, line)| line.iter().all(|cell| cell.is_some()))
.map(|(i, _line)| i)
.collect();
for &y in targets.iter() {
let marked_line = (0..self.field.width())
.map(|_| Some(Block::new('X', Color::white())))
.collect();
self.field.set_line(y, marked_line);
}
Some(targets)
}
}