#![allow(non_upper_case_globals)]
use cmd_lib::*;
use std::io::Read;
use std::{thread, time};
tls_init!(DELAY, f64, 1.0); const DELAY_FACTOR: f64 = 0.8;
const RED: i32 = 1;
const GREEN: i32 = 2;
const YELLOW: i32 = 3;
const BLUE: i32 = 4;
const FUCHSIA: i32 = 5;
const CYAN: i32 = 6;
const WHITE: i32 = 7;
const PLAYFIELD_W: i32 = 10;
const PLAYFIELD_H: i32 = 20;
const PLAYFIELD_X: i32 = 30;
const PLAYFIELD_Y: i32 = 1;
const BORDER_COLOR: i32 = YELLOW;
const SCORE_X: i32 = 1;
const SCORE_Y: i32 = 2;
const SCORE_COLOR: i32 = GREEN;
const HELP_X: i32 = 58;
const HELP_Y: i32 = 1;
const HELP_COLOR: i32 = CYAN;
const NEXT_X: i32 = 14;
const NEXT_Y: i32 = 11;
const GAMEOVER_X: i32 = 1;
const GAMEOVER_Y: i32 = PLAYFIELD_H + 3;
const LEVEL_UP: i32 = 20;
const colors: [i32; 7] = [RED, GREEN, YELLOW, BLUE, FUCHSIA, CYAN, WHITE];
const empty_cell: &str = " ."; const filled_cell: &str = "[]";
tls_init!(use_color, bool, true); tls_init!(score, i32, 0); tls_init!(level, i32, 1); tls_init!(lines_completed, i32, 0); tls_init!(screen_buffer, String, "".to_string());
fn puts(changes: &str) {
tls_set!(screen_buffer, |s| s.push_str(changes));
}
fn flush_screen() {
eprint!("{}", tls_get!(screen_buffer));
tls_set!(screen_buffer, |s| s.clear());
}
const ESC: char = '\x1b';
fn xyprint(x: i32, y: i32, s: &str) {
puts(&format!("{}[{};{}H{}", ESC, y, x, s));
}
fn show_cursor() {
eprint!("{}[?25h", ESC);
}
fn hide_cursor() {
eprint!("{}[?25l", ESC);
}
fn set_fg(color: i32) {
if tls_get!(use_color) {
puts(&format!("{}[3{}m", ESC, color));
}
}
fn set_bg(color: i32) {
if tls_get!(use_color) {
puts(&format!("{}[4{}m", ESC, color));
}
}
fn reset_colors() {
puts(&format!("{}[0m", ESC));
}
fn set_bold() {
puts(&format!("{}[1m", ESC));
}
tls_init!(
playfield,
[i32; PLAYFIELD_H as usize],
[0; PLAYFIELD_H as usize]
);
fn redraw_playfield() {
for y in 0..PLAYFIELD_H {
xyprint(PLAYFIELD_X, PLAYFIELD_Y + y, "");
for x in 0..PLAYFIELD_W {
let color = (tls_get!(playfield)[y as usize] >> (x * 3)) & 7;
if color == 0 {
puts(empty_cell);
} else {
set_fg(color);
set_bg(color);
puts(filled_cell);
reset_colors();
}
}
}
}
fn update_score(lines: i32) {
tls_set!(lines_completed, |l| *l += lines);
tls_set!(score, |s| *s += lines * lines);
if tls_get!(score) > LEVEL_UP * tls_get!(level) {
tls_set!(level, |l| *l += 1); tls_set!(DELAY, |d| *d *= DELAY_FACTOR); }
set_bold();
set_fg(SCORE_COLOR);
xyprint(
SCORE_X,
SCORE_Y,
&format!("Lines completed: {}", tls_get!(lines_completed)),
);
xyprint(
SCORE_X,
SCORE_Y + 1,
&format!("Level: {}", tls_get!(level)),
);
xyprint(
SCORE_X,
SCORE_Y + 2,
&format!("Score: {}", tls_get!(score)),
);
reset_colors();
}
const help: [&str; 9] = [
" Use cursor keys",
" or",
" k: rotate",
"h: left, l: right",
" j: drop",
" q: quit",
" c: toggle color",
"n: toggle show next",
"H: toggle this help",
];
tls_init!(help_on, i32, 1);
fn draw_help() {
set_bold();
set_fg(HELP_COLOR);
for (i, &h) in help.iter().enumerate() {
let s = if tls_get!(help_on) == 1 {
h.to_owned()
} else {
" ".repeat(h.len())
};
xyprint(HELP_X, HELP_Y + i as i32, &s);
}
reset_colors();
}
fn toggle_help() {
tls_set!(help_on, |h| *h ^= 1);
draw_help();
}
const piece_data: [&str; 7] = [
"1256", "159d4567", "45120459", "01561548", "159a845601592654", "159804562159a654", "1456159645694159", ];
fn draw_piece(x: i32, y: i32, ctype: i32, rotation: i32, cell: &str) {
for i in 0..4 {
let c = piece_data[ctype as usize]
.chars()
.nth((i + rotation * 4) as usize)
.unwrap()
.to_digit(16)
.unwrap() as i32;
let nx = x + (c & 3) * 2;
let ny = y + (c >> 2);
xyprint(nx, ny, cell);
}
}
tls_init!(next_piece, i32, 0);
tls_init!(next_piece_rotation, i32, 0);
tls_init!(next_piece_color, i32, 0);
tls_init!(next_on, i32, 1);
fn draw_next(visible: i32) {
let mut s = filled_cell.to_string();
if visible == 1 {
set_fg(tls_get!(next_piece_color));
set_bg(tls_get!(next_piece_color));
} else {
s = " ".repeat(s.len());
}
draw_piece(
NEXT_X,
NEXT_Y,
tls_get!(next_piece),
tls_get!(next_piece_rotation),
&s,
);
reset_colors();
}
fn toggle_next() {
tls_set!(next_on, |x| *x ^= 1);
draw_next(tls_get!(next_on));
}
tls_init!(current_piece, i32, 0);
tls_init!(current_piece_x, i32, 0);
tls_init!(current_piece_y, i32, 0);
tls_init!(current_piece_color, i32, 0);
tls_init!(current_piece_rotation, i32, 0);
fn draw_current(cell: &str) {
draw_piece(
tls_get!(current_piece_x) * 2 + PLAYFIELD_X,
tls_get!(current_piece_y) + PLAYFIELD_Y,
tls_get!(current_piece),
tls_get!(current_piece_rotation),
cell,
);
}
fn show_current() {
set_fg(tls_get!(current_piece_color));
set_bg(tls_get!(current_piece_color));
draw_current(filled_cell);
reset_colors();
}
fn clear_current() {
draw_current(empty_cell);
}
fn new_piece_location_ok(x_test: i32, y_test: i32) -> bool {
for i in 0..4 {
let c = piece_data[tls_get!(current_piece) as usize]
.chars()
.nth((i + tls_get!(current_piece_rotation) * 4) as usize)
.unwrap()
.to_digit(16)
.unwrap() as i32;
let y = (c >> 2) + y_test;
let x = (c & 3) + x_test;
if y < 0 || y >= PLAYFIELD_H || x < 0 || x >= PLAYFIELD_W {
return false;
}
if ((tls_get!(playfield)[y as usize] >> (x * 3)) & 7) != 0 {
return false;
}
}
true
}
fn rand() -> i32 {
run_fun!(bash -c r"echo $RANDOM").unwrap().parse().unwrap()
}
fn get_random_next() {
tls_set!(current_piece, |cur| *cur = tls_get!(next_piece));
tls_set!(current_piece_rotation, |cur| *cur =
tls_get!(next_piece_rotation));
tls_set!(current_piece_color, |cur| *cur = tls_get!(next_piece_color));
tls_set!(current_piece_x, |cur| *cur = (PLAYFIELD_W - 4) / 2);
tls_set!(current_piece_y, |cur| *cur = 0);
if !new_piece_location_ok(tls_get!(current_piece_x), tls_get!(current_piece_y)) {
cmd_exit();
}
show_current();
draw_next(0);
tls_set!(next_piece, |nxt| *nxt = rand() % (piece_data.len() as i32));
let rotations = piece_data[tls_get!(next_piece) as usize].len() as i32 / 4;
tls_set!(next_piece_rotation, |nxt| *nxt =
((rand() % rotations) as u8) as i32);
tls_set!(next_piece_color, |nxt| *nxt =
colors[(rand() as usize) % colors.len()]);
draw_next(tls_get!(next_on));
}
fn draw_border() {
set_bold();
set_fg(BORDER_COLOR);
let x1 = PLAYFIELD_X - 2; let x2 = PLAYFIELD_X + PLAYFIELD_W * 2; for i in 0..=PLAYFIELD_H {
let y = i + PLAYFIELD_Y;
xyprint(x1, y, "<|");
xyprint(x2, y, "|>");
}
let y = PLAYFIELD_Y + PLAYFIELD_H;
for i in 0..PLAYFIELD_W {
let x1 = i * 2 + PLAYFIELD_X; xyprint(x1, y, "==");
xyprint(x1, y + 1, "\\/");
}
reset_colors();
}
fn redraw_screen() {
draw_next(1);
update_score(0);
draw_help();
draw_border();
redraw_playfield();
show_current();
}
fn toggle_color() {
tls_set!(use_color, |x| *x = !*x);
redraw_screen();
}
fn init() {
run_cmd!(clear).unwrap();
hide_cursor();
get_random_next();
get_random_next();
redraw_screen();
flush_screen();
}
fn flatten_playfield() {
for i in 0..4 {
let c: i32 = piece_data[tls_get!(current_piece) as usize]
.chars()
.nth((i + tls_get!(current_piece_rotation) * 4) as usize)
.unwrap()
.to_digit(16)
.unwrap() as i32;
let y = (c >> 2) + tls_get!(current_piece_y);
let x = (c & 3) + tls_get!(current_piece_x);
tls_set!(playfield, |f| f[y as usize] |=
tls_get!(current_piece_color) << (x * 3));
}
}
fn line_full(y: i32) -> bool {
let row = tls_get!(playfield)[y as usize];
for x in 0..PLAYFIELD_W {
if ((row >> (x * 3)) & 7) == 0 {
return false;
}
}
true
}
fn process_complete_lines() -> i32 {
let mut complete_lines = 0;
let mut last_idx = PLAYFIELD_H - 1;
for y in (0..PLAYFIELD_H).rev() {
if !line_full(y) {
if last_idx != y {
tls_set!(playfield, |f| f[last_idx as usize] = f[y as usize]);
}
last_idx -= 1;
} else {
complete_lines += 1;
}
}
for y in 0..complete_lines {
tls_set!(playfield, |f| f[y as usize] = 0);
}
complete_lines
}
fn process_fallen_piece() {
flatten_playfield();
let lines = process_complete_lines();
if lines == 0 {
return;
} else {
update_score(lines);
}
redraw_playfield();
}
fn move_piece(nx: i32, ny: i32) -> bool {
if new_piece_location_ok(nx, ny) {
clear_current(); tls_set!(
current_piece_x, |x| *x = nx
);
tls_set!(
current_piece_y, |y| *y = ny
);
show_current(); return true; } if ny == tls_get!(current_piece_y) {
return true; }
process_fallen_piece(); get_random_next(); false
}
fn cmd_right() {
move_piece(tls_get!(current_piece_x) + 1, tls_get!(current_piece_y));
}
fn cmd_left() {
move_piece(tls_get!(current_piece_x) - 1, tls_get!(current_piece_y));
}
fn cmd_rotate() {
let available_rotations = piece_data[tls_get!(current_piece) as usize].len() as i32 / 4;
let old_rotation = tls_get!(current_piece_rotation); let new_rotation = (old_rotation + 1) % available_rotations; tls_set!(current_piece_rotation, |r| *r = new_rotation); if new_piece_location_ok(
tls_get!(current_piece_x), tls_get!(current_piece_y),
) {
tls_set!(current_piece_rotation, |r| *r = old_rotation); clear_current(); tls_set!(current_piece_rotation, |r| *r = new_rotation); show_current(); } else {
tls_set!(current_piece_rotation, |r| *r = old_rotation); }
}
fn cmd_down() {
move_piece(tls_get!(current_piece_x), tls_get!(current_piece_y) + 1);
}
fn cmd_drop() {
loop {
if !move_piece(tls_get!(current_piece_x), tls_get!(current_piece_y) + 1) {
break;
}
}
}
tls_init!(old_stty_cfg, String, String::new());
fn cmd_exit() {
xyprint(GAMEOVER_X, GAMEOVER_Y, "Game over!");
xyprint(GAMEOVER_X, GAMEOVER_Y + 1, ""); flush_screen(); show_cursor();
let stty_g = tls_get!(old_stty_cfg);
run_cmd!(stty $stty_g).unwrap(); std::process::exit(0);
}
#[cmd_lib::main]
fn main() -> CmdResult {
#[rustfmt::skip]
let old_cfg = run_fun!(stty -g)?; tls_set!(old_stty_cfg, |cfg| *cfg = old_cfg);
run_cmd!(stty raw -echo -isig -icanon min 0 time 0)?;
init();
let mut tick = 0;
loop {
let mut buffer = String::new();
if std::io::stdin().read_to_string(&mut buffer).is_ok() {
match buffer.as_str() {
"q" | "\u{1b}" | "\u{3}" => cmd_exit(), "h" | "\u{1b}[D" => cmd_left(),
"l" | "\u{1b}[C" => cmd_right(),
"j" | "\u{1b}[B" => cmd_drop(),
"k" | "\u{1b}[A" => cmd_rotate(),
"H" => toggle_help(),
"n" => toggle_next(),
"c" => toggle_color(),
_ => (),
}
}
tick += 1;
if tick >= (600.0 * tls_get!(DELAY)) as i32 {
tick = 0;
cmd_down();
}
flush_screen();
thread::sleep(time::Duration::from_millis(1));
}
}