initial commit

This commit is contained in:
4a656666 2022-03-30 10:06:33 -05:00
parent 2cbe7dacb5
commit 37cf304a56
3 changed files with 618 additions and 0 deletions

12
Cargo.toml Normal file
View 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
View 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
View 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();
}