diff --git a/ReadMe.md b/ReadMe.md index 95e202f..fb5e500 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -30,6 +30,12 @@ brew install sdl2 export LIBRARY_PATH="$LIBRARY_PATH:/usr/local/lib" ``` +### Run the game +``` +cargo run --release +``` +You can also just enter ``` cargo build --release ```` and then start the binary from target/src + ## How to play ? This is a local multiplayer game, so two players can play using the same keyboard, player 1 uses 'w' and 's' and so player 2 uses the arrow keys. diff --git a/resources/Roboto-Regular.ttf b/resources/Roboto-Regular.ttf new file mode 100644 index 0000000..3d6861b Binary files /dev/null and b/resources/Roboto-Regular.ttf differ diff --git a/resources/Vector.ttf b/resources/Vector.ttf new file mode 100644 index 0000000..62859bc Binary files /dev/null and b/resources/Vector.ttf differ diff --git a/src/main.rs b/src/main.rs index c96cc25..cb688e7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ use tetra::input::{self, Key}; use tetra::math::Vec2; use tetra::window; use tetra::{Context, ContextBuilder, State}; +use tetra::graphics::text::*; const WINDOW_WIDTH:f32 = 640.0; const WINDOW_HEIGHT:f32 = 480.0; @@ -12,6 +13,7 @@ const PADDLE_SPEED:f32 = 8.0; const BALL_SPEED:f32 = 5.0; const PADDLE_SPIN: f32 = 4.0; const BALL_ACC:f32 = 0.05; +const SCORE_LOCATION:Vec2 = Vec2::new(16.0,16.0); struct Entity { texture: Texture, @@ -51,11 +53,18 @@ impl Entity{ ) } } - +enum GameStatus { + Playing, + Pause, + Ended, +} struct GameState { player1: Entity, player2: Entity, ball: Entity, + score_text: Text, + score:i32, + state:GameStatus } impl GameState{ @@ -74,89 +83,136 @@ impl GameState{ let ball_speed = Vec2::new(-BALL_SPEED,0.0); + let vector_text = Text::new( + format!("Score : {}",0), + Font::vector(ctx, "./resources/Roboto-Regular.ttf", 16.0)?, + ); + + Ok(GameState { player1: Entity::new(player1_texture,player1_position), player2: Entity::new(player2_texture,player2_position), ball: Entity::with_velocity(ball_texture,ball_position,ball_speed), + score_text: vector_text, + score:0, + state:GameStatus::Pause, }) } + fn restart(&mut self){ + self.player1.position = Vec2::new(16.0,(WINDOW_HEIGHT - self.player1.texture.height() as f32) / 2.0); + self.player2.position = Vec2::new(WINDOW_WIDTH - self.player2.texture.width() as f32 -16.0,(WINDOW_HEIGHT - self.player2.texture.height() as f32) / 2.0); + self.ball.position = Vec2::new(WINDOW_WIDTH / 2.0 - (self.ball.texture.width() as f32 / 2.0) as f32,WINDOW_HEIGHT / 2.0 - (self.ball.texture.height() as f32 / 2.0) as f32); + + self.ball.velocity = Vec2::new(-BALL_SPEED,0.0); + + self.score = 0; + self.score_text.set_content(format!("Score : {}",0)); + + self.state = GameStatus::Pause; + } } impl State for GameState{ fn draw(&mut self, ctx: &mut Context) -> tetra::Result { - graphics::clear(ctx,Color::hex("#CCCCCC")); + graphics::clear(ctx,Color::hex("#333333")); self.player1.texture.draw(ctx,self.player1.position); self.player2.texture.draw(ctx,self.player2.position); self.ball.texture.draw(ctx,self.ball.position); + self.score_text.draw(ctx, SCORE_LOCATION); Ok(()) } + fn update(&mut self,ctx: &mut Context) -> tetra::Result{ //Player 1 movements - if input::is_key_down(ctx, Key::W) && self.player1.position.y as i32 > 0{ - self.player1.position.y -= PADDLE_SPEED; - } - if input::is_key_down(ctx, Key::S) && ((self.player1.position.y as i32 + self.player1.texture.height()) as f32) < WINDOW_HEIGHT{ - self.player1.position.y += PADDLE_SPEED; - } - //player 2 movements - if input::is_key_down(ctx, Key::Up) && self.player2.position.y as i32 > 0{ - self.player2.position.y -= PADDLE_SPEED; - } - if input::is_key_down(ctx, Key::Down) && ((self.player2.position.y as i32 + self.player1.texture.height()) as f32) < WINDOW_HEIGHT{ - self.player2.position.y += PADDLE_SPEED; - } + match self.state { + GameStatus::Playing =>{ + if input::is_key_down(ctx, Key::W) && self.player1.position.y as i32 > 0{ + self.player1.position.y -= PADDLE_SPEED; + } + if input::is_key_down(ctx, Key::S) && ((self.player1.position.y as i32 + self.player1.texture.height()) as f32) < WINDOW_HEIGHT{ + self.player1.position.y += PADDLE_SPEED; + } + //player 2 movements + if input::is_key_down(ctx, Key::Up) && self.player2.position.y as i32 > 0{ + self.player2.position.y -= PADDLE_SPEED; + } + if input::is_key_down(ctx, Key::Down) && ((self.player2.position.y as i32 + self.player1.texture.height()) as f32) < WINDOW_HEIGHT{ + self.player2.position.y += PADDLE_SPEED; + } + + //Collisions + + let player1_bounds:Rectangle = self.player1.bounds(); + let player2_bounds:Rectangle = self.player2.bounds(); + let ball_bounds:Rectangle = self.ball.bounds(); + + let paddle_hit = if ball_bounds.intersects(&player1_bounds) { + Some(&self.player1) + }else if ball_bounds.intersects(&player2_bounds){ + Some(&self.player2) + }else{ + None + }; + + if let Some(paddle) = paddle_hit{ + //increase ball velocity before flipping it + self.ball.velocity.x = + -(self.ball.velocity.x + (BALL_ACC * self.ball.velocity.x.signum())); + + //Calculate the offset between the paddle center and the ball + let offset = (paddle.centre().y - self.ball.centre().y) / paddle.height(); + + self.ball.velocity.y += PADDLE_SPIN * -offset; + + //increase the score as + self.score += 1; + self.score_text.set_content(format!("Score : {}",self.score)); + } + + self.ball.position += self.ball.velocity; + + //check borders + if self.ball.position.y <= 0.0 || self.ball.position.y + self.ball.height() >= WINDOW_HEIGHT{ + self.ball.velocity.y = -self.ball.velocity.y; + } + //check winner + if self.ball.position.x <= 0.0{ + //window::quit(ctx); + self.score_text.set_content("Player 2 Won!\nPress Enter to play again"); + self.state = GameStatus::Ended; + } + if self.ball.position.x + self.ball.width() >= WINDOW_WIDTH{ + //window::quit(ctx); + self.score_text.set_content("Player 2 Won!\nPress Enter to play again"); + self.state = GameStatus::Ended; + } + } + GameStatus::Pause =>{ + self.score_text.set_content("Remember \nPlayer 1 uses 'w' and 's'\nPlayer 2 uses up and down arrow keys\nPress Enter to start the game"); - //Collisions - - let player1_bounds:Rectangle = self.player1.bounds(); - let player2_bounds:Rectangle = self.player2.bounds(); - let ball_bounds:Rectangle = self.ball.bounds(); - - let paddle_hit = if ball_bounds.intersects(&player1_bounds) { - Some(&self.player1) - }else if ball_bounds.intersects(&player2_bounds){ - Some(&self.player2) - }else{ - None - }; - - if let Some(paddle) = paddle_hit{ - //increase ball velocity before flipping it - self.ball.velocity.x = - -(self.ball.velocity.x + (BALL_ACC * self.ball.velocity.x.signum())); - - //Calculate the offset between the paddle center and the ball - let offset = (paddle.centre().y - self.ball.centre().y) / paddle.height(); - - self.ball.velocity.y += PADDLE_SPIN * -offset; - } - - self.ball.position += self.ball.velocity; - - //check borders - if self.ball.position.y <= 0.0 || self.ball.position.y + self.ball.height() >= WINDOW_HEIGHT{ - self.ball.velocity.y = -self.ball.velocity.y; - } - //check winner - if self.ball.position.x <= 0.0{ - window::quit(ctx); - println!("Player 2 won!"); - } - if self.ball.position.x + self.ball.width() >= WINDOW_WIDTH{ - window::quit(ctx); - println!("Player 1 won!"); + if input::is_key_down(ctx, Key::Enter){ + self.state = GameStatus::Playing; + } + } + GameStatus::Ended =>{ + if input::is_key_down(ctx, Key::Enter){ + self.restart(); + println!("DEBUG: game restarted"); + } + } } Ok(()) } } + fn main() -> tetra::Result{ - ContextBuilder::new("Pong",WINDOW_WIDTH as i32,WINDOW_HEIGHT as i32) - .quit_on_escape(true) - .build()? - .run(GameState::new) + ContextBuilder::new("Rusty Pong",WINDOW_WIDTH as i32,WINDOW_HEIGHT as i32) + .quit_on_escape(true) + .build()? + .run(GameState::new) }