diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b0fddda --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +SRC := components/ +BUILD := build/ + +SOURCE := $(wildcard $(SRC)/*.cpp) +OBJECT := $(patsubst $(SRC)/%.cpp, $(BUILD)/%.o, $(SOURCE)) + +.PHONY: run + +cping_pong: $(OBJECT) $(BUILD)/main.o + gcc -Wall -lraylib -lm -o $@ $^ + +$(BUILD)/main.o: main.cpp + gcc -Wall -c $< -o $@ + +$(BUILD)/%.o: $(SRC)/%.cpp + gcc -Wall -c $< -o $@ + +run: cping_pong + @./cping_pong diff --git a/README.md b/README.md new file mode 100644 index 0000000..b53ffe6 --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# Ping Pong CPP + +- A ping pong game written in Cpp using RayLib + +
+ ![In-Game screenshot](screens/screen.png) +
+ +## How To Play + +- Use the keys `K/Up` and `J/Down` to move **Up** and **Down** + +## Build + +- Make sure that RayLib is installed on your system then compile the project. + +1. Create directory `build/` + +```sh +$ mkdir -p build/ +``` + +2. Compile the project by running `make` or `make run` to compile and run the project. + +```sh +$ make +$ ./cping_pong +# OR +$ make run +``` diff --git a/build/ball.o b/build/ball.o new file mode 100644 index 0000000..ef605bd Binary files /dev/null and b/build/ball.o differ diff --git a/build/game_manager.o b/build/game_manager.o new file mode 100644 index 0000000..9d15ebc Binary files /dev/null and b/build/game_manager.o differ diff --git a/build/main.o b/build/main.o new file mode 100644 index 0000000..aa20590 Binary files /dev/null and b/build/main.o differ diff --git a/build/paddle.o b/build/paddle.o new file mode 100644 index 0000000..789a958 Binary files /dev/null and b/build/paddle.o differ diff --git a/components/ball.cpp b/components/ball.cpp new file mode 100644 index 0000000..130e231 --- /dev/null +++ b/components/ball.cpp @@ -0,0 +1,46 @@ +#include "../include/ball.hpp" +#include + +void Game::Ball::draw() { DrawCircle(position.x, position.y, radius, color); } + +void Game::Ball::gotoCenter() { + position.x = static_cast(GetScreenWidth()) / 2; + position.y = static_cast(GetScreenHeight()) / 2; +} + +void Game::Ball::update() { + // limit ball speed + float speed = Vector2Length(velocity); + if (speed > 8) + velocity = Vector2Scale(velocity, 0.95f); + else if (speed < 6) + velocity = Vector2Scale(velocity, 1.1f); + + // update ball position based on velocity + position.x += velocity.x; + position.y += velocity.y; + + // collide with top wall + if (position.y - radius < 0) { + position.y = radius; + velocity.y *= -1; + } + + // collide with bottom wall + if (position.y + radius > GetScreenHeight()) { + position.y = GetScreenHeight() - radius; + velocity.y *= -1; + } +} + +void Game::Ball::shoot(Vector2 force) { + velocity.x += force.x; + velocity.y += force.y; +} + +void Game::Ball::shoot(Vector2 direction, float force) { + direction = Vector2Normalize(direction); + + velocity.x += direction.x * force; + velocity.y += direction.y * force; +} diff --git a/components/game_manager.cpp b/components/game_manager.cpp new file mode 100644 index 0000000..bbaa9e0 --- /dev/null +++ b/components/game_manager.cpp @@ -0,0 +1,86 @@ +#include "../include/game_manager.hpp" +#include +#include +#include + +void Game::GameManager::draw() { + const char *human_score_text = TextFormat("%u", human_score); + const char *cpu_score_text = TextFormat("%u", cpu_score); + + int human_text_length = MeasureText(human_score_text, font_size); + int cpu_text_length = MeasureText(cpu_score_text, font_size); + int x_value = GetScreenWidth() / 4; + int y_value = GetScreenHeight() / 2 - font_size / 2; + + DrawText(human_score_text, x_value - human_text_length / 2, y_value, + font_size, RAYWHITE); + + DrawText(cpu_score_text, GetScreenWidth() - x_value - cpu_text_length / 2, + y_value, font_size, RAYWHITE); + + DrawLine(x_value * 2, 0, x_value * 2, GetScreenHeight(), RAYWHITE); + DrawCircleLines(x_value * 2, GetScreenHeight() / 2, 80, RAYWHITE); +} + +void Game::GameManager::update() { + // if ball hits left wall then score goal for CPU and reset ball + if (ball.position.x + ball.radius <= 0) { + ball.gotoCenter(); + ball.velocity = {0, 0}; + ball.shoot({1, 0}, 6); + cpu_score++; + } + + // if ball hits right wall then score goal for human and reset ball + if (ball.position.x - ball.radius >= GetScreenWidth()) { + ball.gotoCenter(); + ball.velocity = {0, 0}; + ball.shoot({-1, 0}, 6); + human_score++; + } + + if (IsKeyPressed(KEY_SPACE) && human_score > 0) { + ball.gotoCenter(); + ball.velocity = {0, 0}; + ball.shoot({-1, 0}, 6); + human_score--; + } + + // change ball direction on collision with any paddle + if (CheckCollisionCircleRec(ball.position, ball.radius, human_paddle.rect)) + onBallPaddleCollision(human_paddle); + if (CheckCollisionCircleRec(ball.position, ball.radius, cpu_paddle.rect)) + onBallPaddleCollision(cpu_paddle); +} + +void Game::GameManager::onBallPaddleCollision(const Paddle &paddle) { + static float timeA = -1, timer = 0; + float time; + + float paddle_speed_ratio = paddle.velocity / paddle.max_velocity; + float ball_speed = Vector2Length(ball.velocity); + float y_touch = + (ball.position.y - paddle.rect.y) / paddle.rect.height * 2 - 1; + Vector2 normal = Vector2Normalize(ball.velocity); + + normal.y = (y_touch + paddle_speed_ratio) / 2; + normal.x *= -1; + ball.velocity = Vector2Scale(normal, ball_speed); + + // fix ball stuck glitch + time = GetTime(); + + if (time - timeA < 0.05) + timer += GetFrameTime(); + else + timer = 0; + + if (timer > 2) { + ball.gotoCenter(); + ball.velocity = {0, 0}; + ball.shoot({-1, 0}, 6); + timer = 0; + TraceLog(LOG_INFO, "Glitch Detected!!"); + } + timeA = GetTime(); +} diff --git a/components/paddle.cpp b/components/paddle.cpp new file mode 100644 index 0000000..5e5f999 --- /dev/null +++ b/components/paddle.cpp @@ -0,0 +1,34 @@ +#include "../include/paddle.hpp" +#include + +float Game::Paddle::window_padding = 10; + +void Game::Paddle::gotoCenter() { + rect.y = static_cast(GetScreenHeight()) / 2 - rect.height / 2; +} + +void Game::Paddle::draw() { DrawRectangleRec(rect, color); } + +void Game::Paddle::update() { + // if velocity is too small set it to 0 + if (std::abs(velocity) < 0.01) + velocity = 0; + // if velocity is too big set it to maximum velocity + if (std::abs(velocity) > max_velocity) + velocity = velocity > 0 ? max_velocity : -max_velocity; + + // update the position of the paddle based on the velocity + rect.y += velocity; + // decrease the velocity over time, so the paddle stops after a while + velocity *= drag; + + // limit the paddle to the screen edges + if (rect.y < 0) + rect.y = 0; + if (rect.y > GetScreenHeight() - rect.height) + rect.y = GetScreenHeight() - rect.height; +} + +void Game::Paddle::moveUp() { velocity -= speed; } + +void Game::Paddle::moveDown() { velocity += speed; } diff --git a/cping_pong b/cping_pong new file mode 100755 index 0000000..136f3a9 Binary files /dev/null and b/cping_pong differ diff --git a/include/ball.hpp b/include/ball.hpp new file mode 100644 index 0000000..96c886e --- /dev/null +++ b/include/ball.hpp @@ -0,0 +1,24 @@ +#ifndef BALL_HPP +#define BALL_HPP + +#include + +namespace Game { +class Ball { +public: + Vector2 position = {0, 0}; + Vector2 velocity = {0, 0}; + Color color = RED; + float radius = 10; + + void gotoCenter(); + + void draw(); + void update(); + + void shoot(Vector2 force); + void shoot(Vector2 direction, float force); +}; +} // namespace Game + +#endif diff --git a/include/game_manager.hpp b/include/game_manager.hpp new file mode 100644 index 0000000..3f3d15d --- /dev/null +++ b/include/game_manager.hpp @@ -0,0 +1,31 @@ +#ifndef GAME_MANAGER_HPP +#define GAME_MANAGER_HPP + +#include "ball.hpp" +#include "paddle.hpp" + +namespace Game { + +class GameManager { + Ball &ball; + const Paddle &human_paddle; + const Paddle &cpu_paddle; + + void onBallPaddleCollision(const Paddle &paddle); + +public: + unsigned int human_score = 0; + unsigned int cpu_score = 0; + + int font_size = 164; + + GameManager(Ball &ball, const Paddle &human_paddle, const Paddle &cpu_paddle) + : ball(ball), human_paddle(human_paddle), cpu_paddle(cpu_paddle) {} + + void update(); + void draw(); +}; + +} // namespace Game + +#endif diff --git a/include/paddle.hpp b/include/paddle.hpp new file mode 100644 index 0000000..1d44920 --- /dev/null +++ b/include/paddle.hpp @@ -0,0 +1,28 @@ +#ifndef PADDLE_HPP +#define PADDLE_HPP + +#include + +namespace Game { +class Paddle { +public: + Rectangle rect = {0, 0, 15, 100}; + Color color = RAYWHITE; + float speed = 2; + float velocity = 0; + float max_velocity = 5; + float drag = 0.88; + static float window_padding; + + void gotoCenter(); + + void draw(); + void update(); + + void moveUp(); + void moveDown(); +}; + +} // namespace Game + +#endif diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..df5d830 --- /dev/null +++ b/main.cpp @@ -0,0 +1,64 @@ +#include "include/ball.hpp" +#include "include/game_manager.hpp" +#include "include/paddle.hpp" +#include + +void cpu_paddle(Game::Paddle &paddle, const Game::Ball &ball) { + float diff = ball.position.y - paddle.rect.y - paddle.rect.height / 2; + + if (diff < 0) + paddle.moveUp(); + else + paddle.moveDown(); +} + +void human_paddle(Game::Paddle &paddle) { + if (IsKeyDown(KEY_UP) || IsKeyDown(KEY_K)) + paddle.moveUp(); + else if (IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_J)) + paddle.moveDown(); +} + +int main() { + Game::Ball ball; + Game::Paddle p1, p2; + Game::GameManager gm(ball, p2, p1); + + InitWindow(800, 500, "PingPongCpp"); + SetTargetFPS(60); + + ball.gotoCenter(); + ball.shoot({1, -1}, 6); + + p1.rect.x = GetScreenWidth() - p1.rect.width - Game::Paddle::window_padding; + p1.gotoCenter(); + + p2.rect.x = Game::Paddle::window_padding; + p2.gotoCenter(); + + while (!WindowShouldClose()) { + ball.update(); + + p1.update(); + cpu_paddle(p1, ball); + + p2.update(); + human_paddle(p2); + + gm.update(); + + BeginDrawing(); + ClearBackground(BLACK); + + gm.draw(); + ball.draw(); + p1.draw(); + p2.draw(); + + EndDrawing(); + } + + CloseWindow(); + + return 0; +} diff --git a/screens/screen.png b/screens/screen.png new file mode 100644 index 0000000..504e84e Binary files /dev/null and b/screens/screen.png differ