initial commit
This commit is contained in:
parent
2cbe7dacb5
commit
37cf304a56
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "tetris"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
pancurses = "0.17.0"
|
||||
rand = "0.8.5"
|
||||
|
||||
[features]
|
216
src/data.rs
Normal file
216
src/data.rs
Normal file
|
@ -0,0 +1,216 @@
|
|||
use crate::Rotation;
|
||||
|
||||
pub type TetriminoData = &'static [&'static [bool]];
|
||||
|
||||
// these particular letter choices make it easier to see the shape of the tetrimino
|
||||
#[allow(non_upper_case_globals)]
|
||||
const o: bool = false;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
const U: bool = true;
|
||||
|
||||
pub const I: [TetriminoData; 4] = [
|
||||
&[
|
||||
&[o, o, o, o],
|
||||
&[U, U, U, U],
|
||||
&[o, o, o, o],
|
||||
&[o, o, o, o],
|
||||
],
|
||||
&[
|
||||
&[o, o, U, o],
|
||||
&[o, o, U, o],
|
||||
&[o, o, U, o],
|
||||
&[o, o, U, o],
|
||||
],
|
||||
&[
|
||||
&[o, o, o, o],
|
||||
&[o, o, o, o],
|
||||
&[U, U, U, U],
|
||||
&[o, o, o, o],
|
||||
],
|
||||
&[
|
||||
&[o, U, o, o],
|
||||
&[o, U, o, o],
|
||||
&[o, U, o, o],
|
||||
&[o, U, o, o],
|
||||
],
|
||||
];
|
||||
|
||||
pub const O: [TetriminoData; 4] = [
|
||||
&[
|
||||
&[U, U],
|
||||
&[U, U],
|
||||
],
|
||||
&[
|
||||
&[U, U],
|
||||
&[U, U],
|
||||
],
|
||||
&[
|
||||
&[U, U],
|
||||
&[U, U],
|
||||
],
|
||||
&[
|
||||
&[U, U],
|
||||
&[U, U],
|
||||
],
|
||||
];
|
||||
|
||||
pub const T: [TetriminoData; 4] = [
|
||||
&[
|
||||
&[o, U, o],
|
||||
&[U, U, U],
|
||||
&[o, o, o],
|
||||
],
|
||||
&[
|
||||
&[o, U, o],
|
||||
&[o, U, U],
|
||||
&[o, U, o],
|
||||
],
|
||||
&[
|
||||
&[o, o, o],
|
||||
&[U, U, U],
|
||||
&[o, U, o],
|
||||
],
|
||||
&[
|
||||
&[o, U, o],
|
||||
&[U, U, o],
|
||||
&[o, U, o],
|
||||
],
|
||||
];
|
||||
|
||||
pub const S: [TetriminoData; 4] = [
|
||||
&[
|
||||
&[o, U, U],
|
||||
&[U, U, o],
|
||||
&[o, o, o],
|
||||
],
|
||||
&[
|
||||
&[o, U, o],
|
||||
&[o, U, U],
|
||||
&[o, o, U],
|
||||
],
|
||||
&[
|
||||
&[o, o, o],
|
||||
&[o, U, U],
|
||||
&[U, U, o],
|
||||
],
|
||||
&[
|
||||
&[U, o, o],
|
||||
&[U, U, o],
|
||||
&[o, U, o],
|
||||
],
|
||||
];
|
||||
|
||||
pub const Z: [TetriminoData; 4] = [
|
||||
&[
|
||||
&[U, U, o],
|
||||
&[o, U, U],
|
||||
&[o, o, o],
|
||||
],
|
||||
&[
|
||||
&[o, o, U],
|
||||
&[o, U, U],
|
||||
&[o, U, o],
|
||||
],
|
||||
&[
|
||||
&[o, o, o],
|
||||
&[U, U, o],
|
||||
&[o, U, U],
|
||||
],
|
||||
&[
|
||||
&[o, U, o],
|
||||
&[U, U, o],
|
||||
&[U, o, o],
|
||||
],
|
||||
];
|
||||
|
||||
pub const J: [TetriminoData; 4] = [
|
||||
&[
|
||||
&[U, o, o],
|
||||
&[U, U, U],
|
||||
&[o, o, o],
|
||||
],
|
||||
&[
|
||||
&[o, U, U],
|
||||
&[o, U, o],
|
||||
&[o, U, o],
|
||||
],
|
||||
&[
|
||||
&[o, o, o],
|
||||
&[U, U, U],
|
||||
&[o, o, U],
|
||||
],
|
||||
&[
|
||||
&[o, U, o],
|
||||
&[o, U, o],
|
||||
&[U, U, o],
|
||||
],
|
||||
];
|
||||
|
||||
pub const L: [TetriminoData; 4] = [
|
||||
&[
|
||||
&[o, o, U],
|
||||
&[U, U, U],
|
||||
&[o, o, o],
|
||||
],
|
||||
&[
|
||||
&[o, U, o],
|
||||
&[o, U, o],
|
||||
&[o, U, U],
|
||||
],
|
||||
&[
|
||||
&[o, o, o],
|
||||
&[U, U, U],
|
||||
&[U, o, o],
|
||||
],
|
||||
&[
|
||||
&[U, U, o],
|
||||
&[o, U, o],
|
||||
&[o, U, o],
|
||||
],
|
||||
];
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct WallkickTable([[(i32, i32); 5]; 8]);
|
||||
|
||||
impl WallkickTable {
|
||||
pub fn for_rotation_cw(self, rotation: Rotation) -> [(i32, i32); 5] {
|
||||
match rotation {
|
||||
Rotation::Spawn => self.0[0],
|
||||
Rotation::Clockwise => self.0[2],
|
||||
Rotation::Flipped => self.0[4],
|
||||
Rotation::CounterClockwise => self.0[6],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn for_rotation_ccw(self, rotation: Rotation) -> [(i32, i32); 5] {
|
||||
match rotation {
|
||||
Rotation::Spawn => self.0[7],
|
||||
Rotation::Clockwise => self.0[1],
|
||||
Rotation::Flipped => self.0[3],
|
||||
Rotation::CounterClockwise => self.0[5],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const WALLKICK_TABLE_1: WallkickTable = WallkickTable([
|
||||
[( 0, 0), (-1, 0), (-1, 1), ( 0,-2), (-1,-2)], // 0>>1
|
||||
[( 0, 0), ( 1, 0), ( 1,-1), ( 0, 2), ( 1, 2)], // 1>>0
|
||||
[( 0, 0), ( 1, 0), ( 1,-1), ( 0, 2), ( 1, 2)], // 1>>2
|
||||
[( 0, 0), (-1, 0), (-1, 1), ( 0,-2), (-1,-2)], // 2>>1
|
||||
[( 0, 0), ( 1, 0), ( 1, 1), ( 0,-2), ( 1,-2)], // 2>>3
|
||||
[( 0, 0), (-1, 0), (-1,-1), ( 0, 2), (-1, 2)], // 3>>2
|
||||
[( 0, 0), (-1, 0), (-1,-1), ( 0, 2), (-1, 2)], // 3>>0
|
||||
[( 0, 0), ( 1, 0), ( 1, 1), ( 0,-2), ( 1,-2)], // 0>>3
|
||||
]);
|
||||
|
||||
pub const WALLKICK_TABLE_2: WallkickTable = WallkickTable([
|
||||
[( 0, 0), (-2, 0), ( 1, 0), (-2,-1), ( 1, 2)], // 0>>1
|
||||
[( 0, 0), ( 2, 0), (-1, 0), ( 2, 1), (-1,-2)], // 1>>0
|
||||
[( 0, 0), (-1, 0), ( 2, 0), (-1, 2), ( 2,-1)], // 1>>2
|
||||
[( 0, 0), ( 1, 0), (-2, 0), ( 1,-2), (-2, 1)], // 2>>1
|
||||
[( 0, 0), ( 2, 0), (-1, 0), ( 2, 1), (-1,-2)], // 2>>3
|
||||
[( 0, 0), (-2, 0), ( 1, 0), (-2,-1), ( 1, 2)], // 3>>2
|
||||
[( 0, 0), ( 1, 0), (-2, 0), ( 1,-2), (-2, 1)], // 3>>0
|
||||
[( 0, 0), (-1, 0), ( 2, 0), (-1, 2), ( 2,-1)], // 0>>3
|
||||
]);
|
390
src/main.rs
Normal file
390
src/main.rs
Normal file
|
@ -0,0 +1,390 @@
|
|||
use std::{collections::VecDeque, time::{Instant, Duration}};
|
||||
use data::{TetriminoData, WallkickTable, WALLKICK_TABLE_1, WALLKICK_TABLE_2};
|
||||
use pancurses::{initscr, noecho, Input, endwin, start_color, init_color, use_default_colors, init_pair, COLOR_RED, COLOR_CYAN, COLOR_YELLOW, COLOR_MAGENTA, COLOR_GREEN, COLOR_BLUE, Window, COLOR_WHITE, curs_set, cbreak};
|
||||
use rand::prelude::*;
|
||||
|
||||
mod data;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum Color {
|
||||
Cyan, // I
|
||||
Yellow, // O
|
||||
Purple, // T
|
||||
Green, // S
|
||||
Red, // Z
|
||||
Blue, // J
|
||||
Orange // L
|
||||
}
|
||||
|
||||
const COLOR_ORANGE: i16 = 8;
|
||||
|
||||
impl Color {
|
||||
fn into_color_pair(self) -> i16 {
|
||||
match self {
|
||||
Self::Cyan => COLOR_CYAN,
|
||||
Self::Yellow => COLOR_YELLOW,
|
||||
Self::Purple => COLOR_MAGENTA,
|
||||
Self::Green => COLOR_GREEN,
|
||||
Self::Red => COLOR_RED,
|
||||
Self::Blue => COLOR_BLUE,
|
||||
Self::Orange => COLOR_ORANGE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum TetriminoKind {
|
||||
I, O, T, S, Z, J, L
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
enum Rotation {
|
||||
Spawn,
|
||||
Clockwise,
|
||||
Flipped,
|
||||
CounterClockwise
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct Tetrimino {
|
||||
kind: TetriminoKind,
|
||||
rotation: Rotation
|
||||
}
|
||||
|
||||
impl Into<Color> for Tetrimino {
|
||||
fn into(self) -> Color {
|
||||
match self.kind {
|
||||
TetriminoKind::I => Color::Cyan,
|
||||
TetriminoKind::O => Color::Yellow,
|
||||
TetriminoKind::T => Color::Purple,
|
||||
TetriminoKind::S => Color::Green,
|
||||
TetriminoKind::Z => Color::Red,
|
||||
TetriminoKind::J => Color::Blue,
|
||||
TetriminoKind::L => Color::Orange,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<TetriminoData> for Tetrimino {
|
||||
fn into(self) -> TetriminoData {
|
||||
let i = self.rotation as usize;
|
||||
match self.kind {
|
||||
TetriminoKind::I => data::I[i],
|
||||
TetriminoKind::O => data::O[i],
|
||||
TetriminoKind::T => data::T[i],
|
||||
TetriminoKind::S => data::S[i],
|
||||
TetriminoKind::Z => data::Z[i],
|
||||
TetriminoKind::J => data::J[i],
|
||||
TetriminoKind::L => data::L[i],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Tetrimino {
|
||||
fn data(self) -> TetriminoData {
|
||||
self.into()
|
||||
}
|
||||
|
||||
fn color(self) -> Color {
|
||||
self.into()
|
||||
}
|
||||
|
||||
fn wallkick_table(self) -> WallkickTable {
|
||||
match self.kind {
|
||||
TetriminoKind::I => WALLKICK_TABLE_2,
|
||||
_ => WALLKICK_TABLE_1
|
||||
}
|
||||
}
|
||||
|
||||
fn rotate_cw(&mut self) {
|
||||
use Rotation::*;
|
||||
self.rotation = match self.rotation {
|
||||
Spawn => Clockwise,
|
||||
Clockwise => Flipped,
|
||||
Flipped => CounterClockwise,
|
||||
CounterClockwise => Spawn,
|
||||
}
|
||||
}
|
||||
|
||||
fn rotate_ccw(&mut self) {
|
||||
use Rotation::*;
|
||||
self.rotation = match self.rotation {
|
||||
Spawn => CounterClockwise,
|
||||
Clockwise => Spawn,
|
||||
Flipped => Clockwise,
|
||||
CounterClockwise => Flipped,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TetriminoGenerator {
|
||||
remaining: VecDeque<TetriminoKind>
|
||||
}
|
||||
|
||||
impl TetriminoGenerator {
|
||||
fn new() -> Self {
|
||||
Self { remaining: VecDeque::new() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for TetriminoGenerator {
|
||||
type Item = Tetrimino;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.remaining.is_empty() {
|
||||
use TetriminoKind::*;
|
||||
let mut next_bag = [I, O, T, S, Z, J, L];
|
||||
next_bag.shuffle(&mut rand::thread_rng());
|
||||
self.remaining.extend(next_bag);
|
||||
}
|
||||
|
||||
// kinda useless to unwrap but its better to panic than to run out of tetriminoes
|
||||
Some(Tetrimino { kind: self.remaining.pop_front().unwrap(), rotation: Rotation::Spawn })
|
||||
}
|
||||
}
|
||||
|
||||
fn init_colors() {
|
||||
start_color();
|
||||
use_default_colors();
|
||||
init_pair(COLOR_CYAN, COLOR_CYAN, -1);
|
||||
init_pair(COLOR_YELLOW, COLOR_YELLOW, -1);
|
||||
init_pair(COLOR_MAGENTA, COLOR_MAGENTA, -1);
|
||||
init_pair(COLOR_GREEN, COLOR_GREEN, -1);
|
||||
init_pair(COLOR_RED, COLOR_RED, -1);
|
||||
init_pair(COLOR_BLUE, COLOR_BLUE, -1);
|
||||
init_color(COLOR_ORANGE, 1000, 843, 0);
|
||||
init_pair(COLOR_ORANGE, COLOR_ORANGE, -1);
|
||||
init_pair(COLOR_WHITE, COLOR_WHITE, -1);
|
||||
}
|
||||
|
||||
fn write_tetrimino(t: TetriminoData, window: &Window, mut x: i32, mut y: i32) {
|
||||
for row in t {
|
||||
for cell in *row {
|
||||
if y >= 0 && *cell {
|
||||
window.mvprintw(y, x, "██");
|
||||
}
|
||||
x += 2;
|
||||
}
|
||||
x -= row.len() as i32 * 2;
|
||||
y += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn write_tetrimino_shadow(t: TetriminoData, window: &Window, mut x: i32, mut y: i32) {
|
||||
for row in t {
|
||||
for cell in *row {
|
||||
if y >= 0 && *cell {
|
||||
window.mvprintw(y, x, "░░");
|
||||
}
|
||||
x += 2;
|
||||
}
|
||||
x -= row.len() as i32 * 2;
|
||||
y += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn print_board(board: [[Option::<Color>; 10]; 40], window: &Window) {
|
||||
for (y, row) in board.iter().enumerate() {
|
||||
for (x, cell) in row.iter().enumerate() {
|
||||
if y as i32 - 20 < 0 { // moment
|
||||
continue;
|
||||
}
|
||||
window.mv(y as i32 - 20, 2 * x as i32);
|
||||
if let Some(cell) = cell {
|
||||
window.color_set(cell.into_color_pair());
|
||||
window.printw("██");
|
||||
} else {
|
||||
window.printw(" ");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_tetrimino_valid(t: TetriminoData, board: [[Option::<Color>; 10]; 40], mut x: i32, mut y: i32) -> bool {
|
||||
for row in t {
|
||||
for cell in *row {
|
||||
if *cell {
|
||||
if x > 9 || x < 0 || y < 0 || y > 39 || board[y as usize][x as usize].is_some() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
x += 1;
|
||||
}
|
||||
x -= row.len() as i32 * 1;
|
||||
y += 1;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn lock_tetrimino(tetrimino: Tetrimino, board: &mut [[Option::<Color>; 10]; 40], mut x: i32, mut y: i32) {
|
||||
for row in tetrimino.data() {
|
||||
for cell in *row {
|
||||
if *cell {
|
||||
board[y as usize][x as usize] = Some(tetrimino.color());
|
||||
}
|
||||
x += 1;
|
||||
}
|
||||
x -= row.len() as i32 * 1;
|
||||
y += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut board = [[None; 10]; 40];
|
||||
|
||||
let window = initscr();
|
||||
|
||||
// dont put this before initscr, you get a segfault :)
|
||||
// "rust is memory safe" not if youre the ncurses crate author
|
||||
noecho();
|
||||
|
||||
curs_set(0);
|
||||
|
||||
cbreak();
|
||||
|
||||
init_colors();
|
||||
|
||||
window.keypad(true);
|
||||
|
||||
window.nodelay(true);
|
||||
|
||||
window.refresh();
|
||||
|
||||
for y in 0..20 {
|
||||
window.mvprintw(y, 20, "|");
|
||||
}
|
||||
window.mvprintw(20, 0, "--------------------+");
|
||||
|
||||
let mut rando = TetriminoGenerator::new();
|
||||
|
||||
macro_rules! reset {
|
||||
($a:ident, $b:ident, $c:ident, $d:ident, $e:ident) => {
|
||||
$a = Instant::now();
|
||||
$b = $e.next().unwrap();
|
||||
$c = 18;
|
||||
$d = 3;
|
||||
}
|
||||
}
|
||||
|
||||
let mut next_fall;
|
||||
let mut tetrimino;
|
||||
let mut y;
|
||||
let mut x;
|
||||
|
||||
reset!(next_fall, tetrimino, y, x, rando);
|
||||
|
||||
loop {
|
||||
let mut needs_refresh = false;
|
||||
|
||||
let key = window.getch();
|
||||
|
||||
if let Some(key) = key {
|
||||
if key == Input::Character('q') {
|
||||
break
|
||||
}
|
||||
|
||||
if key == Input::KeyLeft {
|
||||
if check_tetrimino_valid(tetrimino.into(), board, x - 1, y) {
|
||||
needs_refresh = true;
|
||||
x -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
if let Input::KeyRight = key {
|
||||
if check_tetrimino_valid(tetrimino.into(), board, x + 1, y) {
|
||||
needs_refresh = true;
|
||||
x += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if key == Input::KeyUp || key == Input::Character('x') {
|
||||
for kick in tetrimino.wallkick_table().for_rotation_cw(tetrimino.rotation) {
|
||||
tetrimino.rotate_cw();
|
||||
if check_tetrimino_valid(tetrimino.into(), board, x + kick.0, y - kick.1) {
|
||||
needs_refresh = true;
|
||||
x += kick.0;
|
||||
y -= kick.1;
|
||||
break;
|
||||
} else {
|
||||
tetrimino.rotate_ccw();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if key == Input::Character('z') {
|
||||
for kick in tetrimino.wallkick_table().for_rotation_ccw(tetrimino.rotation) {
|
||||
tetrimino.rotate_ccw();
|
||||
if check_tetrimino_valid(tetrimino.into(), board, x + kick.0, y - kick.1) {
|
||||
needs_refresh = true;
|
||||
x += kick.0;
|
||||
y -= kick.1;
|
||||
break;
|
||||
} else {
|
||||
tetrimino.rotate_cw();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Input::KeyDown = key {
|
||||
if check_tetrimino_valid(tetrimino.into(), board, x, y + 1) {
|
||||
needs_refresh = true;
|
||||
y += 1;
|
||||
next_fall = Instant::now() + Duration::from_millis(500);
|
||||
} else {
|
||||
lock_tetrimino(tetrimino, &mut board, x, y);
|
||||
|
||||
reset!(next_fall, tetrimino, y, x, rando);
|
||||
}
|
||||
}
|
||||
|
||||
if let Input::Character('c') = key {
|
||||
// while !drop_tetrimino(tetrimino, &mut board, &mut x, &mut y) {}
|
||||
while check_tetrimino_valid(tetrimino.data(), board, x, y + 1) {
|
||||
y += 1;
|
||||
}
|
||||
|
||||
lock_tetrimino(tetrimino, &mut board, x, y);
|
||||
|
||||
reset!(next_fall, tetrimino, y, x, rando);
|
||||
}
|
||||
}
|
||||
|
||||
if Instant::now() > next_fall {
|
||||
if check_tetrimino_valid(tetrimino.into(), board, x, y + 1) {
|
||||
needs_refresh = true;
|
||||
y += 1;
|
||||
next_fall += Duration::from_millis(500);
|
||||
} else {
|
||||
lock_tetrimino(tetrimino, &mut board, x, y);
|
||||
|
||||
reset!(next_fall, tetrimino, y, x, rando);
|
||||
}
|
||||
}
|
||||
|
||||
if needs_refresh {
|
||||
for y in 0..=39 {
|
||||
if board[y].iter().all(Option::is_some) {
|
||||
for y2 in (1..=y).into_iter().rev() {
|
||||
board[y2] = board[y2 - 1];
|
||||
}
|
||||
board[0] = [None; 10];
|
||||
}
|
||||
}
|
||||
|
||||
let mut shadow_y = y;
|
||||
while check_tetrimino_valid(tetrimino.into(), board, x, shadow_y + 1) {
|
||||
shadow_y += 1;
|
||||
}
|
||||
|
||||
print_board(board, &window);
|
||||
window.color_set(tetrimino.color().into_color_pair());
|
||||
write_tetrimino(tetrimino.into(), &window, 2 * x, y - 20);
|
||||
window.color_set(COLOR_WHITE);
|
||||
write_tetrimino_shadow(tetrimino.into(), &window, 2 * x, shadow_y - 20);
|
||||
}
|
||||
}
|
||||
|
||||
endwin();
|
||||
}
|
Loading…
Reference in a new issue