From 5fb654acc636fed75b1720baf30c6081f3ed5dc0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=9F=D0=B5=D1=87=D0=B5=D0=BD=D0=B8=D0=BD=20=D0=94=D0=B0?=
 =?UTF-8?q?=D0=BD=D0=B8=D0=BB=D0=B0=20=D0=9C=D0=B8=D1=85=D0=B0=D0=B9=D0=BB?=
 =?UTF-8?q?=D0=BE=D0=B2=D0=B8=D1=87?= <dmpechenin@edu.hse.ru>
Date: Sat, 8 Mar 2025 13:17:31 +0300
Subject: [PATCH] Concrete entities were created + some changes in
 SelectState's + assets' logic

---
 CMakeLists.txt                                |  2 ++
 source/Configuration.h                        | 18 +++++-----
 .../GameState/Entities/DynamicEntities.cpp    | 28 +++++++++++++++
 .../GameState/Entities/DynamicEntities.h      | 24 +++++++++++++
 source/States/GameState/Entities/Pacman.cpp   | 19 ++++++++++
 source/States/GameState/Entities/Pacman.h     | 12 +++++++
 .../GameState/Entities/StaticEntities.cpp     | 20 +++++++++++
 .../GameState/Entities/StaticEntities.h       | 17 +++++++++
 source/States/GameState/GameContext.cpp       | 12 +++++++
 source/States/GameState/GameContext.h         | 12 +++++++
 source/States/GameState/Maze/Room.cpp         |  3 +-
 source/States/GameState/Maze/Room.h           |  4 +--
 source/States/GameState/Maze/RoomSide.cpp     | 33 ++++++++++++++++-
 source/States/GameState/Maze/RoomSide.h       |  2 +-
 source/States/SelectState/SelectState.cpp     | 36 ++++++++++---------
 source/States/SelectState/SelectState.h       |  2 +-
 16 files changed, 212 insertions(+), 32 deletions(-)
 create mode 100644 source/States/GameState/Entities/DynamicEntities.cpp
 create mode 100644 source/States/GameState/Entities/DynamicEntities.h
 create mode 100644 source/States/GameState/Entities/Pacman.cpp
 create mode 100644 source/States/GameState/Entities/Pacman.h
 create mode 100644 source/States/GameState/Entities/StaticEntities.cpp
 create mode 100644 source/States/GameState/Entities/StaticEntities.h
 create mode 100644 source/States/GameState/GameContext.cpp
 create mode 100644 source/States/GameState/GameContext.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index fd6f28c..f5380fa 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -14,4 +14,6 @@ FetchContent_MakeAvailable(SFML)
 add_executable(${PROJECT_NAME} ${SOURCES})
 target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_SOURCE_DIR}/source)
 
+target_compile_definitions(${PROJECT_NAME} PUBLIC ASSETS_PATH="${CMAKE_CURRENT_SOURCE_DIR}/assets/")
+
 target_link_libraries(${PROJECT_NAME} PRIVATE sfml-system sfml-window sfml-graphics)
\ No newline at end of file
diff --git a/source/Configuration.h b/source/Configuration.h
index 4fc5e2d..a1ada6c 100644
--- a/source/Configuration.h
+++ b/source/Configuration.h
@@ -8,7 +8,7 @@ namespace config {
     const sf::Vector2f BUTTON_SIZE = { 250, 100 };
     const size_t BUTTON_FONT_SIZE = static_cast<size_t>(BUTTON_SIZE.y / 1.5f);
     constexpr float BUTTON_FRAME_THICKNESS = 2.0f;
-    constexpr char FONT_FILE[] = "calibril.ttf";
+    constexpr char FONT_FILE[] = ASSETS_PATH "calibril.ttf";
     constexpr char SELECT_LEVEL_TITLE[] = "Select Level";
     const sf::VideoMode SELECT_LEVEL_VIDEO_MODE{ 400, 600 };
     constexpr char BUTTON_TEXT_EASY[] = "Easy";
@@ -23,11 +23,11 @@ namespace config {
     // constexpr float EASY_GAME_ENEMY_RATIO = 0.0f;
     // constexpr float MEDIUM_GAME_ENEMY_RATIO = 0.03f;
     // constexpr float HARD_GAME_ENEMY_RATIO = 0.07f;
-    // constexpr float ROOM_SIZE = 50;
-    // constexpr float GAME_ENEMY_SIZE = ROOM_SIZE * 0.7;
-    // constexpr float GAME_FOOD_SIZE = ROOM_SIZE * 0.2;
+    constexpr float ROOM_SIZE = 50; ///< @todo надо использовать
+    constexpr float GAME_ENEMY_SIZE = ROOM_SIZE * 0.7;
+    constexpr float GAME_FOOD_SIZE = ROOM_SIZE * 0.2;
     // Пакмэн:
-    // constexpr float GAME_PACMAN_SIZE = ROOM_SIZE * 0.8;
+    constexpr float GAME_PACMAN_SIZE = ROOM_SIZE * 0.8;
     // constexpr sf::Keyboard::Key KEY_LEFT = sf::Keyboard::A;
     // constexpr sf::Keyboard::Key KEY_RIGHT = sf::Keyboard::D;
     // constexpr sf::Keyboard::Key KEY_UP = sf::Keyboard::W;
@@ -41,9 +41,9 @@ namespace config {
     // const sf::Color GAME_COLOR_BACKGROUND_INGAME{ 230,230,230 };
     // const sf::Color GAME_COLOR_BACKGROUND_WIN{ 0, 255, 0 };
     // const sf::Color GAME_COLOR_BACKGROUND_LOST{ 255, 0, 0 };
-    // const sf::Color GAME_COLOR_PACMAN{ 250, 150, 0 };
+    const sf::Color GAME_COLOR_PACMAN{ 250, 150, 0 };
     // const sf::Color GAME_COLOR_ROOM{ 255, 255, 255 };
-    // const sf::Color GAME_COLOR_WALL{ 0, 0, 0 };
-    // const sf::Color GAME_FOOD_COLOR{ 0, 200, 100 };
-    // const sf::Color GAME_ENEMY_COLOR{ 255, 50, 0 };
+    const sf::Color GAME_COLOR_WALL{ 0, 0, 0 };
+    const sf::Color GAME_FOOD_COLOR{ 0, 200, 100 };
+    const sf::Color GAME_ENEMY_COLOR{ 255, 50, 0 };
 }
\ No newline at end of file
diff --git a/source/States/GameState/Entities/DynamicEntities.cpp b/source/States/GameState/Entities/DynamicEntities.cpp
new file mode 100644
index 0000000..8c4cd83
--- /dev/null
+++ b/source/States/GameState/Entities/DynamicEntities.cpp
@@ -0,0 +1,28 @@
+#include <Configuration.h>
+#include <States/GameState/Entities/DynamicEntities.h>
+
+Enemy::Enemy() : m_rectangle({config::GAME_ENEMY_SIZE, config::GAME_ENEMY_SIZE}) {
+    m_rectangle.setFillColor(config::GAME_ENEMY_COLOR);
+    m_rectangle.setOrigin({config::GAME_ENEMY_SIZE/2, config::GAME_ENEMY_SIZE/2});
+}
+
+std::unique_ptr<IDynamicEntity> Enemy::clone() const {
+    return std::make_unique<Enemy>(*this);
+}
+
+void Enemy::action() {
+    if (const auto miliseconds = static_cast<size_t>(m_stopwatch.getElapsedTime().asMilliseconds());
+        miliseconds < m_dist_milliseconds(m_rng))
+        return;
+    const auto direction = static_cast<Room::Direction>(m_dist_direction(m_rng));
+    m_ptr_room->get_side(direction)->enter(this);
+    m_stopwatch.restart();
+}
+
+void Enemy::draw_into(sf::RenderWindow& window) const {
+    window.draw(m_rectangle);
+}
+
+void Enemy::prepare_for_drawing()  {
+    m_rectangle.setPosition(m_ptr_room->get_position());
+}
diff --git a/source/States/GameState/Entities/DynamicEntities.h b/source/States/GameState/Entities/DynamicEntities.h
new file mode 100644
index 0000000..b8be739
--- /dev/null
+++ b/source/States/GameState/Entities/DynamicEntities.h
@@ -0,0 +1,24 @@
+#pragma once
+#include <States/GameState/Entities/IEntity.h>
+#include <random>
+
+struct IDynamicEntity : IEntity {
+    [[nodiscard]] virtual std::unique_ptr<IDynamicEntity> clone() const = 0;
+    virtual void action() = 0;
+    ~IDynamicEntity() override = default;
+};
+
+class Enemy : public IDynamicEntity {
+public:
+    Enemy();
+    [[nodiscard]] std::unique_ptr<IDynamicEntity> clone() const override;
+    void action() override;
+    void draw_into(sf::RenderWindow& window) const override;
+    void prepare_for_drawing() override;
+private:
+    sf::RectangleShape m_rectangle;
+    sf::Clock m_stopwatch;
+    std::mt19937 m_rng{std::random_device{}()};
+    std::uniform_int_distribution<size_t> m_dist_milliseconds{0, 9999};
+    std::uniform_int_distribution<> m_dist_direction{0, 3};
+};
diff --git a/source/States/GameState/Entities/Pacman.cpp b/source/States/GameState/Entities/Pacman.cpp
new file mode 100644
index 0000000..e6f04f8
--- /dev/null
+++ b/source/States/GameState/Entities/Pacman.cpp
@@ -0,0 +1,19 @@
+#include <Configuration.h>
+#include <States/GameState/Entities/Pacman.h>
+
+Pacman::Pacman() : m_circle{ config::GAME_PACMAN_SIZE } {
+    m_circle.setFillColor(config::GAME_COLOR_PACMAN);
+    m_circle.setOrigin(config::GAME_PACMAN_SIZE/2, config::GAME_PACMAN_SIZE/2);
+}
+
+void Pacman::move(const Room::Direction direction) {
+    m_ptr_room->get_side(direction)->enter(this);
+}
+
+void Pacman::draw_into(sf::RenderWindow& window) const {
+    window.draw(m_circle);
+}
+
+void Pacman::prepare_for_drawing() {
+    m_circle.setPosition(m_ptr_room->get_position());
+}
\ No newline at end of file
diff --git a/source/States/GameState/Entities/Pacman.h b/source/States/GameState/Entities/Pacman.h
new file mode 100644
index 0000000..827fb79
--- /dev/null
+++ b/source/States/GameState/Entities/Pacman.h
@@ -0,0 +1,12 @@
+#pragma once
+#include <States/GameState/Entities/IEntity.h>
+
+class Pacman : public IEntity {
+public:
+    Pacman();
+    void move(Room::Direction direction);
+    void draw_into(sf::RenderWindow& window) const override;
+    void prepare_for_drawing() override;
+private:
+    sf::CircleShape m_circle;
+};
\ No newline at end of file
diff --git a/source/States/GameState/Entities/StaticEntities.cpp b/source/States/GameState/Entities/StaticEntities.cpp
new file mode 100644
index 0000000..5e22de1
--- /dev/null
+++ b/source/States/GameState/Entities/StaticEntities.cpp
@@ -0,0 +1,20 @@
+#include <States/GameState/Entities/StaticEntities.h>
+#include <Configuration.h>
+
+Food::Food() : m_circle(config::GAME_FOOD_SIZE, 6) {
+    m_circle.setFillColor(config::GAME_FOOD_COLOR);
+    m_circle.setOrigin({config::GAME_FOOD_SIZE/2, config::GAME_FOOD_SIZE/2});
+}
+
+std::unique_ptr<IStaticEntity> Food::clone() const {
+    return std::make_unique<Food>(*this);
+}
+
+void Food::draw_into(sf::RenderWindow& window) const {
+    window.draw(m_circle);
+}
+
+void Food::prepare_for_drawing() {
+    m_circle.setPosition(m_ptr_room->get_position());
+}
+
diff --git a/source/States/GameState/Entities/StaticEntities.h b/source/States/GameState/Entities/StaticEntities.h
new file mode 100644
index 0000000..d005d7d
--- /dev/null
+++ b/source/States/GameState/Entities/StaticEntities.h
@@ -0,0 +1,17 @@
+#pragma once
+#include <States/GameState/Entities/IEntity.h>
+
+struct IStaticEntity : IEntity {
+   [[nodiscard]] virtual std::unique_ptr<IStaticEntity> clone() const = 0;
+   ~IStaticEntity() override = default;
+};
+
+class Food : public IStaticEntity {
+public:
+    Food();
+    [[nodiscard]] std::unique_ptr<IStaticEntity> clone() const override;
+    void draw_into(sf::RenderWindow& window) const override;
+    void prepare_for_drawing() override;
+private:
+    sf::CircleShape m_circle;
+};
\ No newline at end of file
diff --git a/source/States/GameState/GameContext.cpp b/source/States/GameState/GameContext.cpp
new file mode 100644
index 0000000..e6aac1a
--- /dev/null
+++ b/source/States/GameState/GameContext.cpp
@@ -0,0 +1,12 @@
+#include <States/GameState/GameContext.h>
+
+GameContext GameContext::clone() const {
+    GameContext new_game_context;
+    new_game_context.pacman = pacman;
+    new_game_context.state = state;
+    for (const auto& obj : static_objects)
+        new_game_context.static_objects.push_back(obj->clone());
+    for (const auto& obj : dynamic_objects)
+        new_game_context.dynamic_objects.push_back(obj->clone());
+    return new_game_context;
+}
diff --git a/source/States/GameState/GameContext.h b/source/States/GameState/GameContext.h
new file mode 100644
index 0000000..8c295d2
--- /dev/null
+++ b/source/States/GameState/GameContext.h
@@ -0,0 +1,12 @@
+#pragma once
+#include <States/GameState/Entities/Pacman.h>
+#include <States/GameState/Entities/StaticEntities.h>
+#include <States/GameState/Entities/DynamicEntities.h>
+
+struct GameContext {
+    [[nodiscard]] GameContext clone() const;
+    Pacman pacman;
+    std::vector<std::unique_ptr<IStaticEntity>> static_objects;
+    std::vector<std::unique_ptr<IDynamicEntity>> dynamic_objects;
+    enum State{ INGAME, WIN, LOST } state = INGAME;
+};
\ No newline at end of file
diff --git a/source/States/GameState/Maze/Room.cpp b/source/States/GameState/Maze/Room.cpp
index 4f8964a..aa62c09 100644
--- a/source/States/GameState/Maze/Room.cpp
+++ b/source/States/GameState/Maze/Room.cpp
@@ -4,9 +4,10 @@ Room::Room(float size) : m_rectangle({size, size}) {
     m_rectangle.setOrigin(size/2, size/2);
 }
 
-void Room::set_side(const Direction side, std::unique_ptr<IRoomSide>&& ptr_side) {
+void Room::set_side(const Direction side, std::shared_ptr<IRoomSide>&& ptr_side) {
     if (side == INVALID) throw std::invalid_argument("Invalid direction");
     m_sides[side] = std::move(ptr_side);
+    ptr_side->prepare_for_drawing();
 }
 
 Room::Direction Room::get_direction(const IRoomSide* ptr_side) const {
diff --git a/source/States/GameState/Maze/Room.h b/source/States/GameState/Maze/Room.h
index db04902..9520cbf 100644
--- a/source/States/GameState/Maze/Room.h
+++ b/source/States/GameState/Maze/Room.h
@@ -14,12 +14,12 @@ public:
     float get_size() const { return m_rectangle.getSize().x; }
     void set_position(const sf::Vector2f pos) { m_rectangle.setPosition(pos); }
     sf::Vector2f get_position() const { return m_rectangle.getPosition(); }
-    void set_side(Direction side, std::unique_ptr<IRoomSide>&& ptr_side);
+    void set_side(Direction side, std::shared_ptr<IRoomSide>&& ptr_side);
     IRoomSide* get_side(const Direction side) const { return m_sides[side].get(); }
     Direction get_direction(const IRoomSide* ptr_side) const;
     void draw_into(sf::RenderWindow& window) const override;
 private:
     sf::RectangleShape m_rectangle;
-    std::array<std::unique_ptr<IRoomSide>, 4> m_sides;
+    std::array<std::shared_ptr<IRoomSide>, 4> m_sides; ///< shared_ptr? Две комнаты владеют одним IRoomSide
 };
 
diff --git a/source/States/GameState/Maze/RoomSide.cpp b/source/States/GameState/Maze/RoomSide.cpp
index 48f6d5c..4a443b5 100644
--- a/source/States/GameState/Maze/RoomSide.cpp
+++ b/source/States/GameState/Maze/RoomSide.cpp
@@ -1,7 +1,38 @@
+#include <Configuration.h>
 #include <States/GameState/Entities/IEntity.h>
 
 void Pass::enter(IEntity* entity) const {
     if (entity->get_location() == &m_room1)
         entity->set_location(&m_room2);
     else entity->set_location(&m_room1);
-}
\ No newline at end of file
+}
+
+void Wall::prepare_for_drawing() {
+    const sf::Vector2 pos = m_room.get_position();
+    const float size = m_room.get_size();
+    const std::array<sf::Vector2f, 4> corners = { sf::Vector2f{pos.x - size/2, pos.y - size/2},
+        sf::Vector2f{pos.x + size/2, pos.y - size/2},
+        sf::Vector2f{pos.x - size/2, pos.y + size/2},
+        sf::Vector2f{pos.x + size/2, pos.y + size/2}};
+
+    switch (Room::Direction direction = m_room.get_direction(this)) {
+        case Room::Direction::UP:
+            m_line[0] = sf::Vertex(corners[0], config::GAME_COLOR_WALL);
+            m_line[1] = sf::Vertex(corners[1], config::GAME_COLOR_WALL);
+            break;
+        case Room::Direction::DOWN:
+            m_line[0] = sf::Vertex(corners[2], config::GAME_COLOR_WALL);
+            m_line[1] = sf::Vertex(corners[3], config::GAME_COLOR_WALL);
+            break;
+        case Room::Direction::LEFT:
+            m_line[0] = sf::Vertex(corners[0], config::GAME_COLOR_WALL);
+            m_line[1] = sf::Vertex(corners[2], config::GAME_COLOR_WALL);
+            break;
+        case Room::Direction::RIGHT:
+            m_line[0] = sf::Vertex(corners[1], config::GAME_COLOR_WALL);
+            m_line[1] = sf::Vertex(corners[3], config::GAME_COLOR_WALL);
+            break;
+        case Room::Direction::INVALID:
+            throw std::invalid_argument("Invalid direction");
+    }
+}
diff --git a/source/States/GameState/Maze/RoomSide.h b/source/States/GameState/Maze/RoomSide.h
index 0c2dca7..f730f21 100644
--- a/source/States/GameState/Maze/RoomSide.h
+++ b/source/States/GameState/Maze/RoomSide.h
@@ -25,7 +25,7 @@ public:
     explicit Wall(Room& room) : m_room(room) {}
     void enter(IEntity* entity) const override {}
     void draw_into(sf::RenderWindow& window) const override { window.draw(m_line, 2, sf::Lines); }
-    void prepare_for_drawing() override {} /// @todo разобраться, что тут делать
+    void prepare_for_drawing() override;
 private:
     Room& m_room;
     sf::Vertex m_line[2];
diff --git a/source/States/SelectState/SelectState.cpp b/source/States/SelectState/SelectState.cpp
index 2661e9d..4b64ef3 100644
--- a/source/States/SelectState/SelectState.cpp
+++ b/source/States/SelectState/SelectState.cpp
@@ -1,4 +1,4 @@
-#include "SelectState.h"
+#include <States/SelectState/SelectState.h>
 #include <States/ChangeStateCommand.h>
 #include <BasicAbstractions/Font.h>
 
@@ -58,18 +58,18 @@ Menu::Menu(IStateManager& state_manager) {
      config::BUTTON_TEXT_EXIT, config::BUTTON_FONT_SIZE, std::make_unique<GameCommand>(state_manager, std::make_unique<GameBuilderDirector>()));
 }
 
-bool Menu::process_mouse(const sf::Vector2f pos, const bool is_pressed) {
+void Menu::process_mouse(const sf::Vector2f pos, const bool is_pressed) {
     for (auto& button : m_buttons) {
-        if (button.is_position_in(pos)) {
+        if (!button.is_position_in(pos)) {
+            button.unselect();
+            continue;
+        }
+        if (!is_pressed) {
             button.select();
-            if (is_pressed) {
-                button.push();
-                return false;
-            }
+            continue;
         }
-        else button.unselect();
+        button.push();
     }
-    return true;
 }
 
 void Menu::draw_into(sf::RenderWindow& window) const {
@@ -85,13 +85,17 @@ void SelectState::event_handling() {
             m_state_manager.set_next_state(std::make_unique<ExitState>(m_state_manager));
             m_window.close();
         }
+        if (event.type == sf::Event::Resized) {
+            sf::View view = m_window.getView();
+            view.setSize(event.size.width, event.size.height);
+            m_window.setView(view);
+        }
     }
 }
 
 void SelectState::update() {
-    if (!m_menu.process_mouse(m_window.mapPixelToCoords(sf::Mouse::getPosition(m_window)),
-        sf::Mouse::isButtonPressed(sf::Mouse::Left)))
-        m_window.close();
+    m_menu.process_mouse(m_window.mapPixelToCoords(sf::Mouse::getPosition(m_window)),
+        sf::Mouse::isButtonPressed(sf::Mouse::Left));
 }
 
 void SelectState::render() {
@@ -101,10 +105,8 @@ void SelectState::render() {
 }
 
 bool SelectState::do_step() {
-    while (m_window.isOpen()) {
-        event_handling();
-        update();
-        render();
-    }
+    event_handling();
+    update();
+    render();
     return true;
 }
\ No newline at end of file
diff --git a/source/States/SelectState/SelectState.h b/source/States/SelectState/SelectState.h
index 4b0dc6a..cea4897 100644
--- a/source/States/SelectState/SelectState.h
+++ b/source/States/SelectState/SelectState.h
@@ -22,7 +22,7 @@ private:
 class Menu: public IDrawable {
 public:
     explicit Menu(IStateManager& state_manager);
-    bool process_mouse(sf::Vector2f pos, bool is_pressed);
+    void process_mouse(sf::Vector2f pos, bool is_pressed);
     void draw_into(sf::RenderWindow& window) const override;
 private:
     std::array<Button, 4> m_buttons;
-- 
GitLab