use std::sync::Arc;
use bevy::ecs::prelude::*;
use fastrand::Rng;
pub use board::Board;
pub use minos::{Mino, Tile};
pub use piece::{Piece, Rotation, Turn};
mod board;
mod minos;
mod piece;
pub enum Generator {
Bag,
}
impl Generator {
pub fn generate(&self, queue: &mut Vec<Mino>, rng: &mut Rng) {
use Mino::*;
match self {
Self::Bag => {
let mut bag = [I, L, J, S, Z, T, O];
rng.shuffle(&mut bag);
queue.extend_from_slice(&bag);
}
}
}
}
#[derive(Component)]
pub struct Game {
pub lockout: bool,
engine: Arc<Engine>,
shape: (usize, usize),
pub board: Board<Tile>,
pub piece: Piece,
pub queue: Vec<Mino>,
rng: Rng,
}
#[derive(Resource)]
pub struct Engine {
pub shape: (usize, usize),
pub seed: u64,
pub rand: Generator,
}
impl Default for Engine {
fn default() -> Self {
let mut buf = [0; 8];
getrandom::getrandom(&mut buf).unwrap();
Self {
shape: (10, 40),
seed: u64::from_be_bytes(buf),
rand: Generator::Bag,
}
}
}
impl Game {
pub fn new(engine: Arc<Engine>) -> Self {
let shape = engine.shape;
let mut queue = Vec::new();
let mut rng = Rng::with_seed(engine.seed);
engine.rand.generate(&mut queue, &mut rng);
Game {
lockout: false,
engine,
shape,
board: Board::filled(shape, Tile::Empty),
piece: Piece::new(shape, queue.pop().unwrap()),
queue,
rng,
}
}
pub fn translate(&mut self, dx: isize, dy: isize) -> bool {
let mut collided = false;
for (x, y) in &mut self.piece.parts {
*x += dx;
*y += dy;
if !(0..self.shape.0 as isize).contains(x)
|| !(0..self.shape.1 as isize).contains(y)
|| self.board[*y as usize][*x as usize] != Tile::Empty
{
collided = true;
}
}
if collided {
for (x, y) in &mut self.piece.parts {
*x -= dx;
*y -= dy;
}
} else {
self.piece.pivot.0 += dx;
self.piece.pivot.1 += dy;
}
collided
}
pub fn hard_drop(&mut self) {
while !self.translate(0, -1) {}
self.lock_piece();
}
fn lock_piece(&mut self) {
for (x, y) in &self.piece.parts {
self.board[*y as usize][*x as usize] = Tile::Mino(self.piece.kind.clone());
}
let mut i = 0;
while i < self.shape.1 {
if self.board[i].iter().all(|t| t != &Tile::Empty) {
for j in i..self.shape.1 - 1 {
let mut next = self.board[j + 1].to_vec();
self.board[j].swap_with_slice(&mut next);
}
self.board[self.shape.1 - 1].fill(Tile::Empty);
} else {
i += 1;
}
}
let piece = self.next_piece();
for (x, y) in &piece.parts {
if self.board[*y as usize][*x as usize] != Tile::Empty {
self.lockout = true;
return;
}
}
self.piece = piece;
}
fn next_piece(&mut self) -> Piece {
while self.queue.len() < 4 {
self.engine.rand.generate(&mut self.queue, &mut self.rng);
}
Piece::new(self.shape, self.queue.pop().unwrap())
}
pub fn rotate(&mut self, turn: Turn) -> bool {
self.rotate_direct(turn);
let dir = Rotation::from(turn as i8 + self.piece.dir as i8);
for ((ax, ay), (bx, by)) in kicks(&self.piece.kind, self.piece.dir)
.iter()
.zip(kicks(&self.piece.kind, dir))
{
if !self.translate(ax - bx, ay - by) {
self.piece.dir = dir;
return false;
}
}
self.rotate_direct(match turn {
Turn::Cw => Turn::Ccw,
Turn::Ccw => Turn::Cw,
});
true
}
fn rotate_direct(&mut self, turn: Turn) {
let (px, py) = self.piece.pivot;
for (x, y) in &mut self.piece.parts {
std::mem::swap(x, y);
match turn {
Turn::Cw => {
*x = *x - py + px;
*y = px - *y + py;
}
Turn::Ccw => {
*x = py - *x + px;
*y = *y - px + py;
}
}
}
}
pub fn ghost(&self) -> isize {
let mut i = 1;
loop {
for (x, y) in &self.piece.parts {
if *y - i < 0 || self.board[(*y - i) as usize][*x as usize] != Tile::Empty {
return i - 1;
}
}
i += 1;
}
}
}
#[rustfmt::skip]
fn kicks(mino: &Mino, dir: Rotation) -> [(isize, isize); 5] {
match mino {
Mino::O => match dir {
Rotation::Spawn => [( 0, 0), ( 0, 0), ( 0, 0), ( 0, 0), ( 0, 0)],
Rotation::Right => [( 0, -1), ( 0, 0), ( 0, 0), ( 0, 0), ( 0, 0)],
Rotation::Down => [(-1, -1), ( 0, 0), ( 0, 0), ( 0, 0), ( 0, 0)],
Rotation::Left => [(-1, 0), ( 0, 0), ( 0, 0), ( 0, 0), ( 0, 0)],
},
Mino::I => match dir {
Rotation::Spawn => [( 0, 0), (-1, 0), ( 2, 0), (-1, 0), ( 2, 0)],
Rotation::Right => [(-1, 0), ( 0, 0), ( 0, 0), ( 0, 1), ( 0, -2)],
Rotation::Down => [(-1, 1), ( 1, 1), (-2, 1), ( 1, 0), (-2, 0)],
Rotation::Left => [( 0, 1), ( 0, 1), ( 0, 1), ( 0, -1), ( 0, 2)],
},
_ => match dir {
Rotation::Spawn => [( 0, 0), ( 0, 0), ( 0, 0), ( 0, 0), ( 0, 0)],
Rotation::Right => [( 0, 0), ( 1, 0), ( 1, -1), ( 0, 2), ( 1, 2)],
Rotation::Down => [( 0, 0), ( 0, 0), ( 0, 0), ( 0, 0), ( 0, 0)],
Rotation::Left => [( 0, 0), (-1, 0), (-1, -1), ( 0, 2), (-1, 2)],
},
}
}