before block grid
This commit is contained in:
@@ -4,4 +4,5 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
crossterm = "0.27.0"
|
||||
rand = "0.8.5"
|
||||
|
||||
206
src/main.rs
206
src/main.rs
@@ -1,7 +1,12 @@
|
||||
// use rand::prelude::*;
|
||||
use std::time::Instant;
|
||||
use std::{io, io::Write};
|
||||
// https://tetris.wiki/Super_Rotation_System
|
||||
use crossterm::{
|
||||
event,
|
||||
event::{Event, KeyCode},
|
||||
terminal,
|
||||
};
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{io, io::Write};
|
||||
use tetromino::{Rotation, WIDTH};
|
||||
|
||||
const RED_BLOCK: &str = "\x1b[48;2;255;0;0m \x1b[0m";
|
||||
const GREEN_BLOCK: &str = "\x1b[48;2;0;255;0m \x1b[0m";
|
||||
@@ -11,110 +16,105 @@ const ORANGE_BLOCK: &str = "\x1b[48;2;255;166;0m \x1b[0m";
|
||||
const PURPLE_BLOCK: &str = "\x1b[48;2;128;0;128m \x1b[0m";
|
||||
const CYAN_BLOCK: &str = "\x1b[48;2;0;255;255m \x1b[0m";
|
||||
|
||||
const I: [(i8, i8); 4] = [(-1, 0), (0, 0), (1, 0), (2, 0)];
|
||||
const J: [(i8, i8); 4] = [(0, 0), (-1, 0), (1, 0), (-1, 1)];
|
||||
const L: [(i8, i8); 4] = [(0, 0), (-1, 0), (1, 0), (1, 1)];
|
||||
const T: [(i8, i8); 4] = [(0, 0), (0, 1), (-1, 0), (1, 0)];
|
||||
mod tetromino;
|
||||
use tetromino::{Block, Tetromino, TetrominoKind};
|
||||
|
||||
fn main() {
|
||||
let instant = Instant::now();
|
||||
std::thread::sleep(std::time::Duration::from_millis(3));
|
||||
let mut tetromino = Tetromino::new(&instant);
|
||||
let mut rng = rand::thread_rng();
|
||||
terminal::enable_raw_mode().unwrap();
|
||||
let frame_time = Duration::from_millis(200);
|
||||
print!("\x1b[?25l");
|
||||
let mut tetromino = Tetromino::new(TetrominoKind::new(&mut rng));
|
||||
let mut blocks = vec![];
|
||||
let mut prev_frame_dur = Instant::now().elapsed();
|
||||
loop {
|
||||
render(&tetromino, &blocks);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
render(&tetromino, &blocks, &prev_frame_dur);
|
||||
prev_frame_dur = frame_dur.elapsed();
|
||||
}
|
||||
}
|
||||
|
||||
enum TetrominoKind {
|
||||
I,
|
||||
O,
|
||||
T,
|
||||
J,
|
||||
L,
|
||||
S,
|
||||
Z,
|
||||
}
|
||||
|
||||
struct Tetromino {
|
||||
kind: TetrominoKind,
|
||||
pos: (i8, i8),
|
||||
b: [(i8, i8); 4],
|
||||
}
|
||||
|
||||
struct Block {
|
||||
pos: (i8, i8),
|
||||
kind: TetrominoKind,
|
||||
}
|
||||
|
||||
// impl TetrominoKind {
|
||||
// fn to_str(&self) -> &str {
|
||||
// match self {
|
||||
// Color::Red => RED_BLOCK,
|
||||
// Color::Blue => BLUE_BLOCK,
|
||||
// Color::Green => GREEN_BLOCK,
|
||||
// Color::Yellow => YELLOW_BLOCK,
|
||||
// Color::Orange => ORANGE_BLOCK,
|
||||
// Color::Purple => PURPLE_BLOCK,
|
||||
// Color::Cyan => CYAN_BLOCK,
|
||||
// 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() {
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
|
||||
impl Tetromino {
|
||||
fn new(instant: &Instant) -> Self {
|
||||
let kind = TetrominoKind::new(instant);
|
||||
Self {
|
||||
b: kind.get_blocks(),
|
||||
kind,
|
||||
pos: (5, 5),
|
||||
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,
|
||||
Rotate(Rotation),
|
||||
SoftDrop,
|
||||
HardDrop,
|
||||
}
|
||||
|
||||
fn handle_action(action: &Action, tetromino: &mut Tetromino, blocks: &Vec<Block>) {
|
||||
match action {
|
||||
Action::MoveRight => {
|
||||
tetromino.move_tetromino((1, 0), blocks);
|
||||
}
|
||||
Action::MoveLeft => {
|
||||
tetromino.move_tetromino((-1, 0), blocks);
|
||||
}
|
||||
Action::Rotate(r) => tetromino.rotate(r, blocks),
|
||||
Action::SoftDrop => (),
|
||||
Action::HardDrop => (),
|
||||
}
|
||||
}
|
||||
|
||||
impl TetrominoKind {
|
||||
fn new(instant: &Instant) -> Self {
|
||||
match instant.elapsed().as_millis() % 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) -> [(i8, i8); 4] {
|
||||
match self {
|
||||
Self::I => I,
|
||||
Self::O => T,
|
||||
Self::T => T,
|
||||
Self::J => J,
|
||||
Self::L => L,
|
||||
Self::S => T,
|
||||
Self::Z => T,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_block(&self) -> &str {
|
||||
match self {
|
||||
Self::I => CYAN_BLOCK,
|
||||
Self::O => YELLOW_BLOCK,
|
||||
Self::T => PURPLE_BLOCK,
|
||||
Self::J => BLUE_BLOCK,
|
||||
Self::L => ORANGE_BLOCK,
|
||||
Self::S => GREEN_BLOCK,
|
||||
Self::Z => RED_BLOCK,
|
||||
fn input(now: &Instant, frame_time: Duration) -> Option<Action> {
|
||||
if let Some(poll_duration) = frame_time.checked_sub(now.elapsed()) {
|
||||
if event::poll(poll_duration).ok()? {
|
||||
if let Event::Key(k) = event::read().ok()? {
|
||||
if let KeyCode::Char(c) = k.code {
|
||||
match c {
|
||||
'a' => return Some(Action::MoveLeft),
|
||||
'd' => return Some(Action::MoveRight),
|
||||
'l' => return Some(Action::Rotate(Rotation::Right)),
|
||||
'j' => return Some(Action::Rotate(Rotation::Left)),
|
||||
'k' => return Some(Action::Rotate(Rotation::Flip)),
|
||||
's' => return Some(Action::SoftDrop),
|
||||
' ' => return Some(Action::HardDrop),
|
||||
'q' => exit(),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn render(tetromino: &Tetromino, blocks: &Vec<Block>) {
|
||||
fn render(tetromino: &Tetromino, blocks: &Vec<Block>, prev_frame_dur: &Duration) {
|
||||
let mut s = String::with_capacity(2 * 22 * 22);
|
||||
s.push('┏');
|
||||
s.push_str("━━━━━━━━━━━━━━━━━━━━");
|
||||
@@ -146,17 +146,31 @@ fn render(tetromino: &Tetromino, blocks: &Vec<Block>) {
|
||||
s.push('┗');
|
||||
s.push_str("━━━━━━━━━━━━━━━━━━━━");
|
||||
s.push('┛');
|
||||
s.push('\n');
|
||||
s.push_str(&prev_frame_dur.as_millis().to_string());
|
||||
terminal::disable_raw_mode().unwrap();
|
||||
print!("\x1b[2J\x1b[H{s}");
|
||||
terminal::enable_raw_mode().unwrap();
|
||||
io::stdout().flush().unwrap();
|
||||
}
|
||||
|
||||
#[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 exit() -> ! {
|
||||
print!("\x1b[?25h");
|
||||
terminal::disable_raw_mode().unwrap();
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[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}");
|
||||
}
|
||||
}
|
||||
|
||||
213
src/tetromino.rs
Normal file
213
src/tetromino.rs
Normal file
@@ -0,0 +1,213 @@
|
||||
use crate::{
|
||||
BLUE_BLOCK, CYAN_BLOCK, GREEN_BLOCK, ORANGE_BLOCK, PURPLE_BLOCK, RED_BLOCK, YELLOW_BLOCK,
|
||||
};
|
||||
use rand::prelude::*;
|
||||
|
||||
pub const WIDTH: i8 = 10;
|
||||
const HEIGHT: i8 = 20;
|
||||
|
||||
const I: [[(i8, i8); 4]; 4] = [
|
||||
[(0, 2), (1, 2), (2, 2), (3, 2)],
|
||||
[(2, 0), (2, 1), (2, 2), (2, 3)],
|
||||
[(0, 1), (1, 1), (2, 1), (3, 1)],
|
||||
[(1, 0), (1, 1), (1, 2), (1, 3)],
|
||||
];
|
||||
const O: [[(i8, i8); 4]; 4] = [
|
||||
[(1, 1), (1, 2), (2, 1), (2, 2)],
|
||||
[(1, 1), (1, 2), (2, 1), (2, 2)],
|
||||
[(1, 1), (1, 2), (2, 1), (2, 2)],
|
||||
[(1, 1), (1, 2), (2, 1), (2, 2)],
|
||||
];
|
||||
const J: [[(i8, i8); 4]; 4] = [
|
||||
[(0, 1), (1, 1), (2, 1), (0, 2)],
|
||||
[(1, 0), (1, 1), (1, 2), (2, 2)],
|
||||
[(0, 1), (1, 1), (2, 1), (2, 0)],
|
||||
[(1, 0), (1, 1), (1, 2), (0, 0)],
|
||||
];
|
||||
const L: [[(i8, i8); 4]; 4] = [
|
||||
[(0, 1), (1, 1), (2, 1), (2, 2)],
|
||||
[(1, 0), (1, 1), (1, 2), (2, 0)],
|
||||
[(0, 1), (1, 1), (2, 1), (0, 0)],
|
||||
[(1, 0), (1, 1), (1, 2), (0, 2)],
|
||||
];
|
||||
const T: [[(i8, i8); 4]; 4] = [
|
||||
[(0, 1), (1, 1), (2, 1), (1, 2)],
|
||||
[(1, 0), (1, 1), (1, 2), (2, 1)],
|
||||
[(0, 1), (1, 1), (2, 1), (1, 0)],
|
||||
[(1, 0), (1, 1), (1, 2), (0, 1)],
|
||||
];
|
||||
const S: [[(i8, i8); 4]; 4] = [
|
||||
[(0, 1), (1, 1), (1, 2), (2, 2)],
|
||||
[(1, 1), (1, 2), (2, 1), (2, 0)],
|
||||
[(0, 0), (1, 0), (1, 1), (2, 1)],
|
||||
[(0, 2), (0, 1), (1, 1), (1, 0)],
|
||||
];
|
||||
const Z: [[(i8, i8); 4]; 4] = [
|
||||
[(0, 2), (1, 2), (1, 1), (2, 1)],
|
||||
[(1, 0), (1, 1), (2, 1), (2, 2)],
|
||||
[(0, 1), (1, 1), (1, 0), (2, 0)],
|
||||
[(0, 0), (0, 1), (1, 1), (1, 2)],
|
||||
];
|
||||
|
||||
pub struct Tetromino {
|
||||
pub kind: TetrominoKind,
|
||||
pub pos: (i8, i8),
|
||||
pub rotation: Rotation,
|
||||
pub b: [(i8, i8); 4],
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Rotation {
|
||||
Neutral,
|
||||
Right,
|
||||
Flip,
|
||||
Left,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum TetrominoKind {
|
||||
I,
|
||||
O,
|
||||
T,
|
||||
J,
|
||||
L,
|
||||
S,
|
||||
Z,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Block {
|
||||
pub pos: (i8, i8),
|
||||
pub kind: TetrominoKind,
|
||||
}
|
||||
|
||||
struct BlockGrid([[Option<Block>; 10]; 20]);
|
||||
|
||||
impl BlockGrid {
|
||||
fn new() -> Self {
|
||||
Self([[None; 10]; 20])
|
||||
}
|
||||
}
|
||||
|
||||
impl Tetromino {
|
||||
pub fn new(kind: TetrominoKind) -> Self {
|
||||
Self {
|
||||
b: kind.get_blocks(&Rotation::Neutral),
|
||||
kind,
|
||||
rotation: Rotation::Neutral,
|
||||
pos: (5, 20),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_tetromino(&mut self, (x, y): (i8, i8), blocks: &Vec<Block>) -> bool {
|
||||
self.pos.0 += x;
|
||||
self.pos.1 += y;
|
||||
if !self.test_pos(blocks) {
|
||||
self.pos.0 -= x;
|
||||
self.pos.1 -= y;
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rotate(&mut self, rotation: &Rotation, blocks: &Vec<Block>) {
|
||||
let (r, b) = (rotation.clone(), self.b);
|
||||
match self.rotation {
|
||||
Rotation::Neutral => match rotation {
|
||||
Rotation::Neutral => (),
|
||||
Rotation::Left => self.rotation = Rotation::Left,
|
||||
Rotation::Right => self.rotation = Rotation::Right,
|
||||
Rotation::Flip => self.rotation = Rotation::Flip,
|
||||
},
|
||||
|
||||
Rotation::Left => match rotation {
|
||||
Rotation::Neutral => self.rotation = Rotation::Left,
|
||||
Rotation::Left => self.rotation = Rotation::Flip,
|
||||
Rotation::Right => self.rotation = Rotation::Neutral,
|
||||
Rotation::Flip => self.rotation = Rotation::Right,
|
||||
},
|
||||
|
||||
Rotation::Right => match rotation {
|
||||
Rotation::Neutral => self.rotation = Rotation::Right,
|
||||
Rotation::Left => self.rotation = Rotation::Neutral,
|
||||
Rotation::Right => self.rotation = Rotation::Flip,
|
||||
Rotation::Flip => self.rotation = Rotation::Left,
|
||||
},
|
||||
|
||||
Rotation::Flip => match rotation {
|
||||
Rotation::Neutral => self.rotation = Rotation::Flip,
|
||||
Rotation::Left => self.rotation = Rotation::Right,
|
||||
Rotation::Right => self.rotation = Rotation::Left,
|
||||
Rotation::Flip => self.rotation = Rotation::Neutral,
|
||||
},
|
||||
}
|
||||
self.b = self.kind.get_blocks(&self.rotation);
|
||||
if !self.test_pos(blocks) {
|
||||
self.b = b;
|
||||
self.rotation = r;
|
||||
}
|
||||
}
|
||||
|
||||
fn test_pos(&self, blocks: &Vec<Block>) -> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
fn get_blocks(&self, rotation: &Rotation) -> [(i8, i8); 4] {
|
||||
let i = match rotation {
|
||||
Rotation::Neutral => 0,
|
||||
Rotation::Right => 1,
|
||||
Rotation::Flip => 2,
|
||||
Rotation::Left => 3,
|
||||
};
|
||||
match self {
|
||||
Self::I => I[i],
|
||||
Self::O => O[i],
|
||||
Self::T => T[i],
|
||||
Self::J => J[i],
|
||||
Self::L => L[i],
|
||||
Self::S => S[i],
|
||||
Self::Z => Z[i],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_block(&self) -> &str {
|
||||
match self {
|
||||
Self::I => CYAN_BLOCK,
|
||||
Self::O => YELLOW_BLOCK,
|
||||
Self::T => PURPLE_BLOCK,
|
||||
Self::J => BLUE_BLOCK,
|
||||
Self::L => ORANGE_BLOCK,
|
||||
Self::S => GREEN_BLOCK,
|
||||
Self::Z => RED_BLOCK,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user