From 5c6fa2c32a0449727ac5c8e7a9184490663dcbdf Mon Sep 17 00:00:00 2001 From: etoledom Date: Sun, 30 Jun 2019 10:24:05 +0200 Subject: [PATCH 1/5] Add `get_score` public method --- src/game.rs | 91 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 53 insertions(+), 38 deletions(-) diff --git a/src/game.rs b/src/game.rs index 3d02273..642393e 100644 --- a/src/game.rs +++ b/src/game.rs @@ -15,7 +15,7 @@ pub enum GameState { pub struct Game { board: Board, - points: u128, + score: u64, active: ActiveFigure, next: ActiveFigure, waiting_time: f64, @@ -32,7 +32,7 @@ impl Game { let board = Board::new(size); return Game { board, - points: 0, + score: 0, active, next, waiting_time: 0.0, @@ -213,12 +213,16 @@ impl Game { // Score fn add_score_for(&mut self, completed_lines: usize) { - self.points += (completed_lines as u128) * 100; + self.score += (completed_lines as u64) * 100; } fn check_is_game_over(&self) -> bool { return self.active.position().y == 0 && !has_valid_position(&self.active, &self.board); } + + pub fn get_score(&self) -> u64 { + return self.score; + } } #[cfg(test)] @@ -275,7 +279,7 @@ mod game_tests { fn test_rotate_active_figure() { let mut game = get_game(); let rotated = game.active.rotated(); - game.rotate_active_figure(); + game.rotate(); let drawed_points = draw_to_cartesian(game.draw()); assert_eq!(drawed_points, rotated.to_cartesian()); } @@ -326,40 +330,24 @@ mod game_tests { } #[test] fn test_active_figure_is_added_when_it_touches_the_floor() { - let mut game = Game::new( - &Size { - height: 4, - width: 10, - }, - get_randomizer(5), - ); + let mut game = get_game_with_size(4, 10); + assert_eq!(game.active.position().y, 0); // lowest figure block is at y: 1 assert!(game.draw_board().is_empty()); - game.update(10.0); // y: 2 - game.update(10.0); // y: 3 - game.update(10.0); // -> Should add figure to board and create new active + + update_loops(&mut game, 3); // Should add figure to board and create new active assert_eq!(game.active.position().y, 0); assert_eq!(game.draw_board().len(), 4); } #[test] fn test_active_figure_is_added_when_touches_block() { - let mut game = Game::new( - &Size { - height: 7, - width: 10, - }, - Box::new(Random { number: 5 }), - ); + let mut game = get_game_with_size(7, 10); game.active = ActiveFigure::new(FigureType::L, Point { x: 5, y: 5 }); game.update(10.0); // current figure should be added to the board assert_eq!(game.draw_board().len(), 4); // Next figure should colide at y: 5 - game.update(10.0); // y: 1 - game.update(10.0); // y: 2 - game.update(10.0); // y: 3 - game.update(10.0); // y: 4 - game.update(10.0); // y: 5 + update_loops(&mut game, 5); // Takes y from 1 to 5 assert_eq!(game.active.position().y, 0); assert_eq!(game.draw_board().len(), 8); @@ -387,13 +375,7 @@ mod game_tests { } #[test] fn test_is_game_over() { - let mut game = Game::new( - &Size { - height: 6, - width: 10, - }, - get_randomizer(5), - ); + let mut game = get_game_with_size(6, 10); game.board = game.board.replacing_figure_at_xy(3, 1, Some(FigureType::L)); game.board = game.board.replacing_figure_at_xy(4, 1, Some(FigureType::L)); game.board = game.board.replacing_figure_at_xy(5, 1, Some(FigureType::L)); @@ -403,20 +385,53 @@ mod game_tests { #[test] fn test_is_game_over_returns_false() { let mut game = get_game(); - game.update(10.0); + update_loops(&mut game, 1); assert!(!game.is_game_over()); } + #[test] + fn test_get_score() { + let mut game = get_game_with_size(2, 2); + assert_eq!(game.get_score(), 0); + + // Completing line + game.board.replacing_figure_at_xy(0, 1, Some(FigureType::I)); + game.board.replacing_figure_at_xy(1, 1, Some(FigureType::I)); + game.update(10.0); + + assert_eq!(game.get_score(), 100); + } + #[test] + fn test_double_line_score() { + let mut game = get_game_with_size(2, 2); + assert_eq!(game.get_score(), 0); + + game.active = ActiveFigure::new(FigureType::O, Point{ x: 0, y: 0}); + game.update(10.0); + + assert_eq!(game.get_score(), 200); + } + + // HELPERS + fn draw_to_cartesian(draw: Vec) -> Vec { return draw.iter().map(|block| block.position()).collect(); } fn get_game() -> Game { + return get_game_with_size(40, 20); + } + fn get_game_with_size(height: usize, width: usize) -> Game { let size = Size { - height: 40, - width: 20, + height, + width, }; - return Game::new(&size, Box::new(Random { number: 5 })); + return Game::new(&size, get_randomizer()); } - fn get_randomizer(_number: i32) -> Box { + fn get_randomizer() -> Box { return Box::new(Random { number: 5 }); } + fn update_loops(game: &mut Game, update_times: u32) { + for _ in 0..update_times { + game.update(10.0); + } + } } From 28b23f561c25df141f6227ba1ec3189a07e0985f Mon Sep 17 00:00:00 2001 From: etoledom Date: Sun, 30 Jun 2019 10:51:50 +0200 Subject: [PATCH 2/5] New perform action public method --- src/figure/utilities/direction.rs | 29 ---------------- src/game.rs | 56 +++++++++++++++++++------------ 2 files changed, 34 insertions(+), 51 deletions(-) delete mode 100644 src/figure/utilities/direction.rs diff --git a/src/figure/utilities/direction.rs b/src/figure/utilities/direction.rs deleted file mode 100644 index f003841..0000000 --- a/src/figure/utilities/direction.rs +++ /dev/null @@ -1,29 +0,0 @@ -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum Direction { - Up, - Down, - Left, - Right, -} - -impl Direction { - pub fn opposite(&self) -> Direction { - match self { - Direction::Up => Direction::Down, - Direction::Down => Direction::Up, - Direction::Left => Direction::Right, - Direction::Right => Direction::Left, - } - } -} - -#[cfg(test)] -mod geometry_tests { - #[test] - fn test_direction_opposite() { - assert_eq!(super::Direction::Up.opposite(), super::Direction::Down); - assert_eq!(super::Direction::Down.opposite(), super::Direction::Up); - assert_eq!(super::Direction::Left.opposite(), super::Direction::Right); - assert_eq!(super::Direction::Right.opposite(), super::Direction::Left); - } -} diff --git a/src/game.rs b/src/game.rs index 642393e..690dec4 100644 --- a/src/game.rs +++ b/src/game.rs @@ -3,6 +3,13 @@ use super::{ActiveFigure, Block, Board, FigureType, Point, Size}; const MOVING_PERIOD: f64 = 0.2; //secs +pub enum Action { + MoveDown, + MoveLeft, + MoveRight, + Rotate, +} + pub trait Randomizer { fn random_between(&self, first: i32, last: i32) -> i32; } @@ -129,19 +136,24 @@ impl Game { // MOVEMENT FUNCTIONS - pub fn rotate(&mut self) { - self.rotate_active_figure(); + pub fn perform(&mut self, action: Action) { + match action { + Action::MoveLeft => self.move_left(), + Action::MoveRight => self.move_right(), + Action::MoveDown => self.move_down(), + Action::Rotate => self.rotate_active_figure(), + } } - pub fn move_left(&mut self) { + fn move_left(&mut self) { self.update_active_with(self.active.moved_left()); } - pub fn move_right(&mut self) { + fn move_right(&mut self) { self.update_active_with(self.active.moved_right()); } - pub fn move_down(&mut self) { + fn move_down(&mut self) { self.update_active_with(self.active.moved_down()); } @@ -258,7 +270,7 @@ mod game_tests { y: point.y + 1, }) .collect(); - game.move_down(); + game.perform(Action::MoveDown); let drawed_points = draw_to_cartesian(game.draw()); assert_eq!(drawed_points, expected); @@ -268,18 +280,18 @@ mod game_tests { let mut game = get_game(); let y = game.board.height() as i32 - 3; // 3 spaces before the floor game.active = ActiveFigure::new(FigureType::O, Point { x: 10, y }); - game.move_down(); - game.move_down(); - game.move_down(); + game.perform(Action::MoveDown); + game.perform(Action::MoveDown); + game.perform(Action::MoveDown); assert_eq!(game.active.bottom_edge(), 39); - game.move_down(); + game.perform(Action::MoveDown); assert_eq!(game.active.bottom_edge(), 39); } #[test] fn test_rotate_active_figure() { let mut game = get_game(); let rotated = game.active.rotated(); - game.rotate(); + game.perform(Action::Rotate); let drawed_points = draw_to_cartesian(game.draw()); assert_eq!(drawed_points, rotated.to_cartesian()); } @@ -289,7 +301,7 @@ mod game_tests { let mut game = get_game(); game.active = ActiveFigure::new(FigureType::L, Point { x: 10, y: 0 }); assert_eq!(game.active.left_edge(), 10); - game.move_left(); + game.perform(Action::MoveLeft); assert_eq!(game.active.left_edge(), 9); } #[test] @@ -298,17 +310,17 @@ mod game_tests { game.active = ActiveFigure::new(FigureType::L, Point { x: 2, y: 0 }); game.active = game.active.rotated(); // left edge is now at x: 3 assert_eq!(game.active.left_edge(), 3); - game.move_left(); // x: 2 - game.move_left(); // x: 1 - game.move_left(); // x: 0 - game.move_left(); // x: 0 + game.perform(Action::MoveLeft); // x: 2 + game.perform(Action::MoveLeft); // x: 1 + game.perform(Action::MoveLeft); // x: 0 + game.perform(Action::MoveLeft); // x: 0 assert_eq!(game.active.left_edge(), 0); } #[test] fn test_move_right() { let mut game = get_game(); game.active = ActiveFigure::new(FigureType::L, Point { x: 0, y: 0 }); - game.move_right(); + game.perform(Action::MoveRight); assert_eq!(game.active.position(), Point { x: 1, y: 0 }); } #[test] @@ -317,8 +329,8 @@ mod game_tests { game.active = ActiveFigure::new(FigureType::I, Point { x: 16, y: 0 }); game.active = game.active.rotated(); // right edge is now at 18 assert_eq!(game.active.left_edge(), 18); - game.move_right(); // x: 19 - game.move_right(); // x: 19 + game.perform(Action::MoveRight); // x: 19 + game.perform(Action::MoveRight); // x: 19 assert_eq!(game.active.right_edge(), 19); } #[test] @@ -368,9 +380,9 @@ mod game_tests { fn test_wallkick_l_left() { let mut game = get_game(); game.active = ActiveFigure::new(FigureType::L, Point { x: 0, y: 5 }); - game.rotate(); - game.move_left(); - game.rotate(); + game.perform(Action::Rotate); + game.perform(Action::MoveLeft); + game.perform(Action::Rotate); assert_eq!(game.active.position().x, 0); } #[test] From c5c6ad4c710d357965146dba887f8aeaeedb2188 Mon Sep 17 00:00:00 2001 From: etoledom Date: Sun, 30 Jun 2019 11:04:30 +0200 Subject: [PATCH 3/5] Make Action public --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 925d082..685b6d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,5 +11,5 @@ use geometry::Point; use graphics::Color; pub use block::Block; -pub use game::{Game, Randomizer}; +pub use game::{Game, Randomizer, Action}; pub use geometry::Size; From e3ffe86372d4b0e5042425351318dbdc9b2f95ee Mon Sep 17 00:00:00 2001 From: etoledom Date: Sun, 30 Jun 2019 11:07:33 +0200 Subject: [PATCH 4/5] Update version to 0.2.0 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9f41e6a..ffca2b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,5 +2,5 @@ # It is not intended for manual editing. [[package]] name = "tetris_core" -version = "0.1.0" +version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 7b0499e..6ef2d40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tetris_core" -version = "0.1.0" +version = "0.2.0" authors = ["etoledom "] edition = "2018" description = "Simple Tetris game model with no UI or Game engine" From 94c88776623265e05840b95838155f717dc89a51 Mon Sep 17 00:00:00 2001 From: etoledom Date: Sun, 30 Jun 2019 11:37:07 +0200 Subject: [PATCH 5/5] Update README.md --- README.md | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 90077f1..dda159c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,63 @@ # tetris_core -Simple Tetris game engine (with no interface) +Simple Tetris game logic (with no interface) -WARNING: +**WARNING:** This project was created on an early stage of learning RUST. It might lack many good practices. + +## Usage: +- Use any drawing library. (i.e: [piston_window](https://crates.io/crates/piston_window)) +- Use any randomizer library or method (i.e: [rand](https://crates.io/crates/rand)) + +As an example of implementation, you can check https://github.com/etoledom/rust_practice/blob/master/07_tetris/src/main.rs + +Implement the `Randomizer` trait +```rust +struct Rand; +impl Randomizer for Rand { + fn random_between(&self, lower: i32, higher: i32) -> i32 { + let mut rng = rand::thread_rng(); + return rng.gen_range(lower, higher); + } +} +``` + +Instantiate a Tetris Game instance using an instance or your randomizer struct and the desired board size: +```rust +let game_size = Size { + height: 20, + width: 10, +}; +let mut game = Game::new(&game_size, Box::new(rand)); +``` + +#### `update(&mut self, delta_time: f64)` + +Call `game.update(delta_time);` on every game loop. + +#### `draw(&self) -> Vec` + +Get the board model to be drawn: +```rust +let game_blocks = game.draw(); +``` + +A block is a structure that specifies the block's position, size and color in rgba. Position and size are unitary, you can give it the unit and size you want. +``` +struct Block { + pub rect: Rect, + pub color: Color, +} +``` + +#### `perform(&mut self, action: Action)` + Perform movement and rotation actions +```rust +game.perform(Action::Rotate); +``` + +#### `is_game_over(&self) -> bool` +Checks if is game over. + +#### `get_score(&self) -> u64` +Gets the current score.