Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
GKaszewski committed Sep 20, 2023
1 parent 4ea4f75 commit 0a7e81c
Show file tree
Hide file tree
Showing 3 changed files with 390 additions and 0 deletions.
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# tetris-rs
My implementation of Tetris in Rust using Raylib.

## How to play
- `` and `` to move left and right
- `` to rotate
- `` to drop
- `space` to hard drop
- press `p` to pause

## How to build
- Install Rust
- `cargo build --release`
- `./target/release/tetris-rs`
- Enjoy!


## License
MIT
296 changes: 296 additions & 0 deletions src/game.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
use rand::Rng;

pub struct GameState {
pub board: Vec<Vec<u8>>,
pub current_piece: Piece,
pub next_piece: Piece,
pub score: i32,
pub is_paused: bool,
pub is_game_over: bool,
}

#[derive(Clone, Debug)]
pub struct Piece {
pub shape: Vec<Vec<u8>>,
pub x: i32,
pub y: i32,
}

pub enum PieceType {
I,
O,
T,
S,
Z,
J,
L,
}

pub enum GameStateEvent {
LinesCleared(usize),
GameOver(i32),
Continue,
}

impl GameState {
pub fn new() -> Self {
Self {
board: vec![vec![0; 10]; 20],
current_piece: Self::generate_random_piece(),
next_piece: Self::generate_random_piece(),
score: 0,
is_paused: false,
is_game_over: false,
}
}

pub fn update(&mut self) -> Vec<GameStateEvent> {
let mut events = Vec::new();

if self.is_paused {
return events;
}

self.current_piece.y += 1;

if self.is_collision() {
self.current_piece.y -= 1;
self.merge_piece();

let lines_cleared = self.clear_lines();
self.score += lines_cleared as i32 * 100;
if lines_cleared > 0 {
events.push(GameStateEvent::LinesCleared(lines_cleared));
}

self.current_piece = self.next_piece.clone();
self.next_piece = Self::generate_random_piece();

if self.is_collision() {
events.push(GameStateEvent::GameOver(self.score));
self.is_game_over = true;
}
}

if events.is_empty() {
events.push(GameStateEvent::Continue);
}

events
}

pub fn restart(&mut self) {
self.board = vec![vec![0; 10]; 20];
self.current_piece = Self::generate_random_piece();
self.next_piece = Self::generate_random_piece();
self.score = 0;
self.is_paused = false;
self.is_game_over = false;
}

fn is_collision(&self) -> bool {
for (y, row) in self.current_piece.shape.iter().enumerate() {
for (x, &value) in row.iter().enumerate() {
if value == 0 {
continue;
}

let board_x = self.current_piece.x + x as i32;
let board_y = self.current_piece.y + y as i32;

if board_x < 0 || board_x >= self.board[0].len() as i32 || board_y >= self.board.len() as i32 {
return true;
}

if board_y >= 0 && self.board[board_y as usize][board_x as usize] != 0 {
return true;
}
}
}

false
}

fn clear_lines(&mut self) -> usize {
let mut lines_to_clear = Vec::new();

for (y, row) in self.board.iter().enumerate() {
if row.iter().all(|&cell| cell != 0) {
lines_to_clear.push(y);
}
}

for &line in lines_to_clear.iter().rev() {
self.board.remove(line);
self.board.insert(0, vec![0; 10]);
}

lines_to_clear.len()
}

fn generate_random_piece() -> Piece {
let mut rng = rand::thread_rng();
let piece_type = match rng.gen_range(0..=6) {
0 => PieceType::I,
1 => PieceType::O,
2 => PieceType::T,
3 => PieceType::S,
4 => PieceType::Z,
5 => PieceType::J,
6 => PieceType::L,
_ => unreachable!(),
};

match piece_type {
PieceType::I => Piece {
shape: vec![
vec![0, 0, 0, 0],
vec![1, 1, 1, 1],
vec![0, 0, 0, 0],
vec![0, 0, 0, 0],
],
x: 4,
y: 0,
},
PieceType::O => Piece {
shape: vec![
vec![2, 2],
vec![2, 2],
],
x: 4,
y: 0,
},
PieceType::T => Piece {
shape: vec![
vec![0, 3, 0],
vec![3, 3, 3],
vec![0, 0, 0],
],
x: 4,
y: 0,
},
PieceType::S => Piece {
shape: vec![
vec![0, 4, 4],
vec![4, 4, 0],
vec![0, 0, 0],
],
x: 4,
y: 0,
},
PieceType::Z => Piece {
shape: vec![
vec![5, 5, 0],
vec![0, 5, 5],
vec![0, 0, 0],
],
x: 4,
y: 0,
},
PieceType::J => Piece {
shape: vec![
vec![6, 0, 0],
vec![6, 6, 6],
vec![0, 0, 0],
],
x: 4,
y: 0,
},
PieceType::L => Piece {
shape: vec![
vec![0, 0, 7],
vec![7, 7, 7],
vec![0, 0, 0],
],
x: 4,
y: 0,
},
}
}

fn merge_piece(&mut self) {
for (y, row) in self.current_piece.shape.iter().enumerate() {
for (x, &value) in row.iter().enumerate() {
if value == 0 {
continue;
}

let board_x = self.current_piece.x + x as i32;
let board_y = self.current_piece.y + y as i32;

if board_x >= 0 && board_x < self.board[0].len() as i32 &&
board_y >= 0 && board_y < self.board.len() as i32 {
self.board[board_y as usize][board_x as usize] = value;
}
}
}
}

pub fn move_piece(&mut self, dx: i32, dy: i32) {
self.current_piece.x += dx;
self.current_piece.y += dy;

if self.is_collision() {
self.current_piece.x -= dx;
self.current_piece.y -= dy;
}
}

pub fn hard_drop(&mut self) {
while !self.is_collision() {
self.current_piece.y += 1;
}

self.current_piece.y -= 1;
self.merge_piece();

let lines_cleared = self.clear_lines();
self.score += lines_cleared as i32 * 100;

self.current_piece = self.next_piece.clone();
self.next_piece = Self::generate_random_piece();
}

pub fn rotate_piece(&mut self) {
let mut new_shape: Vec<Vec<u8>> = vec![vec![0; self.current_piece.shape.len()]; self.current_piece.shape[0].len()];
for y in 0..self.current_piece.shape.len() {
for x in 0..self.current_piece.shape[y].len() {
new_shape[x][y] = self.current_piece.shape[y][x];
}
}

for row in new_shape.iter_mut() {
row.reverse();
}

let mut collision_detected = false;
for (y, row) in new_shape.iter().enumerate() {
for (x, &value) in row.iter().enumerate() {
if value == 0 {
continue;
}

let board_x = self.current_piece.x + x as i32;
let board_y = self.current_piece.y + y as i32;

if board_x < 0 || board_x >= self.board[0].len() as i32 || board_y >= self.board.len() as i32 {
collision_detected = true;
break;
}

if board_y >= 0 && self.board[board_y as usize][board_x as usize] != 0 {
collision_detected = true;
break;
}
}
}

if !collision_detected {
self.current_piece.shape = new_shape;
}
}

pub fn toggle_pause(&mut self) {
self.is_paused = !self.is_paused;
}
}
75 changes: 75 additions & 0 deletions src/renderer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use raylib::color::Color;
use raylib::drawing::{RaylibDraw, RaylibDrawHandle};
use crate::game::GameState;

const BLOCK_SIZE: i32 = 20;

fn draw_board(game_state: &GameState, d: &mut RaylibDrawHandle) {
for y in 0..game_state.board.len() {
d.draw_line(0, y as i32 * BLOCK_SIZE, 10 * BLOCK_SIZE, y as i32 * BLOCK_SIZE, Color::DARKGRAY);
for x in 0..game_state.board[0].len() {
d.draw_line(x as i32 * BLOCK_SIZE, 0, x as i32 * BLOCK_SIZE, 20 * BLOCK_SIZE, Color::DARKGRAY);
}
}
}

fn draw_locked_pieces(game_state: &GameState, d: &mut RaylibDrawHandle) {
for (y, row) in game_state.board.iter().enumerate() {
for (x, &cell) in row.iter().enumerate() {
if cell != 0 {
let x_pos = x as i32 * BLOCK_SIZE;
let y_pos = y as i32 * BLOCK_SIZE;
d.draw_rectangle(x_pos, y_pos, BLOCK_SIZE, BLOCK_SIZE, Color::RED);
}
}
}
}

pub fn draw(game_state: &GameState, d: &mut RaylibDrawHandle) {
draw_board(game_state, d);

if !game_state.is_game_over {
draw_locked_pieces(game_state, d);
draw_current_piece(game_state, d);
draw_next_piece(game_state, d);
}

draw_ui(&game_state, d);
}

fn draw_ui(game_state: &&GameState, d: &mut RaylibDrawHandle) {
d.draw_text("Next piece:", 12 * BLOCK_SIZE, 0, 20, Color::WHITE);
d.draw_text(&format!("Score: {}", game_state.score), 12 * BLOCK_SIZE, 5 * BLOCK_SIZE, 20, Color::WHITE);

if game_state.is_paused {
d.draw_text("Paused", 12 * BLOCK_SIZE, 10 * BLOCK_SIZE, 20, Color::WHITE);
}

if game_state.is_game_over {
d.draw_text("Game Over", 12 * BLOCK_SIZE, 10 * BLOCK_SIZE, 20, Color::WHITE);
}
}

fn draw_next_piece(game_state: &GameState, d: &mut RaylibDrawHandle) {
for (y, row) in game_state.next_piece.shape.iter().enumerate() {
for (x, &cell) in row.iter().enumerate() {
if cell != 0 {
let x_pos = (x as i32 + 12) * BLOCK_SIZE;
let y_pos = (y as i32 + 1) * BLOCK_SIZE;
d.draw_rectangle(x_pos, y_pos, BLOCK_SIZE, BLOCK_SIZE, Color::DARKGREEN);
}
}
}
}

fn draw_current_piece(game_state: &GameState, d: &mut RaylibDrawHandle) {
for (y, row) in game_state.current_piece.shape.iter().enumerate() {
for (x, &cell) in row.iter().enumerate() {
if cell != 0 {
let x_pos = (x as i32 + game_state.current_piece.x) * BLOCK_SIZE;
let y_pos = (y as i32 + game_state.current_piece.y) * BLOCK_SIZE;
d.draw_rectangle(x_pos, y_pos, BLOCK_SIZE, BLOCK_SIZE, Color::GREEN);
}
}
}
}

0 comments on commit 0a7e81c

Please sign in to comment.