before block grid

This commit is contained in:
2024-05-25 21:06:58 +02:00
parent ea64a7c7bb
commit 7cda917ea7
3 changed files with 324 additions and 96 deletions

View File

@@ -4,4 +4,5 @@ version = "0.1.0"
edition = "2021"
[dependencies]
crossterm = "0.27.0"
rand = "0.8.5"

View File

@@ -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
View 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,
}
}
}