diff --git a/BEE_PLAYER.h b/BEE_PLAYER.h new file mode 100644 index 0000000..23bc321 --- /dev/null +++ b/BEE_PLAYER.h @@ -0,0 +1,18 @@ +// BEE_PLAYER.h +#ifndef BEE_PLAYER_H +#define BEE_PLAYER_H +#include +#include +#include "RAIN.h" // Include the definition +class RAIN; +class Obstacle; + +class BEE_PLAYER { +public: + virtual ~BEE_PLAYER() {} + virtual void attach(std::shared_ptr RAIN) = 0; + virtual void detach(RAIN* RAIN) =0; + virtual void notify(RAIN* RAIN, bool is_collision) =0; +}; + +#endif // BEE_PLAYER_H \ No newline at end of file diff --git a/GameManager.cpp b/GameManager.cpp new file mode 100644 index 0000000..dc591dd --- /dev/null +++ b/GameManager.cpp @@ -0,0 +1,52 @@ +// GameManager.cpp +#include "GameManager.h" +#include "splashkit.h" + +GameManager::GameManager() { + player = nullptr; +} + +void GameManager::setPlayer(Player* player) { + this->player = player; +} + +void GameManager::addObstacle(std::shared_ptr obstacle_ptr) { + obstacles.push_back(obstacle_ptr); + player->attach(obstacle_ptr); // Use shared_ptr instead of raw pointer +} + +void GameManager::checkCollisions() { + if (player == nullptr) return; + + for (const auto& obstacle_ptr : obstacles) { + Obstacle& obstacle = *obstacle_ptr; + if (player->get_x() < obstacle.get_x() + obstacle.get_width() && + player->get_x() + player->get_width() > obstacle.get_x() && + player->get_y() < obstacle.get_y() + obstacle.get_height() && + player->get_y() + player->get_height() > obstacle.get_y()) + { + // Notify player and obstacle about the collision + if (!obstacle.get_collision()) { // Collision started + player->notify(&obstacle, true); + Player::set_HP(Player::get_HP()-1); // Decrease player health on collision + } + draw_text("Collision detected!", COLOR_BLACK, "Arial", 24, player->get_x() + 10, player->get_y() - 50); + } + else { + if (obstacle.get_collision()) { // Collision ended + player->notify(&obstacle, false); + } + } + } +} + +void GameManager::updateGameObjects() { + for (const auto& obstacle_ptr : obstacles) { + Obstacle& obstacle = *obstacle_ptr; + obstacle.update(); + obstacle.draw(); + } +} + + + diff --git a/GameManager.h b/GameManager.h new file mode 100644 index 0000000..5a163f9 --- /dev/null +++ b/GameManager.h @@ -0,0 +1,24 @@ +// GameManager.h +#ifndef GAMEMANAGER_H +#define GAMEMANAGER_H + +#include "player.h" +#include "obstacle.h" +#include +#include + +class GameManager { +public: + GameManager(); + void addObstacle(std::shared_ptr obstacle_ptr); + void setPlayer(Player* player); + void checkCollisions(); + void updateGameObjects(); + void clear_Obstacles(){obstacles.clear();}; + +private: + Player* player; + std::vector> obstacles; +}; + +#endif // GAMEMANAGER_H diff --git a/Observer.h b/Observer.h deleted file mode 100644 index fe38c29..0000000 --- a/Observer.h +++ /dev/null @@ -1,12 +0,0 @@ -// observer.h -#ifndef OBSERVER_H -#define OBSERVER_H - -class Observer { -public: - virtual ~Observer() {} - virtual void CollisionUpdate(bool is_collision)=0; - virtual void deceaseSpeed(int newSpeed)=0; -}; - -#endif // OBSERVER_H \ No newline at end of file diff --git a/RAIN.h b/RAIN.h new file mode 100644 index 0000000..c6d7043 --- /dev/null +++ b/RAIN.h @@ -0,0 +1,12 @@ +// RAIN.h +#ifndef RAIN_H +#define RAIN_H + +class RAIN { +public: + virtual ~RAIN() {} + virtual void CollisionUpdate(bool is_collision)=0; + virtual void deceaseSpeed(int newSpeed)=0; +}; + +#endif // RAIN_H \ No newline at end of file diff --git a/Subject.h b/Subject.h deleted file mode 100644 index c7961d7..0000000 --- a/Subject.h +++ /dev/null @@ -1,16 +0,0 @@ -// Subject.h -#ifndef SUBJECT_H -#define SUBJECT_H -#include -class Observer; -class Obstacle; - -class Subject { -public: - virtual ~Subject() {} - virtual void attach(Observer* observer) =0; - virtual void detach(Observer* observer) =0; - virtual void notify(Observer* observer, bool is_collision) =0; -}; - -#endif // SUBJECT_H \ No newline at end of file diff --git a/game.exe b/game.exe index 0dee3e1..738a776 100644 Binary files a/game.exe and b/game.exe differ diff --git a/obstacle.cpp b/obstacle.cpp index 15b38a5..02aa8fa 100644 --- a/obstacle.cpp +++ b/obstacle.cpp @@ -33,7 +33,7 @@ void Obstacle::CollisionUpdate(bool is_collision) { void Obstacle::deceaseSpeed(int newSpeed){ this->speed = newSpeed; - std::cout << "The Speed equal to 2 now" << std::endl; + //std::cout << "The Speed equal to 2 now" << std::endl; } diff --git a/obstacle.h b/obstacle.h index 6d851fc..c2ed5c1 100644 --- a/obstacle.h +++ b/obstacle.h @@ -2,9 +2,9 @@ #ifndef OBSTACLE_H #define OBSTACLE_H -#include"Observer.h" +#include "RAIN.h" #include "player.h" -class Obstacle : public Observer { +class Obstacle : public RAIN { public: Obstacle(float x, float y,int speed); float get_x() { return x; } diff --git a/player.cpp b/player.cpp index 0838c8b..5296b83 100644 --- a/player.cpp +++ b/player.cpp @@ -27,30 +27,31 @@ void Player::move_left() { } -void Player::attach(Observer* observer) { - observers.push_back(observer); +void Player::attach(std::shared_ptr RAIN) { + RAINs.push_back(RAIN); } -void Player::detach(Observer* observer) { - auto it = std::remove(observers.begin(), observers.end(), observer); - if (it != observers.end()) { - std::cout << "Detaching observer" << std::endl; - observers.erase(it, observers.end()); - } +void Player::detach(RAIN* Rain) { + auto it = std::remove_if(RAINs.begin(), RAINs.end(), + [&Rain](const std::shared_ptr& o) { + return o.get() == Rain; // Compare the raw pointer + }); + RAINs.erase(it, RAINs.end()); } -void Player::notify(Observer* observer, bool is_collision) { - observer->CollisionUpdate(is_collision); // Call onCollision on the observer, passing this obstacle +void Player::notify(RAIN* RAIN, bool is_collision) { + RAIN->CollisionUpdate(is_collision); // Call onCollision on the RAIN, passing this obstacle } -void Player::notify_all_observers() { - std::cout << "Notifying all observers..." << std::endl; - for (Observer* observer : observers) { - if (observer == nullptr) { - std::cout << "Observer is null!" << std::endl; - continue; // Skip null observers +void Player::notify_all_RAINs() { + //std::cout << "Notifying all RAINs..." << std::endl; + for (auto& RAIN : RAINs) { + if (!RAIN) { + std::cout << "RAIN is null!" << std::endl; + continue; // Skip null RAINs } - observer->deceaseSpeed(1); + RAIN->deceaseSpeed(1); } } + diff --git a/player.h b/player.h index bd65d44..758b682 100644 --- a/player.h +++ b/player.h @@ -2,12 +2,12 @@ #ifndef PLAYER_H #define PLAYER_H -#include "Subject.h" +#include "BEE_PLAYER.h" #include "obstacle.h" #include -#include "Observer.h" +#include "RAIN.h" #include -class Player : public Subject { +class Player : public BEE_PLAYER { public: Player(float x, float y, float speed); @@ -20,14 +20,15 @@ class Player : public Subject { float get_speed() { return speed; } static int get_HP(){return HP;} static void set_HP(int hp){HP = hp;} - void attach(Observer* observer) override; - void detach(class Observer* observer) ; - void notify(class Observer* observer, bool is_collision); - void notify_all_observers(); + void attach(std::shared_ptr RAIN) override; + void detach(class RAIN* Rain) ; + void notify(class RAIN* RAIN, bool is_collision); + void notify_all_RAINs(); private: float x, y, speed, width, height; static int HP; - std::vector observers; + std::vector> RAINs; + }; #endif // PLAYER_H \ No newline at end of file diff --git a/program.cpp b/program.cpp index 9c36c87..604fe29 100644 --- a/program.cpp +++ b/program.cpp @@ -5,11 +5,12 @@ #include #include "player.h" #include "obstacle.h" -#include "Observer.h" -#include "Subject.h" +#include "RAIN.h" +#include "BEE_PLAYER.h" #include #include -//skm g++ program.cpp player.cpp obstacle.cpp -o game.exe +#include "GameManager.h" +//skm g++ program.cpp player.cpp obstacle.cpp GameManager.cpp -o game.exe bitmap background = bitmap_named("images/Background.jpg"); bitmap bee = bitmap_named("images/Bee.png"); @@ -35,9 +36,9 @@ void update_timer(); void display_timer(); void display_start_screen(); void player_move(Player* player); -void Spawn_obstacle(std::vector>& obstacles, Player* player, int& spawn_timer); -void render(std::vector>& obstacles, Player& player); -void check_game_over(std::vector>& obstacles,Player& player); +void Spawn_obstacle(GameManager& gameManager, int& spawn_timer); +void render(GameManager& gameManager, Player& player); +void check_game_over(GameManager& gameManager,Player& player); void display_game_over_screen(); void start_game() { @@ -64,14 +65,14 @@ void display_start_screen() { draw_text("Press SPACE to Start", COLOR_BLACK, "Arial", 200, 550, 200); } -void check_game_over(std::vector>& obstacles,Player& player) { +void check_game_over(GameManager& gameManager,Player& player) { if (Player::get_HP() == 1) { - player.notify_all_observers(); + player.notify_all_RAINs(); } else if (Player::get_HP() <= 0) { game_over = true; game_started = false; // Stop the game - obstacles.clear(); + gameManager.clear_Obstacles(); } } @@ -80,38 +81,6 @@ void display_game_over_screen() { draw_text("Press SPACE to Restart", COLOR_WHITE, "Arial", 32, 510, 500); } -template -bool is_colliding(T& obj1, U& obj2) { - float x1 = obj1.get_x(); - float y1 = obj1.get_y(); - float w1 = obj1.get_width(); - float h1 = obj1.get_height(); - - float x2 = obj2.get_x(); - float y2 = obj2.get_y(); - float w2 = obj2.get_width(); - float h2 = obj2.get_height(); - - // Check for collision - return (x1 < x2 + w2 && x1 + w1 > x2 && y1 < y2 + h2 && y1 + h1 > y2); -} - -// Function to handle collision between two objects -template -void handle_collision(T& subject, U& observer) { - if (is_colliding(subject, observer)) { - if (!observer.get_collision()) { // Collision started - subject.notify(&observer, true); - Player::set_HP(Player::get_HP()-1); // Decrease player health on collision - } - draw_text("Collision detected!", COLOR_BLACK, "Arial", 24, subject.get_x() + 10, subject.get_y() - 50); - } else { - if (observer.get_collision()) { // Collision ended - subject.notify(&observer, false); - } - } -} - void player_move(Player* player) { if (key_down(RIGHT_KEY) && player->get_x() <= RIGHT_BOUNDARY) { player->move_right(); @@ -126,14 +95,14 @@ void Spawn_obstacle(std::vector>& obstacles, Player* p if (spawn_timer >= spawn_interval) { spawn_timer = 0; int spawn_x = rand() % RIGHT_BOUNDARY; // Random x-coordinate between 0 and RIGHT_BOUNDARY - auto newObstacle = std::make_unique(spawn_x, 0, 2); - player->attach(newObstacle.get()); // Attach the newly created obstacle - obstacles.push_back(std::move(newObstacle)); // Move the smart pointer into the vector + std::shared_ptr obstacle = std::make_shared(spawn_x, 0, 2); + gameManager.addObstacle(obstacle); + } } -void render(std::vector>& obstacles, Player& player) { +void render(GameManager& gameManager,Player& player) { // Redrawing the bitmap after every clear background and bee double center_x = player.get_x()+(player.get_width()/2); double center_y = player.get_y()+(player.get_height()/2); @@ -149,14 +118,7 @@ void render(std::vector>& obstacles, Player& player) { // Draw the circle for debugging draw_circle(COLOR_RED,scaled_bee_circle); - // Update and draw obstacles - for (const auto& obstacle_ptr : obstacles) { - // Dereference the unique_ptr to access the Obstacle object - Obstacle& obstacle = *obstacle_ptr; - obstacle.update(); - obstacle.draw(); - handle_collision(player, obstacle); - } + gameManager.updateGameObjects(); } @@ -164,8 +126,10 @@ int main() { open_window("BeeFall", WINDOW_WIDTH, WINDOW_HEIGHT); // Named window beefall and window size hide_mouse(); // Hide mouse while cursor is over the game window Player player(player_posx, player_posy, 10.0f); // Initialize player - std::vector> obstacles; // List of obstacles - + // Create game manager and add game objects + GameManager gameManager; + gameManager.setPlayer(&player); + // Timer for obstacle spawning int spawn_timer = 0; @@ -203,15 +167,19 @@ int main() { player_move(&player); // Spawn obstacles - Spawn_obstacle(obstacles, &player, spawn_timer); + Spawn_obstacle(gameManager, spawn_timer); + + // Render game objects - render(obstacles, player); + render(gameManager,player); + gameManager.checkCollisions(); // Update game elements + update_timer(); display_timer(); - check_game_over(obstacles,player); + check_game_over(gameManager,player); refresh_screen(60); } diff --git a/tutorial/README.md b/tutorial/README.md new file mode 100644 index 0000000..abd00e7 --- /dev/null +++ b/tutorial/README.md @@ -0,0 +1,149 @@ +# Tutorial: Combining Design Patterns in the Bee Game Codebase + +In this game, we've implemented several design patterns, notably the **Observer** and **Mediator** patterns. These patterns help manage interactions between objects, making the code more modular and easier to maintain. Let's break down how these patterns are used. + +--- + +## Observer Pattern + +The **Observer Pattern** allows objects to subscribe to and receive updates from another object. This is particularly useful when you have a one-to-many relationship, such as when multiple obstacles (observers) need to be notified about the player’s status. + +### Components Involved: +- **Subject:** The `Player` class acts as the subject. +- **Observers:** The obstacles (`Obstacle` class) act as observers. Each obstacle can be updated when certain conditions are met, such as collisions with the player. + +### Implementation: +- **Attaching Observers:** + When a new obstacle is added to the game, it's attached to the `Player`: + ```cpp + void Player::attach(std::shared_ptr RAIN) { + RAINs.push_back(RAIN); + } + ``` + When a new obstacle is added to the game, it's attached to the Player. This way, the Player maintains a list of observers (obstacles). +- **Notifying Observers:** +```cpp +void Player::notify(RAIN* RAIN, bool is_collision) { + RAIN->CollisionUpdate(is_collision); +} +``` +When the player collides with an obstacle, it calls notify() to update the obstacle's state. +- **Notifying All Observers:** +```cpp +void Player::notify_all_RAINs() { + for (auto& RAIN : RAINs) { + RAIN->deceaseSpeed(1); + } +} +``` +This function is used when the player's health drops to a certain level, notifying all obstacles to slow down. + + +Absolutely! Here’s the entire tutorial wrapped in Markdown code fences, ensuring all sections are properly formatted with closing backticks: + +markdown +Copy code +# Tutorial: Combining Design Patterns in the Bee Game Codebase + +In this game, we've implemented several design patterns, notably the **Observer** and **Mediator** patterns. These patterns help manage interactions between objects, making the code more modular and easier to maintain. Let's break down how these patterns are used. + +--- + +## Observer Pattern + +The **Observer Pattern** allows objects to subscribe to and receive updates from another object. This is particularly useful when you have a one-to-many relationship, such as when multiple obstacles (observers) need to be notified about the player’s status. + +### Components Involved: +- **Subject:** The `Player` class acts as the subject. +- **Observers:** The obstacles (`Obstacle` class) act as observers. Each obstacle can be updated when certain conditions are met, such as collisions with the player. + +### Implementation: +- **Attaching Observers:** + + ```cpp + void Player::attach(std::shared_ptr RAIN) { + RAINs.push_back(RAIN); + } +When a new obstacle is added to the game, it's attached to the Player. This way, the Player maintains a list of observers (obstacles). + +Notifying Observers: + +cpp +Copy code +void Player::notify(RAIN* RAIN, bool is_collision) { + RAIN->CollisionUpdate(is_collision); +} +When the player collides with an obstacle, it calls notify() to update the obstacle's state. + +Notifying All Observers: + +cpp +Copy code +void Player::notify_all_RAINs() { + for (auto& RAIN : RAINs) { + RAIN->deceaseSpeed(1); + } +} +This function is used when the player's health drops to a certain level, notifying all obstacles to slow down. + +**Benefits:** +- **Loose Coupling:** The `Player` doesn’t need to know the specific details of the obstacles; it only knows that they are observers that need to be notified. +- **Scalability:** Adding more obstacles is straightforward without modifying the `Player` class. + +--- + +## Mediator Pattern +The **Mediator Pattern** helps reduce the dependencies between objects by introducing a mediator to handle communication. This pattern is seen in the GameManager class, which mediates interactions between the player and obstacles, such as detecting collisions. + +**Components Involved:** +- **Mediator:** The 'GameManager' class is the mediator. +- **Scalability:** The 'Player' and 'Obstacle' classes are the colleagues that communicate through the mediator. + +### Implementation: +- **GameManager as Mediator:** + +```cpp +void GameManager::addObstacle(std::shared_ptr obstacle_ptr) { + obstacles.push_back(obstacle_ptr); + player->attach(obstacle_ptr); +} +``` +The 'GameManager' handles the logic for adding obstacles, setting up the player, and checking for collisions. + +- **Handling Collisions:** +```cpp +void GameManager::checkCollisions() { + for (const auto& obstacle_ptr : obstacles) { + Obstacle& obstacle = *obstacle_ptr; + if (player->get_x() < obstacle.get_x() + obstacle.get_width() && + player->get_x() + player->get_width() > obstacle.get_x() && + player->get_y() < obstacle.get_y() + obstacle.get_height() && + player->get_y() + player->get_height() > obstacle.get_y()) { + + player->notify(&obstacle, true); + Player::set_HP(Player::get_HP()-1); + } + } +} +``` +The 'checkCollisions()' method detects if the player collides with any obstacles and notifies both the player and the obstacle. + +**Benefits:** +- **Centralized Logic:** The `GameManager` centralizes the game logic for interactions, making it easier to manage and maintain. +- **Reduced Dependencies:** The player and obstacles don’t need direct references to each other, as the `GameManager` handles their interaction. + +--- + +## Interaction Between Observer and Mediator Patterns + +The two patterns work together in the following way: +- **Mediator manages observers:** The `GameManager` (mediator) adds new obstacles and attaches them to the `Player` as observers. +- **Observer pattern updates:** The `Player` then updates these obstacles when collisions happen, or when the player's health changes. + +This combination ensures that the game’s objects are loosely coupled, flexible, and easy to extend. +--- + +## Conclusion +By integrating both the **Observer** and **Mediator** patterns, the game becomes more modular, maintainable, and scalable. The **Observer Pattern** allows the `Player` to communicate with multiple obstacles, while the **Mediator Pattern** in the `GameManager` centralizes control over the interaction logic. These patterns reduce direct dependencies between objects, adhering to the principles of object-oriented design. + +As the game grows, other patterns such as **State** or **Strategy** could be incorporated to manage player behavior or obstacle spawning strategies, further improving the design.