levels and other stuff
This commit is contained in:
209
src/main.rs
209
src/main.rs
@@ -6,7 +6,7 @@ use crossterm::{
|
||||
};
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{io, io::Write};
|
||||
use tetromino::{Rotation, WIDTH};
|
||||
use tetromino::{BlockGrid, Rotation};
|
||||
|
||||
const RED_BLOCK: &str = "\x1b[48;2;255;0;0m \x1b[0m";
|
||||
const GREEN_BLOCK: &str = "\x1b[48;2;0;255;0m \x1b[0m";
|
||||
@@ -17,58 +17,43 @@ const PURPLE_BLOCK: &str = "\x1b[48;2;128;0;128m \x1b[0m";
|
||||
const CYAN_BLOCK: &str = "\x1b[48;2;0;255;255m \x1b[0m";
|
||||
|
||||
mod tetromino;
|
||||
use tetromino::{Block, Tetromino, TetrominoKind};
|
||||
use tetromino::{Block, Tetromino, TetrominoKind, HEIGHT, WIDTH};
|
||||
|
||||
fn main() {
|
||||
let mut rng = rand::thread_rng();
|
||||
terminal::enable_raw_mode().unwrap();
|
||||
let frame_time = Duration::from_millis(200);
|
||||
let mut level = 0;
|
||||
print!("\x1b[?25l");
|
||||
let mut tetromino = Tetromino::new(TetrominoKind::new(&mut rng));
|
||||
let mut blocks = vec![];
|
||||
let mut block_grid = BlockGrid::new();
|
||||
let mut prev_frame_dur = Instant::now().elapsed();
|
||||
let mut score = Score::new();
|
||||
loop {
|
||||
let frame_dur = Instant::now();
|
||||
if !tetromino.move_tetromino((0, -1), &blocks) {
|
||||
spawn_blocks(&tetromino, &mut blocks);
|
||||
tetromino = Tetromino::new(TetrominoKind::new(&mut rng));
|
||||
}
|
||||
render(&tetromino, &blocks, &prev_frame_dur);
|
||||
let frame_time = Duration::from_secs_f64(
|
||||
(0.8f64 - ((level as f64 - 1.) * 0.007)).powf(level as f64 - 1.),
|
||||
);
|
||||
render(&tetromino, &block_grid, &score, &prev_frame_dur, level);
|
||||
while frame_time.checked_sub(frame_dur.elapsed()).is_some() {
|
||||
if let Some(input) = input(&frame_dur, frame_time) {
|
||||
handle_action(&input, &mut tetromino, &blocks);
|
||||
render(&tetromino, &blocks, &prev_frame_dur);
|
||||
handle_action(&input, &mut tetromino, &block_grid);
|
||||
render(&tetromino, &block_grid, &score, &prev_frame_dur, level);
|
||||
}
|
||||
}
|
||||
render(&tetromino, &blocks, &prev_frame_dur);
|
||||
if !tetromino.move_tetromino((0, -1), &block_grid) {
|
||||
if tetromino.pos.1 == 17 {
|
||||
exit();
|
||||
}
|
||||
spawn_blocks(&tetromino, &mut block_grid);
|
||||
tetromino = Tetromino::new(TetrominoKind::new(&mut rng));
|
||||
}
|
||||
clear_lines(&mut block_grid, &mut score);
|
||||
render(&tetromino, &block_grid, &score, &prev_frame_dur, level);
|
||||
prev_frame_dur = frame_dur.elapsed();
|
||||
level = score.lines / 10;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// fn clear_lines(blocks: &mut Vec<Block>) {
|
||||
// let mut levels = [0; 10];
|
||||
// for block in blocks.iter() {
|
||||
// }
|
||||
// let mut to_remove = vec![];
|
||||
// for (y, level) in levels.iter().enumerate() {
|
||||
// if *level == WIDTH {
|
||||
// let mut to_remove = None;
|
||||
// for (i, block) in blocks.iter().enumerate() {
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
fn spawn_blocks(tetromino: &Tetromino, blocks: &mut Vec<Block>) {
|
||||
tetromino.b.iter().for_each(|m| {
|
||||
blocks.push(Block {
|
||||
pos: (tetromino.pos.0 + m.0, tetromino.pos.1 + m.1),
|
||||
kind: tetromino.kind.clone(),
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
enum Action {
|
||||
MoveRight,
|
||||
MoveLeft,
|
||||
@@ -77,17 +62,75 @@ enum Action {
|
||||
HardDrop,
|
||||
}
|
||||
|
||||
fn handle_action(action: &Action, tetromino: &mut Tetromino, blocks: &Vec<Block>) {
|
||||
struct Score {
|
||||
points: u32,
|
||||
lines: u32,
|
||||
}
|
||||
|
||||
impl Score {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
points: 0,
|
||||
lines: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_lines(block_grid: &mut BlockGrid, score: &mut Score) {
|
||||
let mut to_clear = vec![];
|
||||
for (i, line) in block_grid.0.iter_mut().enumerate().rev() {
|
||||
let count = line.iter().flatten().count();
|
||||
if count == 10 {
|
||||
to_clear.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
let cleared_lines = to_clear.len();
|
||||
if cleared_lines == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
match cleared_lines {
|
||||
1 => score.points += 100,
|
||||
2 => score.points += 300,
|
||||
3 => score.points += 500,
|
||||
4 => score.points += 800,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
score.lines += cleared_lines as u32;
|
||||
|
||||
for line in to_clear {
|
||||
for i in line..HEIGHT as usize - 1 {
|
||||
block_grid.0[i] = block_grid.0[i + 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_blocks(tetromino: &Tetromino, block_grid: &mut BlockGrid) {
|
||||
for y in 0..HEIGHT as usize {
|
||||
for x in 0..WIDTH as usize {
|
||||
for piece in tetromino.b {
|
||||
let pos = (tetromino.pos.0 + piece.0, tetromino.pos.1 + piece.1);
|
||||
if pos == (x as i8, y as i8) {
|
||||
assert!(block_grid.0[y][x].is_none());
|
||||
block_grid.0[y][x].replace(Block(tetromino.kind));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_action(action: &Action, tetromino: &mut Tetromino, block_grid: &BlockGrid) {
|
||||
match action {
|
||||
Action::MoveRight => {
|
||||
tetromino.move_tetromino((1, 0), blocks);
|
||||
tetromino.move_tetromino((1, 0), block_grid);
|
||||
}
|
||||
Action::MoveLeft => {
|
||||
tetromino.move_tetromino((-1, 0), blocks);
|
||||
tetromino.move_tetromino((-1, 0), block_grid);
|
||||
}
|
||||
Action::Rotate(r) => tetromino.rotate(r, blocks),
|
||||
Action::Rotate(r) => tetromino.rotate(r, block_grid),
|
||||
Action::SoftDrop => (),
|
||||
Action::HardDrop => (),
|
||||
Action::HardDrop => while tetromino.move_tetromino((0, -1), block_grid) {},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +157,13 @@ fn input(now: &Instant, frame_time: Duration) -> Option<Action> {
|
||||
None
|
||||
}
|
||||
|
||||
fn render(tetromino: &Tetromino, blocks: &Vec<Block>, prev_frame_dur: &Duration) {
|
||||
fn render(
|
||||
tetromino: &Tetromino,
|
||||
block_grid: &BlockGrid,
|
||||
score: &Score,
|
||||
prev_frame_dur: &Duration,
|
||||
level: u32,
|
||||
) {
|
||||
let mut s = String::with_capacity(2 * 22 * 22);
|
||||
s.push('┏');
|
||||
s.push_str("━━━━━━━━━━━━━━━━━━━━");
|
||||
@@ -123,15 +172,12 @@ fn render(tetromino: &Tetromino, blocks: &Vec<Block>, prev_frame_dur: &Duration)
|
||||
s.push('┃');
|
||||
for x in 0..10 {
|
||||
let mut found = false;
|
||||
for block in blocks {
|
||||
if block.pos == (x, y) {
|
||||
s.push_str(block.kind.to_block());
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if let Some(block) = block_grid.0[y][x] {
|
||||
s.push_str(block.0.to_block());
|
||||
found = true;
|
||||
}
|
||||
for block in tetromino.b {
|
||||
if (block.0 + tetromino.pos.0, block.1 + tetromino.pos.1) == (x, y) {
|
||||
if (block.0 + tetromino.pos.0, block.1 + tetromino.pos.1) == (x as i8, y as i8) {
|
||||
s.push_str(tetromino.kind.to_block());
|
||||
found = true;
|
||||
break;
|
||||
@@ -147,7 +193,15 @@ fn render(tetromino: &Tetromino, blocks: &Vec<Block>, prev_frame_dur: &Duration)
|
||||
s.push_str("━━━━━━━━━━━━━━━━━━━━");
|
||||
s.push('┛');
|
||||
s.push('\n');
|
||||
s.push_str("frame_time: ");
|
||||
s.push_str(&prev_frame_dur.as_millis().to_string());
|
||||
s.push_str(" ms, points: ");
|
||||
s.push_str(&score.points.to_string());
|
||||
s.push_str(", lines: ");
|
||||
s.push_str(&score.lines.to_string());
|
||||
s.push_str(", level: ");
|
||||
s.push_str(&level.to_string());
|
||||
s.push('\n');
|
||||
terminal::disable_raw_mode().unwrap();
|
||||
print!("\x1b[2J\x1b[H{s}");
|
||||
terminal::enable_raw_mode().unwrap();
|
||||
@@ -163,14 +217,55 @@ fn exit() -> ! {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rand::prelude::*;
|
||||
#[test]
|
||||
fn color() {
|
||||
println!("red: {RED_BLOCK}");
|
||||
println!("green: {GREEN_BLOCK}");
|
||||
println!("blue: {BLUE_BLOCK}");
|
||||
println!("yellow: {YELLOW_BLOCK}");
|
||||
println!("orange: {ORANGE_BLOCK}");
|
||||
println!("purple: {PURPLE_BLOCK}");
|
||||
println!("cyan: {CYAN_BLOCK}");
|
||||
fn clearing() {
|
||||
let mut rng = thread_rng();
|
||||
let mut block_grid = BlockGrid([
|
||||
[Some(Block(TetrominoKind::new(&mut rng))); WIDTH as usize],
|
||||
[Some(Block(TetrominoKind::new(&mut rng))); WIDTH as usize],
|
||||
[Some(Block(TetrominoKind::new(&mut rng))); WIDTH as usize],
|
||||
[Some(Block(TetrominoKind::new(&mut rng))); WIDTH as usize],
|
||||
[Some(Block(TetrominoKind::new(&mut rng))); WIDTH as usize],
|
||||
[Some(Block(TetrominoKind::new(&mut rng))); WIDTH as usize],
|
||||
[Some(Block(TetrominoKind::new(&mut rng))); WIDTH as usize],
|
||||
[Some(Block(TetrominoKind::new(&mut rng))); WIDTH as usize],
|
||||
[Some(Block(TetrominoKind::new(&mut rng))); WIDTH as usize],
|
||||
[Some(Block(TetrominoKind::new(&mut rng))); WIDTH as usize],
|
||||
[None; WIDTH as usize],
|
||||
[None; WIDTH as usize],
|
||||
[None; WIDTH as usize],
|
||||
[None; WIDTH as usize],
|
||||
[None; WIDTH as usize],
|
||||
[None; WIDTH as usize],
|
||||
[None; WIDTH as usize],
|
||||
[None; WIDTH as usize],
|
||||
[None; WIDTH as usize],
|
||||
[None; WIDTH as usize],
|
||||
]);
|
||||
block_grid.0[1][3] = None;
|
||||
block_grid.0[4][4] = None;
|
||||
block_grid.0[5][1] = None;
|
||||
block_grid.0[6][3] = None;
|
||||
block_grid.0[7][2] = None;
|
||||
block_grid.0[8][8] = None;
|
||||
block_grid.0[9][7] = None;
|
||||
block_grid.0[10][0] = None;
|
||||
block_grid.0[11][9] = None;
|
||||
|
||||
let mut copy = BlockGrid(block_grid.0.clone());
|
||||
copy.0[0] = copy.0[1];
|
||||
copy.0[1] = copy.0[4];
|
||||
copy.0[2] = copy.0[5];
|
||||
copy.0[3] = copy.0[6];
|
||||
copy.0[4] = copy.0[7];
|
||||
copy.0[5] = copy.0[8];
|
||||
copy.0[6] = copy.0[9];
|
||||
copy.0[7] = copy.0[10];
|
||||
copy.0[8] = copy.0[11];
|
||||
copy.0[9] = copy.0[12];
|
||||
let mut score = Score::new();
|
||||
clear_lines(&mut block_grid, &mut score);
|
||||
assert_eq!(block_grid, copy);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::{
|
||||
use rand::prelude::*;
|
||||
|
||||
pub const WIDTH: i8 = 10;
|
||||
const HEIGHT: i8 = 20;
|
||||
pub const HEIGHT: i8 = 20;
|
||||
|
||||
const I: [[(i8, i8); 4]; 4] = [
|
||||
[(0, 2), (1, 2), (2, 2), (3, 2)],
|
||||
@@ -64,7 +64,7 @@ pub enum Rotation {
|
||||
Left,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum TetrominoKind {
|
||||
I,
|
||||
O,
|
||||
@@ -75,16 +75,14 @@ pub enum TetrominoKind {
|
||||
Z,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Block {
|
||||
pub pos: (i8, i8),
|
||||
pub kind: TetrominoKind,
|
||||
}
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct Block(pub TetrominoKind);
|
||||
|
||||
struct BlockGrid([[Option<Block>; 10]; 20]);
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct BlockGrid(pub [[Option<Block>; 10]; 20]);
|
||||
|
||||
impl BlockGrid {
|
||||
fn new() -> Self {
|
||||
pub fn new() -> Self {
|
||||
Self([[None; 10]; 20])
|
||||
}
|
||||
}
|
||||
@@ -95,14 +93,14 @@ impl Tetromino {
|
||||
b: kind.get_blocks(&Rotation::Neutral),
|
||||
kind,
|
||||
rotation: Rotation::Neutral,
|
||||
pos: (5, 20),
|
||||
pos: (3, 17),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_tetromino(&mut self, (x, y): (i8, i8), blocks: &Vec<Block>) -> bool {
|
||||
pub fn move_tetromino(&mut self, (x, y): (i8, i8), block_grid: &BlockGrid) -> bool {
|
||||
self.pos.0 += x;
|
||||
self.pos.1 += y;
|
||||
if !self.test_pos(blocks) {
|
||||
if !self.test_pos(block_grid) {
|
||||
self.pos.0 -= x;
|
||||
self.pos.1 -= y;
|
||||
false
|
||||
@@ -111,7 +109,7 @@ impl Tetromino {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rotate(&mut self, rotation: &Rotation, blocks: &Vec<Block>) {
|
||||
pub fn rotate(&mut self, rotation: &Rotation, block_grid: &BlockGrid) {
|
||||
let (r, b) = (rotation.clone(), self.b);
|
||||
match self.rotation {
|
||||
Rotation::Neutral => match rotation {
|
||||
@@ -143,42 +141,46 @@ impl Tetromino {
|
||||
},
|
||||
}
|
||||
self.b = self.kind.get_blocks(&self.rotation);
|
||||
if !self.test_pos(blocks) {
|
||||
if !self.test_pos(block_grid) {
|
||||
self.b = b;
|
||||
self.rotation = r;
|
||||
}
|
||||
}
|
||||
|
||||
fn test_pos(&self, blocks: &Vec<Block>) -> bool {
|
||||
fn test_pos(&self, block_grid: &BlockGrid) -> bool {
|
||||
for piece in self.b {
|
||||
let (x, y) = (piece.0 + self.pos.0, piece.1 + self.pos.1);
|
||||
if x >= WIDTH || x < 0 || y < 0 {
|
||||
return false;
|
||||
}
|
||||
for block in blocks {
|
||||
if block.pos == (x, y) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for piece in self.b {
|
||||
let (x, y) = (piece.0 + self.pos.0, piece.1 + self.pos.1);
|
||||
if y > HEIGHT - 2 {
|
||||
return true;
|
||||
}
|
||||
if block_grid.0[y as usize][x as usize].is_some() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl TetrominoKind {
|
||||
pub fn new(rng: &mut ThreadRng) -> Self {
|
||||
// match rng.gen_range(0..=7) {
|
||||
// 0 => Self::I,
|
||||
// 1 => Self::O,
|
||||
// 2 => Self::T,
|
||||
// 3 => Self::J,
|
||||
// 4 => Self::L,
|
||||
// 5 => Self::S,
|
||||
// 6 => Self::Z,
|
||||
// 7 => Self::O,
|
||||
// _ => unreachable!(),
|
||||
// }
|
||||
Self::Z
|
||||
match rng.gen_range(0..=7) {
|
||||
0 => Self::I,
|
||||
1 => Self::O,
|
||||
2 => Self::T,
|
||||
3 => Self::J,
|
||||
4 => Self::L,
|
||||
5 => Self::S,
|
||||
6 => Self::Z,
|
||||
7 => Self::O,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_blocks(&self, rotation: &Rotation) -> [(i8, i8); 4] {
|
||||
|
||||
Reference in New Issue
Block a user