Skip to content

Instantly share code, notes, and snippets.

@tylorr
Created September 7, 2017 05:29
Show Gist options
  • Save tylorr/639d904cd1ba715672406c1f2e378bcf to your computer and use it in GitHub Desktop.
Save tylorr/639d904cd1ba715672406c1f2e378bcf to your computer and use it in GitHub Desktop.
use std::io;
use std::fmt;
extern crate ndarray;
use ndarray::{ArrayView2, aview2};
use Block::*;
static I_BLOCKS: [[[Block; 4]; 4]; 4] = [
[[E, E, E, E],
[C, C, C, C],
[E, E, E, E],
[E, E, E, E]],
[[E, E, C, E],
[E, E, C, E],
[E, E, C, E],
[E, E, C, E]],
[[E, E, E, E],
[E, E, E, E],
[C, C, C, C],
[E, E, E, E]],
[[E, C, E, E],
[E, C, E, E],
[E, C, E, E],
[E, C, E, E]],
];
static O_BLOCK: [[Block; 2]; 2] = [[Y, Y], [Y, Y]];
static Z_BLOCKS: [[[Block; 3]; 3]; 4] = [
[[R, R, E],
[E, R, R],
[E, E, E]],
[[E, E, R],
[E, R, R],
[E, R, E]],
[[E, E, E],
[R, R, E],
[E, R, R]],
[[E, R, E],
[R, R, E],
[R, E, E]]
];
static S_BLOCKS: [[[Block; 3]; 3]; 4] = [
[[E, G, G],
[G, G, E],
[E, E, E]],
[[E, G, E],
[E, G, G],
[E, E, G]],
[[E, E, E],
[E, G, G],
[G, G, E]],
[[G, E, E],
[G, G, E],
[E, G, E]]
];
static J_BLOCKS: [[[Block; 3]; 3]; 4] = [
[[B, E, E],
[B, B, B],
[E, E, E]],
[[E, B, B],
[E, B, E],
[E, B, E]],
[[E, E, E],
[B, B, B],
[E, E, B]],
[[E, B, E],
[E, B, E],
[B, B, E]]
];
static L_BLOCKS: [[[Block; 3]; 3]; 4] = [
[[E, E, O],
[O, O, O],
[E, E, E]],
[[E, O, E],
[E, O, E],
[E, O, O]],
[[E, E, E],
[O, O, O],
[O, E, E]],
[[O, O, E],
[E, O, E],
[E, O, E]]
];
static T_BLOCKS: [[[Block; 3]; 3]; 4] = [
[[E, M, E],
[M, M, M],
[E, E, E]],
[[E, M, E],
[E, M, M],
[E, M, E]],
[[E, E, E],
[M, M, M],
[E, M, E]],
[[E, M, E],
[M, M, E],
[E, M, E]]
];
#[derive(Copy,Clone,PartialEq)]
enum Block {
E,
M,
C,
B,
R,
G,
Y,
O,
}
#[derive(Copy,Clone,PartialEq)]
enum Tetramino {
T,
I,
J,
Z,
S,
O,
L,
}
enum Screen {
Title,
Pause,
Game,
}
struct State {
screen: Screen,
board: [[Block; 10]; 22],
score: i32,
cleared_lines: i32,
active_tetramino: Option<Tetramino>,
rotation: usize,
position: (i32, i32),
game_over: bool,
}
impl fmt::Display for Block {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let c = match *self {
E => '.',
M => 'm',
C => 'c',
B => 'b',
R => 'r',
G => 'g',
Y => 'y',
O => 'o',
};
write!(f, "{}", c)
}
}
fn main() {
let mut state = State {
screen: Screen::Game,
board: [[E; 10]; 22],
score: 0,
cleared_lines: 0,
active_tetramino: None,
rotation: 0,
position: (0, 0),
game_over: false,
};
'game: loop {
let mut command = String::new();
io::stdin().read_line(&mut command)
.expect("Failed to read line");
let mut chars = command.chars();
while let Some(c) = chars.next() {
match c {
'q' => break 'game,
' ' | '\n' | '\t' => continue,
_ => {}
}
match state.screen {
Screen::Game => handle_game_input(c, &mut chars, &mut state),
Screen::Title => handle_title_input(c, &mut state),
Screen::Pause => handle_pause_input(c, &mut state),
}
}
}
}
fn handle_game_input(c: char, chars: &mut std::str::Chars, state: &mut State) {
match c {
'@' => state.screen = Screen::Title,
'!' => state.screen = Screen::Pause,
'c' => clear_board(state),
'p' => print_board(state),
'P' => print_active_board(state),
'g' => state.board = read_board(),
's' => step(state),
't' => display_active_tetramino(state),
')' => rotate_right(state),
'(' => rotate_left(state),
';' => println!(),
'<' => shift_left(state),
'>' => shift_right(state),
'v' => shif_down(state),
'V' => drop(state),
'?' =>
match chars.next() {
Some('s') => println!("{}", state.score),
Some('n') => println!("{}", state.cleared_lines),
_ => println!("Invalid query")
},
'T' => set_active_tetramino(state, Tetramino::T),
'I' => set_active_tetramino(state, Tetramino::I),
'J' => set_active_tetramino(state, Tetramino::J),
'Z' => set_active_tetramino(state, Tetramino::Z),
'S' => set_active_tetramino(state, Tetramino::S),
'O' => set_active_tetramino(state, Tetramino::O),
'L' => set_active_tetramino(state, Tetramino::L),
_ => println!("Invalid game command")
}
}
fn handle_title_input(c: char, state: &mut State) {
match c {
'!' => state.screen = Screen::Game,
'p' => println!("Learntris (c) 1992 Tetraminex, Inc.\nPress start button to begin."),
_ => println!("Invalid title command")
}
}
fn handle_pause_input(c: char, state: &mut State) {
match c {
'!' => state.screen = Screen::Game,
'p' => println!("Paused\nPress start button to continue."),
_ => println!("Invalid pause command")
}
}
fn print_active_board(state: &State) {
let tview = get_tetramino_view(state.active_tetramino, state.rotation);
if let Some(v) = tview {
for (r, row) in state.board.iter().enumerate() {
for (c, board_block) in row.iter().enumerate() {
let local_x = (c as i32) - state.position.0;
let local_y = (r as i32) - state.position.1;
if local_x >= 0 && local_x < v.cols() as i32 && local_y >= 0 && local_y < v.rows() as i32 {
let tetramino_block = v[[local_y as usize, local_x as usize]];
if tetramino_block != E {
print!("{} ", tetramino_block.to_string().to_uppercase());
} else {
print!("{} ", board_block);
}
} else {
print!("{} ", board_block);
}
}
println!();
}
if state.game_over {
println!("Game Over");
}
} else {
print_board(state);
}
}
fn print_board(state: &State) {
for row in &state.board {
for block in row {
print!("{} ", block);
}
println!();
}
if state.game_over {
println!("Game Over");
}
}
fn read_board() -> [[Block; 10]; 22] {
let mut board = [[E; 10]; 22];
for row in 0..22 {
let mut row_string = String::new();
io::stdin().read_line(&mut row_string)
.expect("Failed to read row");
let mut col = 0;
for c in row_string.chars() {
let block = match c {
'.' => E,
'm' => M,
'c' => C,
'b' => B,
'r' => R,
'g' => G,
'y' => Y,
'o' => O,
_ => continue,
};
board[row][col] = block;
col += 1;
}
}
board
}
fn clear_board(state: &mut State) {
state.board = [[E; 10]; 22];
state.game_over = false;
}
fn step(state: &mut State) {
for row in &mut state.board {
if !row.iter().any(|&x| x == E) {
state.score += 100;
state.cleared_lines += 1;
for block in row.iter_mut() {
*block = E
}
}
}
}
fn display_active_tetramino(state: &State) {
let view = get_tetramino_view(state.active_tetramino, state.rotation);
if let Some(v) = view {
for row in v.genrows() {
for block in row {
print!("{} ", block);
}
println!();
}
}
}
fn set_active_tetramino(state: &mut State, t: Tetramino) {
state.active_tetramino = Some(t);
state.rotation = 0;
state.position = match t {
Tetramino::O => (4, 0),
_ => (3, 0)
};
}
fn rotate_right(state: &mut State) {
attempt_rotation((state.rotation + 1) % 4, state);
}
fn rotate_left(state: &mut State) {
attempt_rotation((state.rotation + 4 - 1) % 4, state);
}
fn attempt_rotation(rotation: usize, state: &mut State) {
if let Some(tetramino) = state.active_tetramino {
let kick_offsets = if tetramino == Tetramino::I {
match (state.rotation, rotation) {
(0, 1) | (3, 2) => [( 0, 0), (-2, 0), ( 1, 0), (-2, 1), ( 1,-2)],
(1, 0) | (2, 3) => [( 0, 0), ( 2, 0), (-1, 0), ( 2,-1), (-1, 2)],
(1, 2) | (0, 3) => [( 0, 0), (-1, 0), ( 2, 0), (-1,-2), ( 2, 1)],
(2, 1) | (3, 0) => [( 0, 0), ( 1, 0), (-2, 0), ( 1, 2), (-2,-1)],
_ => panic!("Impossible rotation")
}
} else {
match (state.rotation, rotation) {
(0, 1) | (2, 1) => [( 0, 0), (-1, 0), (-1,-1), ( 0, 2), (-1, 2)],
(1, 0) | (1, 2) => [( 0, 0), ( 1, 0), ( 1, 1), ( 0,-2), ( 1,-2)],
(0, 3) | (2, 3) => [( 0, 0), ( 1, 0), ( 1,-1), ( 0, 2), ( 1, 2)],
(3, 0) | (3, 2) => [( 0, 0), (-1, 0), (-1, 1), ( 0,-2), (-1,-2)],
_ => panic!("Impossible rotation")
}
};
// check for collision after rotation and all possible wallkick locations
for offset in kick_offsets.iter() {
let position = (state.position.0 + offset.0, state.position.1 + offset.1);
if check_collision(position, rotation, state) {
state.rotation = rotation;
state.position = position;
break;
}
}
}
}
fn shift_right(state: &mut State) {
let mut position = state.position.clone();
position.0 = position.0 + 1;
if check_collision(position, state.rotation, state) {
state.position = position;
}
}
fn shift_left(state: &mut State) {
let mut position = state.position.clone();
position.0 = position.0 - 1;
if check_collision(position, state.rotation, state) {
state.position = position;
}
}
fn shif_down(state: &mut State) {
let mut position = state.position.clone();
position.1 = position.1 + 1;
if check_collision(position, state.rotation, state) {
state.position = position;
} else if state.position.1 == 0 {
state.game_over = true;
}
}
fn drop(state: &mut State) {
let mut position = state.position.clone();
position.1 = position.1 + 1;
while check_collision(position, state.rotation, state) {
state.position = position;
position.1 = position.1 + 1;
}
if state.position.1 == 0 {
state.game_over = true;
}
commit_active_tetramino(state);
}
fn commit_active_tetramino(state: &mut State) {
let tview = get_tetramino_view(state.active_tetramino, state.rotation);
if let Some(v) = tview {
for ((r, c), elt) in v.indexed_iter() {
if *elt != E {
let abs_x = state.position.0 as usize + c;
let abs_y = state.position.1 as usize + r;
state.board[abs_y][abs_x] = *elt;
}
}
state.active_tetramino = None;
}
}
fn check_collision(position: (i32, i32), rotation: usize, state: &State) -> bool {
let tview = get_tetramino_view(state.active_tetramino, rotation);
if let Some(v) = tview {
for ((r, c), elt) in v.indexed_iter() {
if *elt != E {
let abs_x = position.0 + c as i32;
let abs_y = position.1 + r as i32;
if abs_x < 0 || abs_x >= 10 || abs_y < 0 || abs_y >= 22 {
return false;
}
if state.board[abs_y as usize][abs_x as usize] != E {
return false;
}
}
}
return true;
}
return false;
}
fn get_tetramino_view(tetramino: Option<Tetramino>, rotation: usize) -> Option<ArrayView2<'static, Block>> {
tetramino.map(|t| match t {
Tetramino::I => aview2(&I_BLOCKS[rotation]),
Tetramino::O => aview2(&O_BLOCK),
Tetramino::Z => aview2(&Z_BLOCKS[rotation]),
Tetramino::S => aview2(&S_BLOCKS[rotation]),
Tetramino::J => aview2(&J_BLOCKS[rotation]),
Tetramino::L => aview2(&L_BLOCKS[rotation]),
Tetramino::T => aview2(&T_BLOCKS[rotation]),
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment