diff --git a/.gitignore b/.gitignore index 3dfc9c3..d0a2d4c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ sfml/* Makefile CMakeCache.txt +.DS_Store diff --git a/MemoryManagement/LinkedList/LinkedList/src/approaching.cpp b/MemoryManagement/LinkedList/LinkedList/src/approaching.cpp index de9627a..d44ade4 100644 --- a/MemoryManagement/LinkedList/LinkedList/src/approaching.cpp +++ b/MemoryManagement/LinkedList/LinkedList/src/approaching.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" void approachingLoop(Circle player, Circle consumable[], bool concerned[], int size) { for (int i = 0; i < size; ++i) { diff --git a/MemoryManagement/LinkedList/LinkedList/src/borders.cpp b/MemoryManagement/LinkedList/LinkedList/src/borders.cpp index b5ca906..c9df954 100644 --- a/MemoryManagement/LinkedList/LinkedList/src/borders.cpp +++ b/MemoryManagement/LinkedList/LinkedList/src/borders.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" Point2D adjustToBorders(Point2D position) { Point2D result = position; diff --git a/MemoryManagement/LinkedList/LinkedList/src/collision.cpp b/MemoryManagement/LinkedList/LinkedList/src/collision.cpp index 639aad3..449016a 100644 --- a/MemoryManagement/LinkedList/LinkedList/src/collision.cpp +++ b/MemoryManagement/LinkedList/LinkedList/src/collision.cpp @@ -1,6 +1,6 @@ #include -#include "scene.hpp" +#include "game.hpp" float distance(Point2D a, Point2D b) { float dx = a.x - b.x; diff --git a/MemoryManagement/LinkedList/LinkedList/src/direction.cpp b/MemoryManagement/LinkedList/LinkedList/src/direction.cpp index 2195184..ad7c2bd 100644 --- a/MemoryManagement/LinkedList/LinkedList/src/direction.cpp +++ b/MemoryManagement/LinkedList/LinkedList/src/direction.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" Point2D getDirection(Direction direction) { switch (direction) { diff --git a/MemoryManagement/LinkedList/LinkedList/src/generate.cpp b/MemoryManagement/LinkedList/LinkedList/src/generate.cpp index b08977b..180912d 100644 --- a/MemoryManagement/LinkedList/LinkedList/src/generate.cpp +++ b/MemoryManagement/LinkedList/LinkedList/src/generate.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" #include diff --git a/MemoryManagement/LinkedList/LinkedList/src/loop.cpp b/MemoryManagement/LinkedList/LinkedList/src/loop.cpp index 5fe6ce3..8148f4a 100644 --- a/MemoryManagement/LinkedList/LinkedList/src/loop.cpp +++ b/MemoryManagement/LinkedList/LinkedList/src/loop.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" void collisionLoop(Circle player, Circle consumable[], bool consumed[], int size) { for (int i = 0; i < size; ++i) { diff --git a/MemoryManagement/LinkedList/LinkedList/src/main.cpp b/MemoryManagement/LinkedList/LinkedList/src/main.cpp index 830d150..7113e21 100644 --- a/MemoryManagement/LinkedList/LinkedList/src/main.cpp +++ b/MemoryManagement/LinkedList/LinkedList/src/main.cpp @@ -1,6 +1,6 @@ #include -#include "scene.hpp" +#include "game.hpp" #include "dllist.hpp" const int MAX_CONSUMABLES_COUNT = 8; diff --git a/MemoryManagement/LinkedList/LinkedList/src/point.cpp b/MemoryManagement/LinkedList/LinkedList/src/point.cpp index 6e55434..5c74f69 100644 --- a/MemoryManagement/LinkedList/LinkedList/src/point.cpp +++ b/MemoryManagement/LinkedList/LinkedList/src/point.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" Point2D add(Point2D a, Point2D b) { Point2D c = { 0, 0 }; diff --git a/MemoryManagement/LinkedList/LinkedList/task.md b/MemoryManagement/LinkedList/LinkedList/task.md index b1e65c9..caf1de9 100644 --- a/MemoryManagement/LinkedList/LinkedList/task.md +++ b/MemoryManagement/LinkedList/LinkedList/task.md @@ -55,7 +55,7 @@ void link(Node* cursor, Node* node); It should link together the `cursor` and `node` nodes, putting `node` right after `cursor`. Make sure to properly update the `next` and `prev` fields of all relevant nodes, that is, the nodes pointed by the `cursor`, `cursor->next`, and `node` pointers. -You might assume that all nodes reachable from `cursor` through `next` and `prev` +You might assume that all nodes which are reachable from `cursor` through `next` and `prev` are valid nodes and none of their `next` or `prev` pointers are null (we will see why this is true for our intended list implementation later). @@ -91,7 +91,7 @@ struct List { For this scheme to work properly, we also have to initialize the `next` and `prev` fields of the `sentry` node. We can make them both point to the `sentry` node. -Therefore, in our encoding, an empty list is modelled as +Therefore, in our encoding, an empty list is modeled as a list consisting of a single sentinel node whose `next` and `prev` pointers form a cycle. Write code for the function `initList` implementing this idea (set the `data` field of the `sentry` node to `nullptr`): diff --git a/MemoryManagement/MemoryLayout/Swap/task-info.yaml b/MemoryManagement/MemoryLayout/Swap/task-info.yaml index a4871ca..27c3e2c 100644 --- a/MemoryManagement/MemoryLayout/Swap/task-info.yaml +++ b/MemoryManagement/MemoryLayout/Swap/task-info.yaml @@ -2,11 +2,11 @@ type: edu files: - name: CMakeLists.txt visible: false +- name: test/test.cpp + visible: false - name: src/task.cpp visible: true placeholders: - offset: 32 length: 36 placeholder_text: /* TODO */ -- name: test/test.cpp - visible: false diff --git a/MemoryManagement/TypeCastsAndCStrings/CStringConcat/task-info.yaml b/MemoryManagement/TypeCastsAndCStrings/CStringConcat/task-info.yaml index e7e08d6..4ab59e8 100644 --- a/MemoryManagement/TypeCastsAndCStrings/CStringConcat/task-info.yaml +++ b/MemoryManagement/TypeCastsAndCStrings/CStringConcat/task-info.yaml @@ -3,11 +3,11 @@ custom_name: C Style Strings Concatenation files: - name: CMakeLists.txt visible: false -- name: test/test.cpp - visible: false - name: src/task.cpp visible: true placeholders: - offset: 92 length: 201 placeholder_text: return nullptr; +- name: test/test.cpp + visible: false diff --git a/MemoryManagement/TypeCastsAndCStrings/CStyle/task.md b/MemoryManagement/TypeCastsAndCStrings/CStyle/task.md index 5fe3111..bd2fa8c 100644 --- a/MemoryManagement/TypeCastsAndCStrings/CStyle/task.md +++ b/MemoryManagement/TypeCastsAndCStrings/CStyle/task.md @@ -8,7 +8,7 @@ However, in general, C is not a strict subset of C++, meaning that there are a lot of various [incompatibilities](https://en.wikipedia.org/wiki/Compatibility_of_C_and_C%2B%2B) between these languages. Besides that, there are a lot of language features, idioms, and patterns that differ in C and C++. -These difference give rise to the mentioned C and C++ styles of doing things. +These differences give rise to the mentioned C and C++ styles of doing things. It is important to learn to read C-style code, even if you plan to follow purely the C++ style in the future. As of today, the C language has become a "cross-platform assembly language", diff --git a/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/CMakeLists.txt b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/CMakeLists.txt new file mode 100644 index 0000000..eaeca57 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.25) + +project(ObjectOrientedProgramming-ClassesAndObjects-CollisionsRevisited) + +set(SRC + src/main.cpp + src/engine.cpp src/scenes.cpp src/textures.cpp + src/scene.cpp src/statscene.cpp src/dynscene.cpp + src/gobject.cpp src/gobjectlist.cpp src/cgobject.cpp + src/player.cpp src/consumable.cpp src/enemy.cpp + src/collision.cpp src/direction.cpp + src/rectangle.cpp src/point.cpp + src/operators.cpp src/utils.cpp +) + +set(TEST + test/test.cpp) + +add_executable(${PROJECT_NAME}-run ${SRC}) + +configure_test_target(${PROJECT_NAME}-test "${SRC}" ${TEST}) + +prepare_sfml_framework_lesson_task( + "${CMAKE_CURRENT_SOURCE_DIR}/.." + ${PROJECT_NAME}-run + ${PROJECT_NAME}-test +) diff --git a/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/cgobject.cpp b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/cgobject.cpp new file mode 100644 index 0000000..1d49ba3 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/cgobject.cpp @@ -0,0 +1,47 @@ +#include "cgobject.hpp" + +#include "operators.hpp" + +CircleGameObject::CircleGameObject(Circle circle) + : circle(circle) + , status(GameObjectStatus::NORMAL) +{} + +Point2D CircleGameObject::getPosition() const { + return circle.center; +} + +void CircleGameObject::setPosition(Point2D position) { + circle.center = position; +} + +GameObjectStatus CircleGameObject::getStatus() const { + return status; +} + +void CircleGameObject::setStatus(GameObjectStatus newStatus) { + status = newStatus; +} + +Circle CircleGameObject::getCircle() const { + return circle; +} + +Rectangle CircleGameObject::getBoundingBox() const { + Point2D offset = { circle.radius, circle.radius }; + Point2D p1 = circle.center - offset; + Point2D p2 = circle.center + offset; + return createRectangle(p1, p2); +} + +void CircleGameObject::draw(sf::RenderWindow &window, TextureManager& textureManager) const { + const sf::Texture* texture = getTexture(textureManager); + if (texture == nullptr) + return; + sf::CircleShape shape; + shape.setPosition(circle.center.x, circle.center.y); + shape.setOrigin(circle.radius, circle.radius); + shape.setRadius(circle.radius); + shape.setTexture(texture); + window.draw(shape); +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/collision.cpp b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/collision.cpp new file mode 100644 index 0000000..9b56990 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/collision.cpp @@ -0,0 +1,21 @@ +#include + +#include "collision.hpp" +#include "cgobject.hpp" +#include "utils.hpp" + +CollisionInfo collisionInfo(const Circle& circle1, const Circle& circle2) { + CollisionInfo info; + info.distance = distance(circle1.center, circle2.center); + info.collide = (info.distance < circle1.radius + circle2.radius); + return info; +} + +CollisionInfo collisionInfo(const GameObject& object1, const GameObject& object2) { + const CircleGameObject* circleObject1 = dynamic_cast(&object1); + const CircleGameObject* circleObject2 = dynamic_cast(&object2); + if (circleObject1 && circleObject2) { + return collisionInfo(circleObject1->getCircle(), circleObject2->getCircle()); + } + assert(false); +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/consumable.cpp b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/consumable.cpp new file mode 100644 index 0000000..d03f046 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/consumable.cpp @@ -0,0 +1,46 @@ +#include "consumable.hpp" + +#include "constants.hpp" + +ConsumableObject::ConsumableObject() + : CircleGameObject({ { CONSUMABLE_START_X, CONSUMABLE_START_Y }, CONSUMABLE_RADIUS }) +{} + +GameObjectKind ConsumableObject::getKind() const { + return GameObjectKind::CONSUMABLE; +} + +Point2D ConsumableObject::getVelocity() const { + return { 0.0f, 0.0f }; +} + +void ConsumableObject::update(sf::Time delta) { + if (getStatus() != GameObjectStatus::DESTROYED) { + setStatus(GameObjectStatus::NORMAL); + } +} + +void ConsumableObject::onCollision(const GameObject &object, const CollisionInfo &info) { + if (getStatus() == GameObjectStatus::DESTROYED || object.getKind() == GameObjectKind::CONSUMABLE) { + return; + } + if (info.collide) { + setStatus(GameObjectStatus::DESTROYED); + return; + } + if (info.distance < CONSUMABLE_WARNED_MULTIPLIER * getCircle().radius) { + setStatus(GameObjectStatus::WARNED); + return; + } +} + +const sf::Texture* ConsumableObject::getTexture(TextureManager& textureManager) const { + switch (getStatus()) { + case GameObjectStatus::NORMAL: + return textureManager.getTexture(GameTextureID::STAR); + case GameObjectStatus::WARNED: + return textureManager.getTexture(GameTextureID::STAR_CONCERNED); + case GameObjectStatus::DESTROYED: + return nullptr; + } +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/direction.cpp b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/direction.cpp new file mode 100644 index 0000000..0a2b606 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/direction.cpp @@ -0,0 +1,16 @@ +#include "direction.hpp" + +Point2D getDirection(Direction direction) { + switch (direction) { + case North: + return { 0.0f, -1.0f }; + case East: + return { 1.0f, 0.0f }; + case South: + return { 0.0f, 1.0f }; + case West: + return { -1.0f, 0.0f }; + default: + return { 0.0f, 0.0f }; + } +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/dynscene.cpp b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/dynscene.cpp new file mode 100644 index 0000000..1dbcf2e --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/dynscene.cpp @@ -0,0 +1,144 @@ +#include "dynscene.hpp" + +#include "constants.hpp" +#include "player.hpp" +#include "consumable.hpp" +#include "enemy.hpp" +#include "utils.hpp" + +const int MAX_DYNAMIC_OBJECTS_ON_SCENE = 10; +const int MAX_ENEMY_OBJECTS_ON_SCENE = 4; + +const int NEW_DYNAMIC_OBJECT_PROB = 1; +const int NEW_ENEMY_OBJECT_PROB = 10; + +GameplayDynamicScene::GameplayDynamicScene() : Scene(SCENE_WIDTH, SCENE_HEIGHT) {} + +void GameplayDynamicScene::activate() { + addNewGameObject(GameObjectKind::PLAYER); +} + +void GameplayDynamicScene::deactivate() { + // remove all the objects from the list + objects.remove([&] (const GameObject& object) { + return true; + }); +} + +SceneID GameplayDynamicScene::getID() const { + return SceneID::DYNAMIC_GAME_FIELD; +} + +SceneID GameplayDynamicScene::getNextSceneID() const { + return SceneID::DYNAMIC_GAME_FIELD; +} + +void GameplayDynamicScene::processEvent(const sf::Event& event) { + return; +} + +void GameplayDynamicScene::update(sf::Time delta) { + // update the objects' state + objects.foreach([delta] (GameObject& object) { + object.update(delta); + }); + // move objects according to their velocity and elapsed time + objects.foreach([this, delta] (GameObject& object) { + move(object, delta); + }); + // compute collision information for each pair of objects + objects.foreach([this] (GameObject& object) { + objects.foreach([this, &object] (GameObject& other) { + if (&object != &other) { + detectCollision(object, other); + } + }); + }); + // update the list of objects + updateObjectsList(); +} + +void GameplayDynamicScene::updateObjectsList() { + // remove destroyed objects from the list + objects.remove([] (const GameObject& object) { + return (object.getStatus() == GameObjectStatus::DESTROYED) + && (object.getKind() != GameObjectKind::PLAYER); + }); + // count the number of the different kinds of objects present on the scene + int consumableCount = 0; + int enemyCount = 0; + objects.foreach([&] (const GameObject& object) { + switch (object.getKind()) { + case GameObjectKind::CONSUMABLE: + ++consumableCount; + break; + case GameObjectKind::ENEMY: + ++enemyCount; + break; + default: + break; + } + }); + // add new objects of randomly chosen kind if there is enough room for them on the scene + int dynamicObjectsCount = consumableCount + enemyCount; + if (dynamicObjectsCount < MAX_DYNAMIC_OBJECTS_ON_SCENE) { + int r = rand() % 100; + int k = rand() % 100; + if (r < NEW_DYNAMIC_OBJECT_PROB) { + if (k < NEW_ENEMY_OBJECT_PROB && enemyCount < MAX_ENEMY_OBJECTS_ON_SCENE) { + addNewGameObject(GameObjectKind::ENEMY); + } else if (k > NEW_ENEMY_OBJECT_PROB) { + addNewGameObject(GameObjectKind::CONSUMABLE); + } + } + } +} + +std::shared_ptr GameplayDynamicScene::addNewGameObject(GameObjectKind kind) { + std::shared_ptr object; + while (!object) { + // create an object with default position + switch (kind) { + case GameObjectKind::PLAYER: { + object = std::make_shared(); + break; + } + case GameObjectKind::CONSUMABLE: { + object = std::make_shared(); + break; + } + case GameObjectKind::ENEMY: { + object = std::make_shared(); + break; + } + } + // set random position for consumable and enemy objects + if (kind == GameObjectKind::CONSUMABLE || + kind == GameObjectKind::ENEMY) { + setObjectPosition(*object, generatePoint(getBoundingBox())); + } else { + fitInto(*object); + } + // check that object does not collide with existing objects + bool collide = false; + objects.foreach([&collide, &object](GameObject& other) { + collide |= collisionInfo(*object, other).collide; + }); + // reset a colliding object + if (collide) { + object = nullptr; + } + } + objects.insert(object); + return object; +} + +void GameplayDynamicScene::draw(sf::RenderWindow &window, TextureManager& textureManager) { + // draw background + drawBackground(window, textureManager.getTexture(GameTextureID::SPACE)); + // draw all objects on the scene + objects.foreach([&] (const GameObject& object) { + object.draw(window, textureManager); + }); +} + diff --git a/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/enemy.cpp b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/enemy.cpp new file mode 100644 index 0000000..3bb34d8 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/enemy.cpp @@ -0,0 +1,44 @@ +#include "enemy.hpp" + +#include "constants.hpp" +#include "operators.hpp" +#include "utils.hpp" + +EnemyObject::EnemyObject() + : CircleGameObject({ { ENEMY_START_X, ENEMY_START_Y }, ENEMY_RADIUS }) +{} + +GameObjectKind EnemyObject::getKind() const { + return GameObjectKind::ENEMY; +} + +Point2D EnemyObject::getVelocity() const { + return velocity; +} + +void EnemyObject::update(sf::Time delta) { + if (CircleGameObject::getStatus() == GameObjectStatus::DESTROYED) + return; + updateTimer += delta; + if (updateTimer < sf::seconds(1.0f)) + return; + updateTimer = sf::milliseconds(0.0f); + updateVelocity(); +} + +void EnemyObject::setVelocity(Point2D velocity) { + this->velocity = velocity; +} + +void EnemyObject::updateVelocity() { + // TODO: write your solution here +} + +void EnemyObject::onCollision(const GameObject &object, const CollisionInfo &info) { + return; +} + +const sf::Texture* EnemyObject::getTexture(TextureManager& textureManager) const { + // TODO: write your solution here + return nullptr; +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/engine.cpp b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/engine.cpp new file mode 100644 index 0000000..32ca01d --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/engine.cpp @@ -0,0 +1,89 @@ +#include "engine.hpp" + +#include "constants.hpp" + +GameEngine *GameEngine::create() { + static GameEngine engine; + return &engine; +} + +GameEngine::GameEngine() + : scene(nullptr) + , active(true) +{ + // initialize random number generator + srand(time(nullptr)); + // initialize resource managers + active &= sceneManager.initialize(); + active &= textureManager.initialize(); + if (!active) { + return; + } + // set the current scene + scene = sceneManager.getCurrentScene(); + // initialize the application window + window.create(sf::VideoMode(WINDOW_WIDTH, WINDOW_HEIGHT), "Space Game"); + window.setFramerateLimit(60); +} + +void GameEngine::run() { + sf::Clock clock; + while (isActive()) { + sf::Time delta = clock.restart(); + processInput(); + update(delta); + render(); + sceneTransition(); + } +} + +bool GameEngine::isActive() const { + return active && window.isOpen(); +} + +void GameEngine::close() { + active = false; + window.close(); +} + +void GameEngine::processInput() { + sf::Event event; + while (window.pollEvent(event)) { + processEvent(event); + } +} + +void GameEngine::processEvent(const sf::Event &event) { + switch (event.type) { + case sf::Event::Closed: + close(); + break; + default: + scene->processEvent(event); + break; + } +} + +void GameEngine::update(sf::Time delta) { + scene->update(delta); +} + +void GameEngine::render() { + window.clear(sf::Color::White); + scene->draw(window, textureManager); + window.display(); +} + +void GameEngine::sceneTransition() { + if (scene->getNextSceneID() != scene->getID()) { + sceneManager.transitionScene(scene->getNextSceneID()); + scene = sceneManager.getCurrentScene(); + resizeWindow(); + } +} + +void GameEngine::resizeWindow() { + Rectangle sceneBox = scene->getBoundingBox(); + sf::Vector2u sceneSize = sf::Vector2u(width(sceneBox), height(sceneBox)); + window.setSize(sceneSize); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/gobject.cpp b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/gobject.cpp new file mode 100644 index 0000000..a673ef5 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/gobject.cpp @@ -0,0 +1,7 @@ +#include "gobject.hpp" + +#include "operators.hpp" + +void GameObject::move(Point2D vector) { + setPosition(getPosition() + vector); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/gobjectlist.cpp b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/gobjectlist.cpp new file mode 100644 index 0000000..5b8b35e --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/gobjectlist.cpp @@ -0,0 +1,54 @@ +#include "gobjectlist.hpp" + +void GameObjectList::link(GameObjectList::Node *cursor, std::unique_ptr &&node) { + // TODO: write your solution here +} + +void GameObjectList::unlink(GameObjectList::Node *node) { + // TODO: write your solution here +} + +GameObjectList::GameObjectList() { + // TODO: write your solution here +} + +void GameObjectList::insert(const std::shared_ptr &object) { + // TODO: write your solution here +} + +void GameObjectList::remove(const std::function &pred) { + GameObjectList::Node* curr = head->next.get(); + while (curr != tail) { + Node* next = curr->next.get(); + if (pred(*curr->object)) { + unlink(curr); + } + curr = next; + } +} + +void GameObjectList::foreach(const std::function& apply) { + Node* curr = head->next.get(); + while (curr != tail) { + apply(*curr->object); + curr = curr->next.get(); + } +} + +GameObjectList::GameObjectList(const GameObjectList &other) : GameObjectList() { + // TODO: write your solution here +} + +GameObjectList::GameObjectList(GameObjectList &&other) noexcept : GameObjectList() { + // TODO: write your solution here +} + +GameObjectList &GameObjectList::operator=(GameObjectList other) { + // TODO: write your solution here + return *this; +} + +void swap(GameObjectList& first, GameObjectList& second) { + using std::swap; + // TODO: write your solution here +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/main.cpp b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/main.cpp new file mode 100644 index 0000000..b192818 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/main.cpp @@ -0,0 +1,13 @@ +#include "engine.hpp" + +#include + +int main() { + GameEngine* engine = GameEngine::create(); + if (!engine) { + std::cout << "Game engine is not created!\n"; + return 1; + } + GameEngine::create()->run(); + return 0; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/operators.cpp b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/operators.cpp new file mode 100644 index 0000000..4dc9097 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/operators.cpp @@ -0,0 +1,42 @@ +#include "game.hpp" + +Point2D operator+(Point2D a, Point2D b) { + return add(a, b); +} + +Point2D operator-(Point2D a) { + return { -a.x, -a.y }; +} + +Point2D operator-(Point2D a, Point2D b) { + return add(a, -b); +} + +Point2D operator*(float s, Point2D a) { + return mul(s, a); +} + +Circle operator+(Circle c, Point2D v) { + return { c.center + v, c.radius }; +} + +Circle operator-(Circle c, Point2D v) { + return { c.center - v, c.radius }; +} + +Rectangle operator+(Rectangle r, Point2D v) { + return { r.topLeft + v, r.botRight + v }; +} + +Rectangle operator-(Rectangle r, Point2D v) { + return { r.topLeft - v, r.botRight - v }; +} + +Circle operator*(float s, Circle c) { + return { c.center, s * c.radius }; +} + +Rectangle operator*(float s, Rectangle r) { + Point2D v = { width(r), height(r) }; + return { r.topLeft, r.topLeft + s * v }; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/player.cpp b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/player.cpp new file mode 100644 index 0000000..452577b --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/player.cpp @@ -0,0 +1,54 @@ +#include "player.hpp" + +#include "constants.hpp" +#include "direction.hpp" +#include "operators.hpp" + +PlayerObject::PlayerObject() + : CircleGameObject({ { PLAYER_START_X, PLAYER_START_Y }, RADIUS }) +{} + +GameObjectKind PlayerObject::getKind() const { + return GameObjectKind::PLAYER; +} + +Point2D PlayerObject::getVelocity() const { + Point2D velocity = { 0.0f, 0.0f }; + if (CircleGameObject::getStatus() == GameObjectStatus::DESTROYED) + return velocity; + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) { + velocity = velocity + getDirection(North); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) { + velocity = velocity + getDirection(East); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) { + velocity = velocity + getDirection(South); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) { + velocity = velocity + getDirection(West); + } + velocity = SPEED * velocity; + return velocity; +} + +void PlayerObject::update(sf::Time delta) { + return; +} + +const sf::Texture* PlayerObject::getTexture(TextureManager& textureManager) const { + switch (getStatus()) { + case GameObjectStatus::NORMAL: + return textureManager.getTexture(GameTextureID::PLANET); + case GameObjectStatus::WARNED: + return textureManager.getTexture(GameTextureID::PLANET); + case GameObjectStatus::DESTROYED: + return textureManager.getTexture(GameTextureID::PLANET_DEAD); + default: + return nullptr; + } +} + +void PlayerObject::onCollision(const GameObject &object, const CollisionInfo &info) { + // TODO: write your solution here +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/point.cpp b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/point.cpp new file mode 100644 index 0000000..5c74f69 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/point.cpp @@ -0,0 +1,19 @@ +#include "game.hpp" + +Point2D add(Point2D a, Point2D b) { + Point2D c = { 0, 0 }; + c.x = a.x + b.x; + c.y = a.y + b.y; + return c; +} + +Point2D mul(float s, Point2D a) { + Point2D b = { 0, 0 }; + b.x = s * a.x; + b.y = s * a.y; + return b; +} + +Point2D move(Point2D position, Point2D velocity, float delta) { + return add(position, mul(delta, velocity)); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/rectangle.cpp b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/rectangle.cpp new file mode 100644 index 0000000..2b5a1ec --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/rectangle.cpp @@ -0,0 +1,35 @@ +#include "rectangle.hpp" + +#include "operators.hpp" + +Point2D center(const Rectangle& rect) { + // TODO: explain this C++ initialization syntax + return rect.topLeft + 0.5f * Point2D { width(rect), height(rect) }; +} + +Rectangle createRectangle(Point2D p1, Point2D p2) { + Rectangle rect; + rect.topLeft = { std::min(p1.x, p2.x), std::min(p1.y, p2.y) }; + rect.botRight = { std::max(p1.x, p2.x), std::max(p1.y, p2.y) }; + return rect; +} + +Rectangle fitInto(const Rectangle& rect, const Rectangle& intoRect) { + if (width(rect) > width(intoRect) || height(rect) > height(intoRect)) { + return rect; + } + Point2D vector = { 0.0f, 0.0f }; + if (rect.topLeft.x < intoRect.topLeft.x) { + vector.x += intoRect.topLeft.x - rect.topLeft.x; + } + if (rect.topLeft.y < intoRect.topLeft.y) { + vector.y += intoRect.topLeft.y - rect.topLeft.y; + } + if (rect.botRight.x > intoRect.botRight.x) { + vector.x += intoRect.botRight.x - rect.botRight.x; + } + if (rect.botRight.y > intoRect.botRight.y) { + vector.y += intoRect.botRight.y - rect.botRight.y; + } + return rect + vector; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/scene.cpp b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/scene.cpp new file mode 100644 index 0000000..1457361 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/scene.cpp @@ -0,0 +1,47 @@ +#include "scene.hpp" + +#include "operators.hpp" + +Scene::Scene(float width, float height) + : width(width) + , height(height) +{} + +Rectangle Scene::getBoundingBox() const { + Rectangle box; + box.topLeft = { 0.0f, 0.0f }; + box.botRight = { width, height }; + return box; +} + +void Scene::setObjectPosition(GameObject& object, Point2D position) { + object.setPosition(position); + fitInto(object); +} + +void Scene::move(GameObject &object, Point2D vector) { + object.move(vector); + fitInto(object); +} + +void Scene::move(GameObject& object, sf::Time delta) { + move(object, 0.001f * delta.asMilliseconds() * object.getVelocity()); +} + +void Scene::fitInto(GameObject &object) { + Rectangle rect = ::fitInto(object.getBoundingBox(), getBoundingBox()); + object.setPosition(center(rect)); +} + +void Scene::detectCollision(GameObject& object1, GameObject& object2) { + CollisionInfo info = collisionInfo(object1, object2); + object1.onCollision(object2, info); + object2.onCollision(object1, info); +} + +void Scene::drawBackground(sf::RenderWindow &window, const sf::Texture* texture) const { + sf::Sprite background; + background.setTexture(*texture); + background.setTextureRect(sf::IntRect(0, 0, width, height)); + window.draw(background); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/scenes.cpp b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/scenes.cpp new file mode 100644 index 0000000..7125bb8 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/scenes.cpp @@ -0,0 +1,14 @@ +#include "scenes.hpp" + +bool SceneManager::initialize() { + staticScene.activate(); + return true; +} + +Scene* SceneManager::getCurrentScene() { + return &staticScene; +} + +void SceneManager::transitionScene(SceneID id) { + return; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/statscene.cpp b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/statscene.cpp new file mode 100644 index 0000000..a6abdfa --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/statscene.cpp @@ -0,0 +1,46 @@ +#include "statscene.hpp" + +#include "constants.hpp" + +GameplayStaticScene::GameplayStaticScene() : Scene(SCENE_WIDTH, SCENE_HEIGHT) {} + +void GameplayStaticScene::activate() { + fitInto(player); + fitInto(consumable); + fitInto(enemy); +} + +void GameplayStaticScene::deactivate() { + return; +} + +SceneID GameplayStaticScene::getID() const { + return SceneID::STATIC_GAME_FIELD; +} + +SceneID GameplayStaticScene::getNextSceneID() const { + return SceneID::STATIC_GAME_FIELD; +} + +void GameplayStaticScene::processEvent(const sf::Event& event) { + return; +} + +void GameplayStaticScene::update(sf::Time delta) { + player.update(delta); + consumable.update(delta); + enemy.update(delta); + move(player, delta); + move(consumable, delta); + move(enemy, delta); + detectCollision(player, consumable); + detectCollision(enemy, player); + detectCollision(enemy, consumable); +} + +void GameplayStaticScene::draw(sf::RenderWindow &window, TextureManager& textureManager) { + drawBackground(window, textureManager.getTexture(GameTextureID::SPACE)); + player.draw(window, textureManager); + consumable.draw(window, textureManager); + enemy.draw(window, textureManager); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/textures.cpp b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/textures.cpp new file mode 100644 index 0000000..2b5abcc --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/textures.cpp @@ -0,0 +1,42 @@ +#include "textures.hpp" + +#include + +const char* getTextureFilename(GameTextureID id) { + switch (id) { + case GameTextureID::SPACE: + return "resources/space.png"; + case GameTextureID::PLANET: + return "resources/planet.png"; + case GameTextureID::PLANET_DEAD: + return "resources/planetDead.png"; + case GameTextureID::STAR: + return "resources/star.png"; + case GameTextureID::STAR_CONCERNED: + return "resources/starConcerned.png"; + case GameTextureID::BLACKHOLE: + return "resources/blackhole.png"; + default: + return ""; + } +} + +bool TextureManager::initialize() { + for (size_t i = 0; i < SIZE; ++i) { + GameTextureID id = static_cast(i); + const char* filename = getTextureFilename(id); + sf::Texture* texture = &textures[i]; + if (!texture->loadFromFile(filename)) { + std::cerr << "Could not open file " << filename << "\n"; + return false; + } + if (id == GameTextureID::SPACE) { + texture->setRepeated(true); + } + } + return true; +} + +const sf::Texture* TextureManager::getTexture(GameTextureID id) const { + return &textures[static_cast(id)]; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/utils.cpp b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/utils.cpp new file mode 100644 index 0000000..783fc7a --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/src/utils.cpp @@ -0,0 +1,51 @@ +#include "utils.hpp" + +#include +#include +#include + +float distance(Point2D a, Point2D b) { + float dx = a.x - b.x; + float dy = a.y - b.y; + return sqrt(dx * dx + dy * dy); +} + +float generateFloat(float min, float max) { + return min + (rand() / (RAND_MAX / (max - min))); +} + +int generateInt(int min, int max) { + return min + rand() % (max - min + 1); +} + +bool generateBool(float prob) { + return generateFloat(0.0f, 1.0f) < prob; +} + +Point2D generatePoint(const Rectangle& boundingBox) { + Point2D point = { 0.0f, 0.0f, }; + point.x = generateFloat(boundingBox.topLeft.x, boundingBox.botRight.x); + point.y = generateFloat(boundingBox.topLeft.y, boundingBox.botRight.y); + return point; +} + +Circle generateCircle(float radius, const Rectangle& boundingBox) { + Circle circle = { { 0.0f, 0.0f }, 0.0f }; + if (radius > std::min(width(boundingBox), height(boundingBox))) { + return circle; + } + circle.center.x = generateFloat( + boundingBox.topLeft.x + radius, + boundingBox.botRight.x - radius + ); + circle.center.y = generateFloat( + boundingBox.topLeft.x + radius, + boundingBox.botRight.x - radius + ); + circle.radius = radius; + return circle; +} + +Rectangle generateRectangle(const Rectangle& boundingBox) { + return createRectangle(generatePoint(boundingBox), generatePoint(boundingBox)); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/task-info.yaml b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/task-info.yaml new file mode 100644 index 0000000..c50488d --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/task-info.yaml @@ -0,0 +1,46 @@ +type: edu +custom_name: Collisions Revisited +files: +- name: src/consumable.cpp + visible: true +- name: src/engine.cpp + visible: true +- name: src/scenes.cpp + visible: true +- name: src/textures.cpp + visible: true +- name: src/scene.cpp + visible: true +- name: src/statscene.cpp + visible: true +- name: src/dynscene.cpp + visible: true +- name: src/gobject.cpp + visible: true +- name: src/gobjectlist.cpp + visible: true +- name: src/cgobject.cpp + visible: true +- name: src/player.cpp + visible: true +- name: src/enemy.cpp + visible: true +- name: src/collision.cpp + visible: true +- name: src/direction.cpp + visible: true +- name: src/rectangle.cpp + visible: true +- name: src/point.cpp + visible: true +- name: src/operators.cpp + visible: true +- name: src/utils.cpp + visible: true +- name: CMakeLists.txt + visible: false +- name: src/main.cpp + visible: true + editable: false +- name: test/test.cpp + visible: false diff --git a/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/task-remote-info.yaml b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/task-remote-info.yaml new file mode 100644 index 0000000..a103451 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/task-remote-info.yaml @@ -0,0 +1 @@ +id: 1504863152 diff --git a/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/task.md b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/task.md new file mode 100644 index 0000000..ba2b3d0 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/task.md @@ -0,0 +1,91 @@ +Now, let us restore the collision detection functionality in our refactored game. + +We have already changed the signature of the collision detection function. + +```c++ +CollisionInfo collisionInfo(const Circle& circle1, const Circle& circle2); +``` + +Now, it takes two circle shapes via constant references. +Instead of a boolean flag indicating whether a collision has occurred, +it returns the `CollisionInfo` structure: + +```c++ +struct CollisionInfo { + bool collide; + float distance; +}; +``` + +This design will give us the flexibility to compute various information +about a possible collision between two objects inside the `collisionInfo` function, +and it preserves the opportunity for other modules of our game to decide what to do with this information. +For now, we will store the boolean flag `collide`, indicating whether a collision has occurred, +and the computed `distance` between two objects. However, you +will have the opportunity to later extend this structure +with any additional information required to implement new features in our game. + +You might wonder why we decided to model `CollisionInfo` as a structure, +rather than using the fancy objects we learned about in this lesson. +In fact, we did so purposefully to illustrate the following point. +Structures, declared via `struct`, and objects/classes, declared via `class`, +are not incompatible concepts in C++, +and often instances of both can be found within the same codebase. + +* Objects are used to tie together data (fields) and behavior (methods). + Objects provide _encapsulation_ and _polymorphism_. + The state of objects can satisfy various invariants, + maintained by carefully controlling the visibility of a class' members. + +* Structures are used as simple data containers. + They have a predictable memory layout and predictable behavior — + there are no associated virtual methods dispatched at runtime. + +In C++, structures are also sometimes referred to as [_POD types_](https://en.wikipedia.org/wiki/Passive_data_structure), +where POD stands for _plain old data_. + +
+ +Technically, in C++, there is no big difference between the `class` and `struct` keywords. +For example, one can declare classes using the `struct` keyword, and vice versa. +The only real difference is that : +* in a `struct`, members have `public` visibility by default; +* in a `class`, members have `private` visibility by default. + +However, a prevalent convention among C++ developers is +to use the `class` keyword for declaring actual classes in the object-oriented programming sense, +while the `struct` keyword is used to declare POD types. + +
+ +Now, with the help of the new collision detection function, +your task is to reimplement the behavior of consumable objects. +- Upon colliding with another object, the consumable should change its status to `DESTROYED`. +- When another object is approaching the consumable, it should change its status to `WARNED`. + This should happen whenever the distance between the other object and the consumable is less than `C * r`, where + - `r` is the radius of the consumable object, + - `C` is a special multiplier constant. + +```c++ +const float CONSUMABLE_WARNED_MULTIPLIER = 6.0f; +``` + +To do so, please implement the `onCollision` method of the `ConsumableObject` class. +This method is periodically called from the `Scene` class, notifying the object +about potential collisions with other objects. +The function takes another object as its first argument, +and the `CollisionInfo` structure as the second, +which contains the information about the distance between the objects +and whether they actually collided. +It is up to the method's implementation to decide what to do with this information. + +Note that when the consumable object becomes `WARNED`, it should eventually +change its status back to `NORMAL` once other objects have left its nearby area. +This can be achieved by also modifying the implementation of the `update` method. +This method is also periodically called from the `Scene` class to enable +the object to update its internal state. +This function takes as its single argument the amount of time elapsed since the last update, +although you will not need this information for the current task. +Reset the status of the live (that is, not `DESTROYED`!) consumable back to `NORMAL`. +If there are any objects nearby, they will be detected again during an `onCollision` call; +otherwise, the consumable object will remain in the `NORMAL` status. diff --git a/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/test/test.cpp b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/test/test.cpp new file mode 100644 index 0000000..3ecde6d --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/CollisionsRevisited/test/test.cpp @@ -0,0 +1,219 @@ +#include + +#include "consumable.hpp" +#include "operators.hpp" + +#include "testscene.hpp" +#include "testing.hpp" + +testing::Environment* const env = + testing::AddGlobalTestEnvironment(new TestEnvironment); + +const float MIN = -10e3; +const float MAX = 10e3; + +const Rectangle generateArea = { { MIN, MIN }, { MAX, MAX } }; + +std::string onCollision_error_msg(GameObject* object, GameObject* other, const CollisionInfo& info, + GameObjectStatus expected, GameObjectStatus actual) { + std::ostringstream stream; + stream << "Testing expression:\n" + << " object.onCollision(other, info)" << "\n"; + stream << "Test data:" << "\n" + << " object.getKind() = " << object->getKind() << "\n" + << " object.getStatus() = " << object->getStatus() << "\n" + << " other.getKind() = " << other->getKind() << "\n" + << " other.getStatus() = " << other->getStatus() << "\n" + << " info = " << info << "\n"; + stream << "Expected result:\n" + << " object.getStatus() = " << expected << "\n"; + stream << "Actual result:\n" + << " object.getStatus() = " << actual << "\n"; + return stream.str(); +} + +TEST(ConsumableObjectTest, OnCollisionDestroyTest) { + TestConsumableObject object = TestConsumableObject(); + object.performSetStatus(GameObjectStatus::NORMAL); + + TestGameObject other = TestGameObject(); + other.setKind(GameObjectKind::PLAYER); + other.setStatus(GameObjectStatus::NORMAL); + + CollisionInfo info = CollisionInfo { true, 0.0f }; + TestConsumableObject actual = object; + actual.onCollision(other, info); + + ASSERT_EQ(GameObjectStatus::DESTROYED, actual.getStatus()) + << onCollision_error_msg(&object, &other, info, GameObjectStatus::DESTROYED, actual.getStatus()) + << "The status of consumable object after a collision with another non-consumable object should become GameObjectStatus::DESTROYED"; +} + +TEST(ConsumableObjectTest, OnCollisionDestroyWarnedTest) { + TestConsumableObject object = TestConsumableObject(); + object.performSetStatus(GameObjectStatus::WARNED); + + TestGameObject other = TestGameObject(); + other.setKind(GameObjectKind::PLAYER); + other.setStatus(GameObjectStatus::NORMAL); + + CollisionInfo info = CollisionInfo { true, 0.0f }; + TestConsumableObject actual = object; + actual.onCollision(other, info); + + ASSERT_EQ(GameObjectStatus::DESTROYED, actual.getStatus()) + << onCollision_error_msg(&object, &other, info, GameObjectStatus::DESTROYED, actual.getStatus()) + << "The status of consumable object after a collision with another non-consumable object should become GameObjectStatus::DESTROYED"; +} + +TEST(ConsumableObjectTest, OnCollisionWarnTest) { + TestConsumableObject object = TestConsumableObject(); + object.performSetStatus(GameObjectStatus::NORMAL); + + TestGameObject other = TestGameObject(); + other.setKind(GameObjectKind::PLAYER); + other.setStatus(GameObjectStatus::NORMAL); + + CollisionInfo info = CollisionInfo { false, object.getCircle().radius + 10.0f }; + TestConsumableObject actual = object; + actual.onCollision(other, info); + + ASSERT_EQ(GameObjectStatus::WARNED, actual.getStatus()) + << onCollision_error_msg(&object, &other, info, GameObjectStatus::WARNED, actual.getStatus()) + << "The status of consumable object after another non-consumable object approached it should become GameObjectStatus::WARNED"; +} + +TEST(ConsumableObjectTest, OnCollisionConsumableTest) { + TestConsumableObject object = TestConsumableObject(); + object.performSetStatus(GameObjectStatus::NORMAL); + + TestGameObject other = TestGameObject(); + other.setKind(GameObjectKind::CONSUMABLE); + other.setStatus(GameObjectStatus::NORMAL); + + CollisionInfo info = CollisionInfo { true, 0.0f }; + TestConsumableObject actual = object; + actual.onCollision(other, info); + + ASSERT_EQ(GameObjectStatus::NORMAL, actual.getStatus()) + << onCollision_error_msg(&object, &other, info, GameObjectStatus::NORMAL, actual.getStatus()) + << "The status of consumable object after a collision with another consumable object should not change"; +} + +TEST(ConsumableObjectTest, OnCollisionConsumableWarningTest) { + TestConsumableObject object = TestConsumableObject(); + object.performSetStatus(GameObjectStatus::NORMAL); + + TestGameObject other = TestGameObject(); + other.setKind(GameObjectKind::CONSUMABLE); + other.setStatus(GameObjectStatus::NORMAL); + + CollisionInfo info = CollisionInfo { false, object.getCircle().radius + 10.0f }; + TestConsumableObject actual = object; + actual.onCollision(other, info); + + ASSERT_EQ(GameObjectStatus::NORMAL, actual.getStatus()) + << onCollision_error_msg(&object, &other, info, GameObjectStatus::NORMAL, actual.getStatus()) + << "The status of consumable object after another consumable object approached it should not change"; +} + +TEST(ConsumableObjectTest, OnCollisionDestroyedTest) { + TestConsumableObject object = TestConsumableObject(); + object.performSetStatus(GameObjectStatus::DESTROYED); + + TestGameObject other = TestGameObject(); + other.setKind(GameObjectKind::PLAYER); + other.setStatus(GameObjectStatus::NORMAL); + + CollisionInfo info = CollisionInfo { true, 0.0f }; + TestConsumableObject actual = object; + actual.onCollision(other, info); + + ASSERT_EQ(GameObjectStatus::DESTROYED, actual.getStatus()) + << onCollision_error_msg(&object, &other, info, GameObjectStatus::DESTROYED, actual.getStatus()) + << "The status of destroyed consumable object after a collision should remain GameObjectStatus::DESTROYED"; +} + +TEST(ConsumableObjectTest, OnCollisionDestroyedWarningTest) { + TestConsumableObject object = TestConsumableObject(); + object.performSetStatus(GameObjectStatus::DESTROYED); + + TestGameObject other = TestGameObject(); + other.setKind(GameObjectKind::PLAYER); + other.setStatus(GameObjectStatus::NORMAL); + + CollisionInfo info = CollisionInfo { false, object.getCircle().radius + 10.0f }; + TestConsumableObject actual = object; + actual.onCollision(other, info); + + ASSERT_EQ(GameObjectStatus::DESTROYED, actual.getStatus()) + << onCollision_error_msg(&object, &other, info, GameObjectStatus::DESTROYED, actual.getStatus()) + << "The status of destroyed consumable object after a another object approached it should remain GameObjectStatus::DESTROYED"; +} + +TEST(ConsumableObjectTest, OnCollisionDestroyedNoCollisionTest) { + TestConsumableObject object = TestConsumableObject(); + object.performSetStatus(GameObjectStatus::DESTROYED); + + TestGameObject other = TestGameObject(); + other.setKind(GameObjectKind::PLAYER); + other.setStatus(GameObjectStatus::NORMAL); + + CollisionInfo info = CollisionInfo { false, 2 * CONSUMABLE_WARNED_MULTIPLIER * object.getCircle().radius }; + TestConsumableObject actual = object; + actual.onCollision(other, info); + + ASSERT_EQ(GameObjectStatus::DESTROYED, actual.getStatus()) + << onCollision_error_msg(&object, &other, info, GameObjectStatus::DESTROYED, actual.getStatus()) + << "The status of destroyed consumable object should remain GameObjectStatus::DESTROYED"; +} + +std::string update_error_msg(GameObject* object, GameObjectStatus expected, GameObjectStatus actual) { + std::ostringstream stream; + stream << "Testing expression:\n" + << " object.update(delta)" << "\n"; + stream << "Test data:" << "\n" + << " object.getKind() = " << object->getKind() << "\n" + << " object.getStatus() = " << object->getStatus() << "\n"; + stream << "Expected result:\n" + << " object.getStatus() = " << expected << "\n"; + stream << "Actual result:\n" + << " object.getStatus() = " << actual << "\n"; + return stream.str(); +} + +TEST(ConsumableObjectTest, UpdateNormalTest) { + TestConsumableObject object = TestConsumableObject(); + object.performSetStatus(GameObjectStatus::NORMAL); + + TestConsumableObject actual = object; + actual.update(sf::milliseconds(10)); + + ASSERT_EQ(GameObjectStatus::NORMAL, actual.getStatus()) + << update_error_msg(&object, GameObjectStatus::NORMAL, actual.getStatus()) + << "The status of consumable object after an update should remain GameObjectStatus::NORMAL"; +} + +TEST(ConsumableObjectTest, UpdateWarnedTest) { + TestConsumableObject object = TestConsumableObject(); + object.performSetStatus(GameObjectStatus::WARNED); + + TestConsumableObject actual = object; + actual.update(sf::milliseconds(10)); + + ASSERT_EQ(GameObjectStatus::NORMAL, actual.getStatus()) + << update_error_msg(&object, GameObjectStatus::NORMAL, actual.getStatus()) + << "The status of warned consumable object after an update should become GameObjectStatus::NORMAL"; +} + +TEST(ConsumableObjectTest, UpdateDestroyedTest) { + TestConsumableObject object = TestConsumableObject(); + object.performSetStatus(GameObjectStatus::DESTROYED); + + TestConsumableObject actual = object; + actual.update(sf::milliseconds(10)); + + ASSERT_EQ(GameObjectStatus::DESTROYED, actual.getStatus()) + << update_error_msg(&object, GameObjectStatus::DESTROYED, actual.getStatus()) + << "The status of destroyed consumable object after an update should remain GameObjectStatus::DESTROYED"; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/CMakeLists.txt b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/CMakeLists.txt new file mode 100644 index 0000000..8a7f54e --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.25) + +project(ObjectOrientedProgramming-ClassesAndObjects-Encapsulation) + +set(SRC + src/main.cpp + src/engine.cpp src/scenes.cpp src/textures.cpp + src/scene.cpp src/statscene.cpp src/dynscene.cpp + src/gobject.cpp src/gobjectlist.cpp src/cgobject.cpp + src/player.cpp src/consumable.cpp src/enemy.cpp + src/collision.cpp src/direction.cpp + src/rectangle.cpp src/point.cpp + src/operators.cpp src/utils.cpp +) + +set(TEST + test/test.cpp) + +add_executable(${PROJECT_NAME}-run ${SRC}) + +configure_test_target(${PROJECT_NAME}-test "${SRC}" ${TEST}) + +prepare_sfml_framework_lesson_task( + "${CMAKE_CURRENT_SOURCE_DIR}/.." + ${PROJECT_NAME}-run + ${PROJECT_NAME}-test +) diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/cgobject.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/cgobject.cpp new file mode 100644 index 0000000..1d49ba3 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/cgobject.cpp @@ -0,0 +1,47 @@ +#include "cgobject.hpp" + +#include "operators.hpp" + +CircleGameObject::CircleGameObject(Circle circle) + : circle(circle) + , status(GameObjectStatus::NORMAL) +{} + +Point2D CircleGameObject::getPosition() const { + return circle.center; +} + +void CircleGameObject::setPosition(Point2D position) { + circle.center = position; +} + +GameObjectStatus CircleGameObject::getStatus() const { + return status; +} + +void CircleGameObject::setStatus(GameObjectStatus newStatus) { + status = newStatus; +} + +Circle CircleGameObject::getCircle() const { + return circle; +} + +Rectangle CircleGameObject::getBoundingBox() const { + Point2D offset = { circle.radius, circle.radius }; + Point2D p1 = circle.center - offset; + Point2D p2 = circle.center + offset; + return createRectangle(p1, p2); +} + +void CircleGameObject::draw(sf::RenderWindow &window, TextureManager& textureManager) const { + const sf::Texture* texture = getTexture(textureManager); + if (texture == nullptr) + return; + sf::CircleShape shape; + shape.setPosition(circle.center.x, circle.center.y); + shape.setOrigin(circle.radius, circle.radius); + shape.setRadius(circle.radius); + shape.setTexture(texture); + window.draw(shape); +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/collision.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/collision.cpp new file mode 100644 index 0000000..9b56990 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/collision.cpp @@ -0,0 +1,21 @@ +#include + +#include "collision.hpp" +#include "cgobject.hpp" +#include "utils.hpp" + +CollisionInfo collisionInfo(const Circle& circle1, const Circle& circle2) { + CollisionInfo info; + info.distance = distance(circle1.center, circle2.center); + info.collide = (info.distance < circle1.radius + circle2.radius); + return info; +} + +CollisionInfo collisionInfo(const GameObject& object1, const GameObject& object2) { + const CircleGameObject* circleObject1 = dynamic_cast(&object1); + const CircleGameObject* circleObject2 = dynamic_cast(&object2); + if (circleObject1 && circleObject2) { + return collisionInfo(circleObject1->getCircle(), circleObject2->getCircle()); + } + assert(false); +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/consumable.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/consumable.cpp new file mode 100644 index 0000000..2d371e4 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/consumable.cpp @@ -0,0 +1,34 @@ +#include "consumable.hpp" + +#include "constants.hpp" + +ConsumableObject::ConsumableObject() + : CircleGameObject({ { CONSUMABLE_START_X, CONSUMABLE_START_Y }, CONSUMABLE_RADIUS }) +{} + +GameObjectKind ConsumableObject::getKind() const { + return GameObjectKind::CONSUMABLE; +} + +Point2D ConsumableObject::getVelocity() const { + return { 0.0f, 0.0f }; +} + +void ConsumableObject::update(sf::Time delta) { + // TODO: write your solution here +} + +void ConsumableObject::onCollision(const GameObject &object, const CollisionInfo &info) { + // TODO: write your solution here +} + +const sf::Texture* ConsumableObject::getTexture(TextureManager& textureManager) const { + switch (getStatus()) { + case GameObjectStatus::NORMAL: + return textureManager.getTexture(GameTextureID::STAR); + case GameObjectStatus::WARNED: + return textureManager.getTexture(GameTextureID::STAR_CONCERNED); + case GameObjectStatus::DESTROYED: + return nullptr; + } +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/direction.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/direction.cpp new file mode 100644 index 0000000..0a2b606 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/direction.cpp @@ -0,0 +1,16 @@ +#include "direction.hpp" + +Point2D getDirection(Direction direction) { + switch (direction) { + case North: + return { 0.0f, -1.0f }; + case East: + return { 1.0f, 0.0f }; + case South: + return { 0.0f, 1.0f }; + case West: + return { -1.0f, 0.0f }; + default: + return { 0.0f, 0.0f }; + } +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/dynscene.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/dynscene.cpp new file mode 100644 index 0000000..1dbcf2e --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/dynscene.cpp @@ -0,0 +1,144 @@ +#include "dynscene.hpp" + +#include "constants.hpp" +#include "player.hpp" +#include "consumable.hpp" +#include "enemy.hpp" +#include "utils.hpp" + +const int MAX_DYNAMIC_OBJECTS_ON_SCENE = 10; +const int MAX_ENEMY_OBJECTS_ON_SCENE = 4; + +const int NEW_DYNAMIC_OBJECT_PROB = 1; +const int NEW_ENEMY_OBJECT_PROB = 10; + +GameplayDynamicScene::GameplayDynamicScene() : Scene(SCENE_WIDTH, SCENE_HEIGHT) {} + +void GameplayDynamicScene::activate() { + addNewGameObject(GameObjectKind::PLAYER); +} + +void GameplayDynamicScene::deactivate() { + // remove all the objects from the list + objects.remove([&] (const GameObject& object) { + return true; + }); +} + +SceneID GameplayDynamicScene::getID() const { + return SceneID::DYNAMIC_GAME_FIELD; +} + +SceneID GameplayDynamicScene::getNextSceneID() const { + return SceneID::DYNAMIC_GAME_FIELD; +} + +void GameplayDynamicScene::processEvent(const sf::Event& event) { + return; +} + +void GameplayDynamicScene::update(sf::Time delta) { + // update the objects' state + objects.foreach([delta] (GameObject& object) { + object.update(delta); + }); + // move objects according to their velocity and elapsed time + objects.foreach([this, delta] (GameObject& object) { + move(object, delta); + }); + // compute collision information for each pair of objects + objects.foreach([this] (GameObject& object) { + objects.foreach([this, &object] (GameObject& other) { + if (&object != &other) { + detectCollision(object, other); + } + }); + }); + // update the list of objects + updateObjectsList(); +} + +void GameplayDynamicScene::updateObjectsList() { + // remove destroyed objects from the list + objects.remove([] (const GameObject& object) { + return (object.getStatus() == GameObjectStatus::DESTROYED) + && (object.getKind() != GameObjectKind::PLAYER); + }); + // count the number of the different kinds of objects present on the scene + int consumableCount = 0; + int enemyCount = 0; + objects.foreach([&] (const GameObject& object) { + switch (object.getKind()) { + case GameObjectKind::CONSUMABLE: + ++consumableCount; + break; + case GameObjectKind::ENEMY: + ++enemyCount; + break; + default: + break; + } + }); + // add new objects of randomly chosen kind if there is enough room for them on the scene + int dynamicObjectsCount = consumableCount + enemyCount; + if (dynamicObjectsCount < MAX_DYNAMIC_OBJECTS_ON_SCENE) { + int r = rand() % 100; + int k = rand() % 100; + if (r < NEW_DYNAMIC_OBJECT_PROB) { + if (k < NEW_ENEMY_OBJECT_PROB && enemyCount < MAX_ENEMY_OBJECTS_ON_SCENE) { + addNewGameObject(GameObjectKind::ENEMY); + } else if (k > NEW_ENEMY_OBJECT_PROB) { + addNewGameObject(GameObjectKind::CONSUMABLE); + } + } + } +} + +std::shared_ptr GameplayDynamicScene::addNewGameObject(GameObjectKind kind) { + std::shared_ptr object; + while (!object) { + // create an object with default position + switch (kind) { + case GameObjectKind::PLAYER: { + object = std::make_shared(); + break; + } + case GameObjectKind::CONSUMABLE: { + object = std::make_shared(); + break; + } + case GameObjectKind::ENEMY: { + object = std::make_shared(); + break; + } + } + // set random position for consumable and enemy objects + if (kind == GameObjectKind::CONSUMABLE || + kind == GameObjectKind::ENEMY) { + setObjectPosition(*object, generatePoint(getBoundingBox())); + } else { + fitInto(*object); + } + // check that object does not collide with existing objects + bool collide = false; + objects.foreach([&collide, &object](GameObject& other) { + collide |= collisionInfo(*object, other).collide; + }); + // reset a colliding object + if (collide) { + object = nullptr; + } + } + objects.insert(object); + return object; +} + +void GameplayDynamicScene::draw(sf::RenderWindow &window, TextureManager& textureManager) { + // draw background + drawBackground(window, textureManager.getTexture(GameTextureID::SPACE)); + // draw all objects on the scene + objects.foreach([&] (const GameObject& object) { + object.draw(window, textureManager); + }); +} + diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/enemy.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/enemy.cpp new file mode 100644 index 0000000..3bb34d8 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/enemy.cpp @@ -0,0 +1,44 @@ +#include "enemy.hpp" + +#include "constants.hpp" +#include "operators.hpp" +#include "utils.hpp" + +EnemyObject::EnemyObject() + : CircleGameObject({ { ENEMY_START_X, ENEMY_START_Y }, ENEMY_RADIUS }) +{} + +GameObjectKind EnemyObject::getKind() const { + return GameObjectKind::ENEMY; +} + +Point2D EnemyObject::getVelocity() const { + return velocity; +} + +void EnemyObject::update(sf::Time delta) { + if (CircleGameObject::getStatus() == GameObjectStatus::DESTROYED) + return; + updateTimer += delta; + if (updateTimer < sf::seconds(1.0f)) + return; + updateTimer = sf::milliseconds(0.0f); + updateVelocity(); +} + +void EnemyObject::setVelocity(Point2D velocity) { + this->velocity = velocity; +} + +void EnemyObject::updateVelocity() { + // TODO: write your solution here +} + +void EnemyObject::onCollision(const GameObject &object, const CollisionInfo &info) { + return; +} + +const sf::Texture* EnemyObject::getTexture(TextureManager& textureManager) const { + // TODO: write your solution here + return nullptr; +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/engine.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/engine.cpp new file mode 100644 index 0000000..c506cdf --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/engine.cpp @@ -0,0 +1,89 @@ +#include "engine.hpp" + +#include "constants.hpp" + +GameEngine *GameEngine::create() { + // TODO: write your solution here + return nullptr; +} + +GameEngine::GameEngine() + : scene(nullptr) + , active(true) +{ + // initialize random number generator + srand(time(nullptr)); + // initialize resource managers + active &= sceneManager.initialize(); + active &= textureManager.initialize(); + if (!active) { + return; + } + // set the current scene + scene = sceneManager.getCurrentScene(); + // initialize the application window + window.create(sf::VideoMode(WINDOW_WIDTH, WINDOW_HEIGHT), "Space Game"); + window.setFramerateLimit(60); +} + +void GameEngine::run() { + sf::Clock clock; + while (isActive()) { + sf::Time delta = clock.restart(); + processInput(); + update(delta); + render(); + sceneTransition(); + } +} + +bool GameEngine::isActive() const { + return active && window.isOpen(); +} + +void GameEngine::close() { + active = false; + window.close(); +} + +void GameEngine::processInput() { + sf::Event event; + while (window.pollEvent(event)) { + processEvent(event); + } +} + +void GameEngine::processEvent(const sf::Event &event) { + switch (event.type) { + case sf::Event::Closed: + close(); + break; + default: + scene->processEvent(event); + break; + } +} + +void GameEngine::update(sf::Time delta) { + scene->update(delta); +} + +void GameEngine::render() { + window.clear(sf::Color::White); + scene->draw(window, textureManager); + window.display(); +} + +void GameEngine::sceneTransition() { + if (scene->getNextSceneID() != scene->getID()) { + sceneManager.transitionScene(scene->getNextSceneID()); + scene = sceneManager.getCurrentScene(); + resizeWindow(); + } +} + +void GameEngine::resizeWindow() { + Rectangle sceneBox = scene->getBoundingBox(); + sf::Vector2u sceneSize = sf::Vector2u(width(sceneBox), height(sceneBox)); + window.setSize(sceneSize); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/gobject.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/gobject.cpp new file mode 100644 index 0000000..a673ef5 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/gobject.cpp @@ -0,0 +1,7 @@ +#include "gobject.hpp" + +#include "operators.hpp" + +void GameObject::move(Point2D vector) { + setPosition(getPosition() + vector); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/gobjectlist.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/gobjectlist.cpp new file mode 100644 index 0000000..5b8b35e --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/gobjectlist.cpp @@ -0,0 +1,54 @@ +#include "gobjectlist.hpp" + +void GameObjectList::link(GameObjectList::Node *cursor, std::unique_ptr &&node) { + // TODO: write your solution here +} + +void GameObjectList::unlink(GameObjectList::Node *node) { + // TODO: write your solution here +} + +GameObjectList::GameObjectList() { + // TODO: write your solution here +} + +void GameObjectList::insert(const std::shared_ptr &object) { + // TODO: write your solution here +} + +void GameObjectList::remove(const std::function &pred) { + GameObjectList::Node* curr = head->next.get(); + while (curr != tail) { + Node* next = curr->next.get(); + if (pred(*curr->object)) { + unlink(curr); + } + curr = next; + } +} + +void GameObjectList::foreach(const std::function& apply) { + Node* curr = head->next.get(); + while (curr != tail) { + apply(*curr->object); + curr = curr->next.get(); + } +} + +GameObjectList::GameObjectList(const GameObjectList &other) : GameObjectList() { + // TODO: write your solution here +} + +GameObjectList::GameObjectList(GameObjectList &&other) noexcept : GameObjectList() { + // TODO: write your solution here +} + +GameObjectList &GameObjectList::operator=(GameObjectList other) { + // TODO: write your solution here + return *this; +} + +void swap(GameObjectList& first, GameObjectList& second) { + using std::swap; + // TODO: write your solution here +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/main.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/main.cpp new file mode 100644 index 0000000..b192818 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/main.cpp @@ -0,0 +1,13 @@ +#include "engine.hpp" + +#include + +int main() { + GameEngine* engine = GameEngine::create(); + if (!engine) { + std::cout << "Game engine is not created!\n"; + return 1; + } + GameEngine::create()->run(); + return 0; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/operators.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/operators.cpp new file mode 100644 index 0000000..4dc9097 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/operators.cpp @@ -0,0 +1,42 @@ +#include "game.hpp" + +Point2D operator+(Point2D a, Point2D b) { + return add(a, b); +} + +Point2D operator-(Point2D a) { + return { -a.x, -a.y }; +} + +Point2D operator-(Point2D a, Point2D b) { + return add(a, -b); +} + +Point2D operator*(float s, Point2D a) { + return mul(s, a); +} + +Circle operator+(Circle c, Point2D v) { + return { c.center + v, c.radius }; +} + +Circle operator-(Circle c, Point2D v) { + return { c.center - v, c.radius }; +} + +Rectangle operator+(Rectangle r, Point2D v) { + return { r.topLeft + v, r.botRight + v }; +} + +Rectangle operator-(Rectangle r, Point2D v) { + return { r.topLeft - v, r.botRight - v }; +} + +Circle operator*(float s, Circle c) { + return { c.center, s * c.radius }; +} + +Rectangle operator*(float s, Rectangle r) { + Point2D v = { width(r), height(r) }; + return { r.topLeft, r.topLeft + s * v }; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/player.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/player.cpp new file mode 100644 index 0000000..452577b --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/player.cpp @@ -0,0 +1,54 @@ +#include "player.hpp" + +#include "constants.hpp" +#include "direction.hpp" +#include "operators.hpp" + +PlayerObject::PlayerObject() + : CircleGameObject({ { PLAYER_START_X, PLAYER_START_Y }, RADIUS }) +{} + +GameObjectKind PlayerObject::getKind() const { + return GameObjectKind::PLAYER; +} + +Point2D PlayerObject::getVelocity() const { + Point2D velocity = { 0.0f, 0.0f }; + if (CircleGameObject::getStatus() == GameObjectStatus::DESTROYED) + return velocity; + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) { + velocity = velocity + getDirection(North); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) { + velocity = velocity + getDirection(East); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) { + velocity = velocity + getDirection(South); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) { + velocity = velocity + getDirection(West); + } + velocity = SPEED * velocity; + return velocity; +} + +void PlayerObject::update(sf::Time delta) { + return; +} + +const sf::Texture* PlayerObject::getTexture(TextureManager& textureManager) const { + switch (getStatus()) { + case GameObjectStatus::NORMAL: + return textureManager.getTexture(GameTextureID::PLANET); + case GameObjectStatus::WARNED: + return textureManager.getTexture(GameTextureID::PLANET); + case GameObjectStatus::DESTROYED: + return textureManager.getTexture(GameTextureID::PLANET_DEAD); + default: + return nullptr; + } +} + +void PlayerObject::onCollision(const GameObject &object, const CollisionInfo &info) { + // TODO: write your solution here +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/point.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/point.cpp new file mode 100644 index 0000000..5c74f69 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/point.cpp @@ -0,0 +1,19 @@ +#include "game.hpp" + +Point2D add(Point2D a, Point2D b) { + Point2D c = { 0, 0 }; + c.x = a.x + b.x; + c.y = a.y + b.y; + return c; +} + +Point2D mul(float s, Point2D a) { + Point2D b = { 0, 0 }; + b.x = s * a.x; + b.y = s * a.y; + return b; +} + +Point2D move(Point2D position, Point2D velocity, float delta) { + return add(position, mul(delta, velocity)); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/rectangle.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/rectangle.cpp new file mode 100644 index 0000000..2b5a1ec --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/rectangle.cpp @@ -0,0 +1,35 @@ +#include "rectangle.hpp" + +#include "operators.hpp" + +Point2D center(const Rectangle& rect) { + // TODO: explain this C++ initialization syntax + return rect.topLeft + 0.5f * Point2D { width(rect), height(rect) }; +} + +Rectangle createRectangle(Point2D p1, Point2D p2) { + Rectangle rect; + rect.topLeft = { std::min(p1.x, p2.x), std::min(p1.y, p2.y) }; + rect.botRight = { std::max(p1.x, p2.x), std::max(p1.y, p2.y) }; + return rect; +} + +Rectangle fitInto(const Rectangle& rect, const Rectangle& intoRect) { + if (width(rect) > width(intoRect) || height(rect) > height(intoRect)) { + return rect; + } + Point2D vector = { 0.0f, 0.0f }; + if (rect.topLeft.x < intoRect.topLeft.x) { + vector.x += intoRect.topLeft.x - rect.topLeft.x; + } + if (rect.topLeft.y < intoRect.topLeft.y) { + vector.y += intoRect.topLeft.y - rect.topLeft.y; + } + if (rect.botRight.x > intoRect.botRight.x) { + vector.x += intoRect.botRight.x - rect.botRight.x; + } + if (rect.botRight.y > intoRect.botRight.y) { + vector.y += intoRect.botRight.y - rect.botRight.y; + } + return rect + vector; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/scene.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/scene.cpp new file mode 100644 index 0000000..1457361 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/scene.cpp @@ -0,0 +1,47 @@ +#include "scene.hpp" + +#include "operators.hpp" + +Scene::Scene(float width, float height) + : width(width) + , height(height) +{} + +Rectangle Scene::getBoundingBox() const { + Rectangle box; + box.topLeft = { 0.0f, 0.0f }; + box.botRight = { width, height }; + return box; +} + +void Scene::setObjectPosition(GameObject& object, Point2D position) { + object.setPosition(position); + fitInto(object); +} + +void Scene::move(GameObject &object, Point2D vector) { + object.move(vector); + fitInto(object); +} + +void Scene::move(GameObject& object, sf::Time delta) { + move(object, 0.001f * delta.asMilliseconds() * object.getVelocity()); +} + +void Scene::fitInto(GameObject &object) { + Rectangle rect = ::fitInto(object.getBoundingBox(), getBoundingBox()); + object.setPosition(center(rect)); +} + +void Scene::detectCollision(GameObject& object1, GameObject& object2) { + CollisionInfo info = collisionInfo(object1, object2); + object1.onCollision(object2, info); + object2.onCollision(object1, info); +} + +void Scene::drawBackground(sf::RenderWindow &window, const sf::Texture* texture) const { + sf::Sprite background; + background.setTexture(*texture); + background.setTextureRect(sf::IntRect(0, 0, width, height)); + window.draw(background); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/scenes.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/scenes.cpp new file mode 100644 index 0000000..7125bb8 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/scenes.cpp @@ -0,0 +1,14 @@ +#include "scenes.hpp" + +bool SceneManager::initialize() { + staticScene.activate(); + return true; +} + +Scene* SceneManager::getCurrentScene() { + return &staticScene; +} + +void SceneManager::transitionScene(SceneID id) { + return; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/statscene.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/statscene.cpp new file mode 100644 index 0000000..a6abdfa --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/statscene.cpp @@ -0,0 +1,46 @@ +#include "statscene.hpp" + +#include "constants.hpp" + +GameplayStaticScene::GameplayStaticScene() : Scene(SCENE_WIDTH, SCENE_HEIGHT) {} + +void GameplayStaticScene::activate() { + fitInto(player); + fitInto(consumable); + fitInto(enemy); +} + +void GameplayStaticScene::deactivate() { + return; +} + +SceneID GameplayStaticScene::getID() const { + return SceneID::STATIC_GAME_FIELD; +} + +SceneID GameplayStaticScene::getNextSceneID() const { + return SceneID::STATIC_GAME_FIELD; +} + +void GameplayStaticScene::processEvent(const sf::Event& event) { + return; +} + +void GameplayStaticScene::update(sf::Time delta) { + player.update(delta); + consumable.update(delta); + enemy.update(delta); + move(player, delta); + move(consumable, delta); + move(enemy, delta); + detectCollision(player, consumable); + detectCollision(enemy, player); + detectCollision(enemy, consumable); +} + +void GameplayStaticScene::draw(sf::RenderWindow &window, TextureManager& textureManager) { + drawBackground(window, textureManager.getTexture(GameTextureID::SPACE)); + player.draw(window, textureManager); + consumable.draw(window, textureManager); + enemy.draw(window, textureManager); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/textures.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/textures.cpp new file mode 100644 index 0000000..2b5abcc --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/textures.cpp @@ -0,0 +1,42 @@ +#include "textures.hpp" + +#include + +const char* getTextureFilename(GameTextureID id) { + switch (id) { + case GameTextureID::SPACE: + return "resources/space.png"; + case GameTextureID::PLANET: + return "resources/planet.png"; + case GameTextureID::PLANET_DEAD: + return "resources/planetDead.png"; + case GameTextureID::STAR: + return "resources/star.png"; + case GameTextureID::STAR_CONCERNED: + return "resources/starConcerned.png"; + case GameTextureID::BLACKHOLE: + return "resources/blackhole.png"; + default: + return ""; + } +} + +bool TextureManager::initialize() { + for (size_t i = 0; i < SIZE; ++i) { + GameTextureID id = static_cast(i); + const char* filename = getTextureFilename(id); + sf::Texture* texture = &textures[i]; + if (!texture->loadFromFile(filename)) { + std::cerr << "Could not open file " << filename << "\n"; + return false; + } + if (id == GameTextureID::SPACE) { + texture->setRepeated(true); + } + } + return true; +} + +const sf::Texture* TextureManager::getTexture(GameTextureID id) const { + return &textures[static_cast(id)]; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/utils.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/utils.cpp new file mode 100644 index 0000000..783fc7a --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/src/utils.cpp @@ -0,0 +1,51 @@ +#include "utils.hpp" + +#include +#include +#include + +float distance(Point2D a, Point2D b) { + float dx = a.x - b.x; + float dy = a.y - b.y; + return sqrt(dx * dx + dy * dy); +} + +float generateFloat(float min, float max) { + return min + (rand() / (RAND_MAX / (max - min))); +} + +int generateInt(int min, int max) { + return min + rand() % (max - min + 1); +} + +bool generateBool(float prob) { + return generateFloat(0.0f, 1.0f) < prob; +} + +Point2D generatePoint(const Rectangle& boundingBox) { + Point2D point = { 0.0f, 0.0f, }; + point.x = generateFloat(boundingBox.topLeft.x, boundingBox.botRight.x); + point.y = generateFloat(boundingBox.topLeft.y, boundingBox.botRight.y); + return point; +} + +Circle generateCircle(float radius, const Rectangle& boundingBox) { + Circle circle = { { 0.0f, 0.0f }, 0.0f }; + if (radius > std::min(width(boundingBox), height(boundingBox))) { + return circle; + } + circle.center.x = generateFloat( + boundingBox.topLeft.x + radius, + boundingBox.botRight.x - radius + ); + circle.center.y = generateFloat( + boundingBox.topLeft.x + radius, + boundingBox.botRight.x - radius + ); + circle.radius = radius; + return circle; +} + +Rectangle generateRectangle(const Rectangle& boundingBox) { + return createRectangle(generatePoint(boundingBox), generatePoint(boundingBox)); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/task-info.yaml b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/task-info.yaml new file mode 100644 index 0000000..4089f01 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/task-info.yaml @@ -0,0 +1,46 @@ +type: edu +custom_name: Encapsulation +files: +- name: src/scene.cpp + visible: true +- name: src/main.cpp + visible: true + editable: false +- name: src/engine.cpp + visible: true +- name: src/scenes.cpp + visible: true +- name: src/textures.cpp + visible: true +- name: src/statscene.cpp + visible: true +- name: src/dynscene.cpp + visible: true +- name: src/gobject.cpp + visible: true +- name: src/gobjectlist.cpp + visible: true +- name: src/cgobject.cpp + visible: true +- name: src/player.cpp + visible: true +- name: src/consumable.cpp + visible: true +- name: src/enemy.cpp + visible: true +- name: src/collision.cpp + visible: true +- name: src/direction.cpp + visible: true +- name: src/rectangle.cpp + visible: true +- name: src/point.cpp + visible: true +- name: src/operators.cpp + visible: true +- name: src/utils.cpp + visible: true +- name: CMakeLists.txt + visible: false +- name: test/test.cpp + visible: false diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/task-remote-info.yaml b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/task-remote-info.yaml new file mode 100644 index 0000000..7334942 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/task-remote-info.yaml @@ -0,0 +1 @@ +id: 2097510149 diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/task.md b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/task.md new file mode 100644 index 0000000..928ec53 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/task.md @@ -0,0 +1,102 @@ +Let us now consider another essential class of the game — the `Scene` class. +The game scene object is responsible for handling user input, +keeping game objects and managing their updates, +drawing graphics on the window, and other activities. +In essence, it is the workhorse of our simple game engine. + +Let us have a look at the declaration of the `Scene` class. +This class is pretty packed — it includes both regular and virtual methods +as well as some data fields. +For details regarding the purpose of these methods, we refer to their documentation. + +The `Scene` class groups its members into three sections: `public`, `protected`, and `private`. +Let us finally decipher their meaning! + +With the help of these _visibility modifiers_, +the class can regulate how its clients interact with the objects of this class: + +* all the fields and methods declared within the `public` section + are visible and can be freely used outside the class; +* all the fields and methods declared within the `protected` section + are visible and can be used within the class itself and within its descendants; +* all the fields and methods declared within the `private` section + are visible and can be used only within the class itself and nowhere else; +* there is a single exception to the previous rule — + a class marked as a `friend` of a given class can access its protected and private members. + +
+ +When applied in the context of inheritance, +as, for example, in the declaration of the `CircleGameObject`: +``` +class CircleGameObject : public GameObject +``` +the visibility modifiers have the following meaning: + +* if a class is inherited in `public` mode, + the public members of the base class become public in the derived class, + and the protected members of the base class become protected in the derived class; + however, private members from the base class are not directly accessible from the derived class; +* if a class is inherited in `protected` mode, + both the public and protected members of the base class become protected in the derived class, + while private members from the base class are not directly accessible from the derived class; +* if a class is inherited in `private` mode, + all public and protected members of the base class become private in the derived class, + and private members from the base class are not directly accessible from the derived class. + +
+ +The publicly available fields and methods of a class are also called its _public interface_ +(not to be confused with the term _interface_ denoting classes containing pure virtual functions and devoid of state). +The public interface of a class defines the visibility of the class's objects from the outside, +denoting what fields and methods the clients of the class can access. + +You might be wondering why it's necessary to hide some fields or methods of a class — +after all, they could be useful externally. +However, the ability to hide certain _implementation details_ +allows objects to have ultimate control over their internal state. + +This principle is known under the name _encapsulation_. +Encapsulation allows the developer of a class to maintain the _invariants_ on an object's data, +ensuring that objects of this class always remain in a valid state. + +Let us explain this using the example of the `Scene` class. +Among other things, this class is responsible for storing the game objects appearing on the scene. +One useful invariant that the `Scene` class might enforce is ensuring that all of its game objects lie within the scene's borders. +However, if the `GameObject` class provides a `public` method to change an object's position (i.e., `setPosition`), +then the `Scene` object has no means to guarantee this property. +Any other class or function can change the position of an object and put it outside the scene's visible area. +Conversely, if the only way for a user class to change the position of an object is through a call +to a scene's method (for example, its `move` method), then the implementation of this method can +take some additional actions to ensure that the object remains within the scene. +This way, by controlling the visibility of an object's fields and methods, +a developer can enforce various useful program state invariants. + +Mastering class invariants and controlling the visibility of their members is +a skill that comes with experience. The more complex applications you architect and develop, +the more proficient you will become at designing classes and their invariants. + +To consolidate the material of this step, please +implement the following two methods of the `Scene` class. + +```c++ +void setObjectPosition(GameObject& object, Point2D position); +void move(GameObject& object, Point2D vector); +``` + +You need to ensure the invariant of the `Scene` class we discussed above — +objects in the scene must remain within its borders. + +To implement these methods, you must use +the corresponding methods of the `GameObject` class, +as well as another method the `Scene` class — the `fitInto` method. +This method adjusts the position of an object so that it fits within the `Scene` borders. + +
+ +Note that the `Scene` class is declared as a `friend` of the `GameObject` class +(see the class declaration in the `gobject.hpp` file). +As such, the `Scene` class can access the `setPosition` method of the `GameObject` class, +even though it is declared as a private method. + +
diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/test/test.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/test/test.cpp new file mode 100644 index 0000000..e052d1a --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Encapsulation/test/test.cpp @@ -0,0 +1,218 @@ +#include + +#include + +#include "scene.hpp" +#include "operators.hpp" +#include "constants.hpp" + +#include "testscene.hpp" +#include "testing.hpp" + +testing::Environment* const env = + testing::AddGlobalTestEnvironment(new TestEnvironment); + +std::string setObjectPosition_error_msg(Scene* scene, GameObject* object, Point2D position, Point2D expected, Point2D actual) { + std::ostringstream stream; + stream << "Testing expression:\n" + << " scene.setObjectPosition(object, position)" << "\n"; + stream << "Test data:" << "\n" + << " scene.getBoundingBox() = " << scene->getBoundingBox() << "\n" + << " object.getPosition() = " << object->getPosition() << "\n" + << " position = " << position << "\n"; + stream << "Expected result:\n" + << " object.getPosition() = " << expected << "\n"; + stream << "Actual result:\n" + << " object.getPosition() = " << actual << "\n"; + return stream.str(); +} + +std::string setObjectPositionInRectangle_error_msg(Scene* scene, GameObject* object, Point2D position, Point2D actual) { + std::ostringstream stream; + stream << "Testing expression:\n" + << " scene.setObjectPosition(object, position)" << "\n"; + stream << "Test data:" << "\n" + << " scene.getBoundingBox() = " << scene->getBoundingBox() << "\n" + << " object.getPosition() = " << object->getPosition() << "\n" + << " position = " << position << "\n"; + stream << "Actual result:\n" + << " object.getPosition() = " << actual << "\n"; + stream << "The result is not withing the scene bounds!\n"; + return stream.str(); +} + +TEST(SceneTest, SetObjectPositionInBoundsTest) { + TestScene scene = TestScene(SCENE_WIDTH, SCENE_HEIGHT); + property_test( + [] () { + const float radius = 20.0f; + Point2D offset = { radius, radius }; + Rectangle positionArea = { + Point2D { 0.0f, 0.0f } + offset, + Point2D { SCENE_WIDTH, SCENE_HEIGHT } - offset + }; + Point2D position = generatePoint(positionArea); + Circle circle = Circle { position, radius }; + Point2D velocity = Point2D { 0.0f, 0.0f }; + TestCircleGameObject object = TestCircleGameObject(circle, velocity, GameObjectKind::CONSUMABLE); + Rectangle newPositionArea = positionArea; + Point2D newPosition = generatePoint(newPositionArea); + return std::make_tuple(object, newPosition); + }, + [&scene] (std::tuple data) { + TestCircleGameObject object; + Point2D newPosition; + std::tie(object, newPosition) = data; + TestCircleGameObject expected = object; + expected.performSetPosition(newPosition); + TestCircleGameObject actual = object; + scene.performSetObjectPosition(actual, newPosition); + ASSERT_TRUE(isInRectangle(actual.getPosition(), scene.getBoundingBox())) + << setObjectPositionInRectangle_error_msg(&scene, &object, newPosition, actual.getPosition()); + ASSERT_FLOAT_EQ(expected.getPosition().x, actual.getPosition().x) + << setObjectPosition_error_msg(&scene, &object, newPosition, expected.getPosition(), actual.getPosition()); + ASSERT_FLOAT_EQ(expected.getPosition().y, actual.getPosition().y) + << setObjectPosition_error_msg(&scene, &object, newPosition, expected.getPosition(), actual.getPosition()); + } + ); +} + +TEST(SceneTest, SetObjectPositionOutOfBoundsTest) { + TestScene scene = TestScene(SCENE_WIDTH, SCENE_HEIGHT); + property_test( + [&scene] () { + const float radius = 20.0f; + Point2D offset = { radius, radius }; + Rectangle positionArea = { + Point2D { 0.0f, 0.0f } + offset, + Point2D { SCENE_WIDTH, SCENE_HEIGHT } - offset + }; + Point2D position = generatePoint(positionArea); + Circle circle = Circle { position, radius }; + Point2D velocity = Point2D { 0.0f, 0.0f }; + TestCircleGameObject object = TestCircleGameObject(circle, velocity, GameObjectKind::CONSUMABLE); + Rectangle newPositionArea = { + { - 2 * SCENE_WIDTH, -2 * SCENE_HEIGHT }, + { 2 * SCENE_WIDTH, 2 * SCENE_HEIGHT } + }; + Point2D newPosition; + do { + newPosition = generatePoint(newPositionArea); + } while (isInRectangle(newPosition, scene.getBoundingBox())); + return std::make_tuple(object, newPosition); + }, + [&scene] (std::tuple data) { + TestCircleGameObject object; + Point2D newPosition; + std::tie(object, newPosition) = data; + TestCircleGameObject expected = object; + expected.performSetPosition(newPosition); + Rectangle rect = fitInto(expected.getBoundingBox(), scene.getBoundingBox()); + expected.performSetPosition(center(rect)); + TestCircleGameObject actual = object; + scene.performSetObjectPosition(actual, newPosition); + ASSERT_TRUE(isInRectangle(actual.getPosition(), scene.getBoundingBox())) + << setObjectPositionInRectangle_error_msg(&scene, &object, newPosition, actual.getPosition()); + ASSERT_FLOAT_EQ(expected.getPosition().x, actual.getPosition().x) + << setObjectPosition_error_msg(&scene, &object, newPosition, expected.getPosition(), actual.getPosition()); + ASSERT_FLOAT_EQ(expected.getPosition().y, actual.getPosition().y) + << setObjectPosition_error_msg(&scene, &object, newPosition, expected.getPosition(), actual.getPosition()); + } + ); +} + +std::string moveObject_error_msg(Scene* scene, GameObject* object, Point2D vector, Point2D expected, Point2D actual) { + std::ostringstream stream; + stream << "Testing expression:\n" + << " scene.move(object, vector)" << "\n"; + stream << "Test data:" << "\n" + << " scene.getBoundingBox() = " << scene->getBoundingBox() << "\n" + << " object.getPosition() = " << object->getPosition() << "\n" + << " vector = " << vector << "\n"; + stream << "Expected result:\n" + << " object.getPosition() = " << expected << "\n"; + stream << "Actual result:\n" + << " object.getPosition() = " << actual << "\n"; + return stream.str(); +} + +std::string moveObjectInRectangle_error_msg(Scene* scene, GameObject* object, Point2D vector, Point2D actual) { + std::ostringstream stream; + stream << "Testing expression:\n" + << " scene.move(object, vector)" << "\n"; + stream << "Test data:" << "\n" + << " scene.getBoundingBox() = " << scene->getBoundingBox() << "\n" + << " object.getPosition() = " << object->getPosition() << "\n" + << " vector = " << vector << "\n"; + stream << "Actual result:\n" + << " object.getPosition() = " << actual << "\n"; + stream << "The result is not withing the scene bounds!\n"; + return stream.str(); +} + +TEST(SceneTest, MoveInBoundsTest) { + TestScene scene = TestScene(SCENE_WIDTH, SCENE_HEIGHT); + property_test( + [] () { + const float radius = 20.0f; + Point2D position = { SCENE_WIDTH / 2, SCENE_HEIGHT / 2 }; + Circle circle = Circle { position, radius }; + Point2D velocity = Point2D { 0.0f, 0.0f }; + TestCircleGameObject object = TestCircleGameObject(circle, velocity, GameObjectKind::CONSUMABLE); + Rectangle vectorArea = Rectangle { { 0.0f, 0.0f }, { 2 * radius, 2 * radius } }; + Point2D vector = generatePoint(vectorArea); + return std::make_tuple(object, vector); + }, + [&scene] (std::tuple data) { + TestCircleGameObject object; + Point2D vector; + std::tie(object, vector) = data; + TestCircleGameObject expected = object; + expected.performSetPosition(expected.getPosition() + vector); + TestCircleGameObject actual = object; + scene.performMove(actual, vector); + ASSERT_TRUE(isInRectangle(actual.getPosition(), scene.getBoundingBox())) + << moveObjectInRectangle_error_msg(&scene, &object, vector, actual.getPosition()); + ASSERT_FLOAT_EQ(expected.getPosition().x, actual.getPosition().x) + << moveObject_error_msg(&scene, &object, vector, expected.getPosition(), actual.getPosition()); + ASSERT_FLOAT_EQ(expected.getPosition().y, actual.getPosition().y) + << moveObject_error_msg(&scene, &object, vector, expected.getPosition(), actual.getPosition()); + } + ); +} + +TEST(SceneTest, MoveOutOfBoundsTest) { + TestScene scene = TestScene(SCENE_WIDTH, SCENE_HEIGHT); + property_test( + [] () { + const float radius = 20.0f; + Point2D position = { SCENE_WIDTH / 2, SCENE_HEIGHT / 2 }; + Circle circle = Circle { position, radius }; + Point2D velocity = Point2D { 0.0f, 0.0f }; + TestCircleGameObject object = TestCircleGameObject(circle, velocity, GameObjectKind::CONSUMABLE); + Rectangle vectorArea = Rectangle { + { SCENE_WIDTH, SCENE_HEIGHT }, + { 4 * SCENE_WIDTH, 4 * SCENE_HEIGHT }, + }; + Point2D vector = generatePoint(vectorArea); + return std::make_tuple(object, vector); + }, + [&scene] (std::tuple data) { + TestCircleGameObject object; + Point2D vector; + std::tie(object, vector) = data; + TestCircleGameObject expected = object; + expected.performSetPosition(expected.getPosition() + vector); + Rectangle rect = fitInto(expected.getBoundingBox(), scene.getBoundingBox()); + expected.performSetPosition(center(rect)); + TestCircleGameObject actual = object; + scene.performMove(actual, vector); + ASSERT_TRUE(isInRectangle(actual.getPosition(), scene.getBoundingBox())) + << moveObjectInRectangle_error_msg(&scene, &object, vector, actual.getPosition()); + ASSERT_FLOAT_EQ(expected.getPosition().x, actual.getPosition().x) + << moveObject_error_msg(&scene, &object, vector, expected.getPosition(), actual.getPosition()); + ASSERT_FLOAT_EQ(expected.getPosition().y, actual.getPosition().y) + << moveObject_error_msg(&scene, &object, vector, expected.getPosition(), actual.getPosition()); + } + ); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/CMakeLists.txt b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/CMakeLists.txt new file mode 100644 index 0000000..db78be6 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.25) + +project(ObjectOrientedProgramming-ClassesAndObjects-Inheritance) + +set(SRC + src/main.cpp + src/engine.cpp src/scenes.cpp src/textures.cpp + src/scene.cpp src/statscene.cpp src/dynscene.cpp + src/gobject.cpp src/gobjectlist.cpp src/cgobject.cpp + src/player.cpp src/consumable.cpp src/enemy.cpp + src/collision.cpp src/direction.cpp + src/rectangle.cpp src/point.cpp + src/operators.cpp src/utils.cpp +) + +set(TEST + test/test.cpp) + +add_executable(${PROJECT_NAME}-run ${SRC}) + +configure_test_target(${PROJECT_NAME}-test "${SRC}" ${TEST}) + +prepare_sfml_framework_lesson_task( + "${CMAKE_CURRENT_SOURCE_DIR}/.." + ${PROJECT_NAME}-run + ${PROJECT_NAME}-test +) diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/cgobject.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/cgobject.cpp new file mode 100644 index 0000000..1d49ba3 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/cgobject.cpp @@ -0,0 +1,47 @@ +#include "cgobject.hpp" + +#include "operators.hpp" + +CircleGameObject::CircleGameObject(Circle circle) + : circle(circle) + , status(GameObjectStatus::NORMAL) +{} + +Point2D CircleGameObject::getPosition() const { + return circle.center; +} + +void CircleGameObject::setPosition(Point2D position) { + circle.center = position; +} + +GameObjectStatus CircleGameObject::getStatus() const { + return status; +} + +void CircleGameObject::setStatus(GameObjectStatus newStatus) { + status = newStatus; +} + +Circle CircleGameObject::getCircle() const { + return circle; +} + +Rectangle CircleGameObject::getBoundingBox() const { + Point2D offset = { circle.radius, circle.radius }; + Point2D p1 = circle.center - offset; + Point2D p2 = circle.center + offset; + return createRectangle(p1, p2); +} + +void CircleGameObject::draw(sf::RenderWindow &window, TextureManager& textureManager) const { + const sf::Texture* texture = getTexture(textureManager); + if (texture == nullptr) + return; + sf::CircleShape shape; + shape.setPosition(circle.center.x, circle.center.y); + shape.setOrigin(circle.radius, circle.radius); + shape.setRadius(circle.radius); + shape.setTexture(texture); + window.draw(shape); +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/collision.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/collision.cpp new file mode 100644 index 0000000..9b56990 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/collision.cpp @@ -0,0 +1,21 @@ +#include + +#include "collision.hpp" +#include "cgobject.hpp" +#include "utils.hpp" + +CollisionInfo collisionInfo(const Circle& circle1, const Circle& circle2) { + CollisionInfo info; + info.distance = distance(circle1.center, circle2.center); + info.collide = (info.distance < circle1.radius + circle2.radius); + return info; +} + +CollisionInfo collisionInfo(const GameObject& object1, const GameObject& object2) { + const CircleGameObject* circleObject1 = dynamic_cast(&object1); + const CircleGameObject* circleObject2 = dynamic_cast(&object2); + if (circleObject1 && circleObject2) { + return collisionInfo(circleObject1->getCircle(), circleObject2->getCircle()); + } + assert(false); +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/consumable.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/consumable.cpp new file mode 100644 index 0000000..7bd2eaa --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/consumable.cpp @@ -0,0 +1,27 @@ +#include "consumable.hpp" + +#include "constants.hpp" + +ConsumableObject::ConsumableObject() + : CircleGameObject({ { CONSUMABLE_START_X, CONSUMABLE_START_Y }, CONSUMABLE_RADIUS }) +{} + +GameObjectKind ConsumableObject::getKind() const { + return GameObjectKind::CONSUMABLE; +} + +Point2D ConsumableObject::getVelocity() const { + return { 0.0f, 0.0f }; +} + +void ConsumableObject::update(sf::Time delta) { + // TODO: write your solution here +} + +void ConsumableObject::onCollision(const GameObject &object, const CollisionInfo &info) { + // TODO: write your solution here +} + +const sf::Texture* ConsumableObject::getTexture(TextureManager& textureManager) const { + // TODO: write your solution here +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/direction.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/direction.cpp new file mode 100644 index 0000000..0a2b606 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/direction.cpp @@ -0,0 +1,16 @@ +#include "direction.hpp" + +Point2D getDirection(Direction direction) { + switch (direction) { + case North: + return { 0.0f, -1.0f }; + case East: + return { 1.0f, 0.0f }; + case South: + return { 0.0f, 1.0f }; + case West: + return { -1.0f, 0.0f }; + default: + return { 0.0f, 0.0f }; + } +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/dynscene.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/dynscene.cpp new file mode 100644 index 0000000..1dbcf2e --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/dynscene.cpp @@ -0,0 +1,144 @@ +#include "dynscene.hpp" + +#include "constants.hpp" +#include "player.hpp" +#include "consumable.hpp" +#include "enemy.hpp" +#include "utils.hpp" + +const int MAX_DYNAMIC_OBJECTS_ON_SCENE = 10; +const int MAX_ENEMY_OBJECTS_ON_SCENE = 4; + +const int NEW_DYNAMIC_OBJECT_PROB = 1; +const int NEW_ENEMY_OBJECT_PROB = 10; + +GameplayDynamicScene::GameplayDynamicScene() : Scene(SCENE_WIDTH, SCENE_HEIGHT) {} + +void GameplayDynamicScene::activate() { + addNewGameObject(GameObjectKind::PLAYER); +} + +void GameplayDynamicScene::deactivate() { + // remove all the objects from the list + objects.remove([&] (const GameObject& object) { + return true; + }); +} + +SceneID GameplayDynamicScene::getID() const { + return SceneID::DYNAMIC_GAME_FIELD; +} + +SceneID GameplayDynamicScene::getNextSceneID() const { + return SceneID::DYNAMIC_GAME_FIELD; +} + +void GameplayDynamicScene::processEvent(const sf::Event& event) { + return; +} + +void GameplayDynamicScene::update(sf::Time delta) { + // update the objects' state + objects.foreach([delta] (GameObject& object) { + object.update(delta); + }); + // move objects according to their velocity and elapsed time + objects.foreach([this, delta] (GameObject& object) { + move(object, delta); + }); + // compute collision information for each pair of objects + objects.foreach([this] (GameObject& object) { + objects.foreach([this, &object] (GameObject& other) { + if (&object != &other) { + detectCollision(object, other); + } + }); + }); + // update the list of objects + updateObjectsList(); +} + +void GameplayDynamicScene::updateObjectsList() { + // remove destroyed objects from the list + objects.remove([] (const GameObject& object) { + return (object.getStatus() == GameObjectStatus::DESTROYED) + && (object.getKind() != GameObjectKind::PLAYER); + }); + // count the number of the different kinds of objects present on the scene + int consumableCount = 0; + int enemyCount = 0; + objects.foreach([&] (const GameObject& object) { + switch (object.getKind()) { + case GameObjectKind::CONSUMABLE: + ++consumableCount; + break; + case GameObjectKind::ENEMY: + ++enemyCount; + break; + default: + break; + } + }); + // add new objects of randomly chosen kind if there is enough room for them on the scene + int dynamicObjectsCount = consumableCount + enemyCount; + if (dynamicObjectsCount < MAX_DYNAMIC_OBJECTS_ON_SCENE) { + int r = rand() % 100; + int k = rand() % 100; + if (r < NEW_DYNAMIC_OBJECT_PROB) { + if (k < NEW_ENEMY_OBJECT_PROB && enemyCount < MAX_ENEMY_OBJECTS_ON_SCENE) { + addNewGameObject(GameObjectKind::ENEMY); + } else if (k > NEW_ENEMY_OBJECT_PROB) { + addNewGameObject(GameObjectKind::CONSUMABLE); + } + } + } +} + +std::shared_ptr GameplayDynamicScene::addNewGameObject(GameObjectKind kind) { + std::shared_ptr object; + while (!object) { + // create an object with default position + switch (kind) { + case GameObjectKind::PLAYER: { + object = std::make_shared(); + break; + } + case GameObjectKind::CONSUMABLE: { + object = std::make_shared(); + break; + } + case GameObjectKind::ENEMY: { + object = std::make_shared(); + break; + } + } + // set random position for consumable and enemy objects + if (kind == GameObjectKind::CONSUMABLE || + kind == GameObjectKind::ENEMY) { + setObjectPosition(*object, generatePoint(getBoundingBox())); + } else { + fitInto(*object); + } + // check that object does not collide with existing objects + bool collide = false; + objects.foreach([&collide, &object](GameObject& other) { + collide |= collisionInfo(*object, other).collide; + }); + // reset a colliding object + if (collide) { + object = nullptr; + } + } + objects.insert(object); + return object; +} + +void GameplayDynamicScene::draw(sf::RenderWindow &window, TextureManager& textureManager) { + // draw background + drawBackground(window, textureManager.getTexture(GameTextureID::SPACE)); + // draw all objects on the scene + objects.foreach([&] (const GameObject& object) { + object.draw(window, textureManager); + }); +} + diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/enemy.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/enemy.cpp new file mode 100644 index 0000000..3bb34d8 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/enemy.cpp @@ -0,0 +1,44 @@ +#include "enemy.hpp" + +#include "constants.hpp" +#include "operators.hpp" +#include "utils.hpp" + +EnemyObject::EnemyObject() + : CircleGameObject({ { ENEMY_START_X, ENEMY_START_Y }, ENEMY_RADIUS }) +{} + +GameObjectKind EnemyObject::getKind() const { + return GameObjectKind::ENEMY; +} + +Point2D EnemyObject::getVelocity() const { + return velocity; +} + +void EnemyObject::update(sf::Time delta) { + if (CircleGameObject::getStatus() == GameObjectStatus::DESTROYED) + return; + updateTimer += delta; + if (updateTimer < sf::seconds(1.0f)) + return; + updateTimer = sf::milliseconds(0.0f); + updateVelocity(); +} + +void EnemyObject::setVelocity(Point2D velocity) { + this->velocity = velocity; +} + +void EnemyObject::updateVelocity() { + // TODO: write your solution here +} + +void EnemyObject::onCollision(const GameObject &object, const CollisionInfo &info) { + return; +} + +const sf::Texture* EnemyObject::getTexture(TextureManager& textureManager) const { + // TODO: write your solution here + return nullptr; +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/engine.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/engine.cpp new file mode 100644 index 0000000..c506cdf --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/engine.cpp @@ -0,0 +1,89 @@ +#include "engine.hpp" + +#include "constants.hpp" + +GameEngine *GameEngine::create() { + // TODO: write your solution here + return nullptr; +} + +GameEngine::GameEngine() + : scene(nullptr) + , active(true) +{ + // initialize random number generator + srand(time(nullptr)); + // initialize resource managers + active &= sceneManager.initialize(); + active &= textureManager.initialize(); + if (!active) { + return; + } + // set the current scene + scene = sceneManager.getCurrentScene(); + // initialize the application window + window.create(sf::VideoMode(WINDOW_WIDTH, WINDOW_HEIGHT), "Space Game"); + window.setFramerateLimit(60); +} + +void GameEngine::run() { + sf::Clock clock; + while (isActive()) { + sf::Time delta = clock.restart(); + processInput(); + update(delta); + render(); + sceneTransition(); + } +} + +bool GameEngine::isActive() const { + return active && window.isOpen(); +} + +void GameEngine::close() { + active = false; + window.close(); +} + +void GameEngine::processInput() { + sf::Event event; + while (window.pollEvent(event)) { + processEvent(event); + } +} + +void GameEngine::processEvent(const sf::Event &event) { + switch (event.type) { + case sf::Event::Closed: + close(); + break; + default: + scene->processEvent(event); + break; + } +} + +void GameEngine::update(sf::Time delta) { + scene->update(delta); +} + +void GameEngine::render() { + window.clear(sf::Color::White); + scene->draw(window, textureManager); + window.display(); +} + +void GameEngine::sceneTransition() { + if (scene->getNextSceneID() != scene->getID()) { + sceneManager.transitionScene(scene->getNextSceneID()); + scene = sceneManager.getCurrentScene(); + resizeWindow(); + } +} + +void GameEngine::resizeWindow() { + Rectangle sceneBox = scene->getBoundingBox(); + sf::Vector2u sceneSize = sf::Vector2u(width(sceneBox), height(sceneBox)); + window.setSize(sceneSize); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/gobject.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/gobject.cpp new file mode 100644 index 0000000..a673ef5 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/gobject.cpp @@ -0,0 +1,7 @@ +#include "gobject.hpp" + +#include "operators.hpp" + +void GameObject::move(Point2D vector) { + setPosition(getPosition() + vector); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/gobjectlist.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/gobjectlist.cpp new file mode 100644 index 0000000..5b8b35e --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/gobjectlist.cpp @@ -0,0 +1,54 @@ +#include "gobjectlist.hpp" + +void GameObjectList::link(GameObjectList::Node *cursor, std::unique_ptr &&node) { + // TODO: write your solution here +} + +void GameObjectList::unlink(GameObjectList::Node *node) { + // TODO: write your solution here +} + +GameObjectList::GameObjectList() { + // TODO: write your solution here +} + +void GameObjectList::insert(const std::shared_ptr &object) { + // TODO: write your solution here +} + +void GameObjectList::remove(const std::function &pred) { + GameObjectList::Node* curr = head->next.get(); + while (curr != tail) { + Node* next = curr->next.get(); + if (pred(*curr->object)) { + unlink(curr); + } + curr = next; + } +} + +void GameObjectList::foreach(const std::function& apply) { + Node* curr = head->next.get(); + while (curr != tail) { + apply(*curr->object); + curr = curr->next.get(); + } +} + +GameObjectList::GameObjectList(const GameObjectList &other) : GameObjectList() { + // TODO: write your solution here +} + +GameObjectList::GameObjectList(GameObjectList &&other) noexcept : GameObjectList() { + // TODO: write your solution here +} + +GameObjectList &GameObjectList::operator=(GameObjectList other) { + // TODO: write your solution here + return *this; +} + +void swap(GameObjectList& first, GameObjectList& second) { + using std::swap; + // TODO: write your solution here +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/main.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/main.cpp new file mode 100644 index 0000000..b192818 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/main.cpp @@ -0,0 +1,13 @@ +#include "engine.hpp" + +#include + +int main() { + GameEngine* engine = GameEngine::create(); + if (!engine) { + std::cout << "Game engine is not created!\n"; + return 1; + } + GameEngine::create()->run(); + return 0; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/operators.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/operators.cpp new file mode 100644 index 0000000..4dc9097 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/operators.cpp @@ -0,0 +1,42 @@ +#include "game.hpp" + +Point2D operator+(Point2D a, Point2D b) { + return add(a, b); +} + +Point2D operator-(Point2D a) { + return { -a.x, -a.y }; +} + +Point2D operator-(Point2D a, Point2D b) { + return add(a, -b); +} + +Point2D operator*(float s, Point2D a) { + return mul(s, a); +} + +Circle operator+(Circle c, Point2D v) { + return { c.center + v, c.radius }; +} + +Circle operator-(Circle c, Point2D v) { + return { c.center - v, c.radius }; +} + +Rectangle operator+(Rectangle r, Point2D v) { + return { r.topLeft + v, r.botRight + v }; +} + +Rectangle operator-(Rectangle r, Point2D v) { + return { r.topLeft - v, r.botRight - v }; +} + +Circle operator*(float s, Circle c) { + return { c.center, s * c.radius }; +} + +Rectangle operator*(float s, Rectangle r) { + Point2D v = { width(r), height(r) }; + return { r.topLeft, r.topLeft + s * v }; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/player.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/player.cpp new file mode 100644 index 0000000..e335cf0 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/player.cpp @@ -0,0 +1,46 @@ +#include "player.hpp" + +#include "constants.hpp" +#include "direction.hpp" +#include "operators.hpp" + +PlayerObject::PlayerObject() + : CircleGameObject({ { PLAYER_START_X, PLAYER_START_Y }, RADIUS }) +{} + +GameObjectKind PlayerObject::getKind() const { + return GameObjectKind::PLAYER; +} + +Point2D PlayerObject::getVelocity() const { + Point2D velocity = { 0.0f, 0.0f }; + if (CircleGameObject::getStatus() == GameObjectStatus::DESTROYED) + return velocity; + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) { + velocity = velocity + getDirection(North); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) { + velocity = velocity + getDirection(East); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) { + velocity = velocity + getDirection(South); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) { + velocity = velocity + getDirection(West); + } + velocity = SPEED * velocity; + return velocity; +} + +void PlayerObject::update(sf::Time delta) { + return; +} + +const sf::Texture* PlayerObject::getTexture(TextureManager& textureManager) const { + // TODO: write your solution here + return nullptr; +} + +void PlayerObject::onCollision(const GameObject &object, const CollisionInfo &info) { + // TODO: write your solution here +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/point.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/point.cpp new file mode 100644 index 0000000..5c74f69 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/point.cpp @@ -0,0 +1,19 @@ +#include "game.hpp" + +Point2D add(Point2D a, Point2D b) { + Point2D c = { 0, 0 }; + c.x = a.x + b.x; + c.y = a.y + b.y; + return c; +} + +Point2D mul(float s, Point2D a) { + Point2D b = { 0, 0 }; + b.x = s * a.x; + b.y = s * a.y; + return b; +} + +Point2D move(Point2D position, Point2D velocity, float delta) { + return add(position, mul(delta, velocity)); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/rectangle.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/rectangle.cpp new file mode 100644 index 0000000..2b5a1ec --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/rectangle.cpp @@ -0,0 +1,35 @@ +#include "rectangle.hpp" + +#include "operators.hpp" + +Point2D center(const Rectangle& rect) { + // TODO: explain this C++ initialization syntax + return rect.topLeft + 0.5f * Point2D { width(rect), height(rect) }; +} + +Rectangle createRectangle(Point2D p1, Point2D p2) { + Rectangle rect; + rect.topLeft = { std::min(p1.x, p2.x), std::min(p1.y, p2.y) }; + rect.botRight = { std::max(p1.x, p2.x), std::max(p1.y, p2.y) }; + return rect; +} + +Rectangle fitInto(const Rectangle& rect, const Rectangle& intoRect) { + if (width(rect) > width(intoRect) || height(rect) > height(intoRect)) { + return rect; + } + Point2D vector = { 0.0f, 0.0f }; + if (rect.topLeft.x < intoRect.topLeft.x) { + vector.x += intoRect.topLeft.x - rect.topLeft.x; + } + if (rect.topLeft.y < intoRect.topLeft.y) { + vector.y += intoRect.topLeft.y - rect.topLeft.y; + } + if (rect.botRight.x > intoRect.botRight.x) { + vector.x += intoRect.botRight.x - rect.botRight.x; + } + if (rect.botRight.y > intoRect.botRight.y) { + vector.y += intoRect.botRight.y - rect.botRight.y; + } + return rect + vector; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/scene.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/scene.cpp new file mode 100644 index 0000000..81a4470 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/scene.cpp @@ -0,0 +1,45 @@ +#include "scene.hpp" + +#include "operators.hpp" + +Scene::Scene(float width, float height) + : width(width) + , height(height) +{} + +Rectangle Scene::getBoundingBox() const { + Rectangle box; + box.topLeft = { 0.0f, 0.0f }; + box.botRight = { width, height }; + return box; +} + +void Scene::setObjectPosition(GameObject& object, Point2D position) { + // TODO: write your solution here +} + +void Scene::move(GameObject &object, Point2D vector) { + // TODO: write your solution here +} + +void Scene::move(GameObject& object, sf::Time delta) { + move(object, 0.001f * delta.asMilliseconds() * object.getVelocity()); +} + +void Scene::fitInto(GameObject &object) { + Rectangle rect = ::fitInto(object.getBoundingBox(), getBoundingBox()); + object.setPosition(center(rect)); +} + +void Scene::detectCollision(GameObject& object1, GameObject& object2) { + CollisionInfo info = collisionInfo(object1, object2); + object1.onCollision(object2, info); + object2.onCollision(object1, info); +} + +void Scene::drawBackground(sf::RenderWindow &window, const sf::Texture* texture) const { + sf::Sprite background; + background.setTexture(*texture); + background.setTextureRect(sf::IntRect(0, 0, width, height)); + window.draw(background); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/scenes.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/scenes.cpp new file mode 100644 index 0000000..7125bb8 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/scenes.cpp @@ -0,0 +1,14 @@ +#include "scenes.hpp" + +bool SceneManager::initialize() { + staticScene.activate(); + return true; +} + +Scene* SceneManager::getCurrentScene() { + return &staticScene; +} + +void SceneManager::transitionScene(SceneID id) { + return; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/statscene.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/statscene.cpp new file mode 100644 index 0000000..a6abdfa --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/statscene.cpp @@ -0,0 +1,46 @@ +#include "statscene.hpp" + +#include "constants.hpp" + +GameplayStaticScene::GameplayStaticScene() : Scene(SCENE_WIDTH, SCENE_HEIGHT) {} + +void GameplayStaticScene::activate() { + fitInto(player); + fitInto(consumable); + fitInto(enemy); +} + +void GameplayStaticScene::deactivate() { + return; +} + +SceneID GameplayStaticScene::getID() const { + return SceneID::STATIC_GAME_FIELD; +} + +SceneID GameplayStaticScene::getNextSceneID() const { + return SceneID::STATIC_GAME_FIELD; +} + +void GameplayStaticScene::processEvent(const sf::Event& event) { + return; +} + +void GameplayStaticScene::update(sf::Time delta) { + player.update(delta); + consumable.update(delta); + enemy.update(delta); + move(player, delta); + move(consumable, delta); + move(enemy, delta); + detectCollision(player, consumable); + detectCollision(enemy, player); + detectCollision(enemy, consumable); +} + +void GameplayStaticScene::draw(sf::RenderWindow &window, TextureManager& textureManager) { + drawBackground(window, textureManager.getTexture(GameTextureID::SPACE)); + player.draw(window, textureManager); + consumable.draw(window, textureManager); + enemy.draw(window, textureManager); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/textures.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/textures.cpp new file mode 100644 index 0000000..2b5abcc --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/textures.cpp @@ -0,0 +1,42 @@ +#include "textures.hpp" + +#include + +const char* getTextureFilename(GameTextureID id) { + switch (id) { + case GameTextureID::SPACE: + return "resources/space.png"; + case GameTextureID::PLANET: + return "resources/planet.png"; + case GameTextureID::PLANET_DEAD: + return "resources/planetDead.png"; + case GameTextureID::STAR: + return "resources/star.png"; + case GameTextureID::STAR_CONCERNED: + return "resources/starConcerned.png"; + case GameTextureID::BLACKHOLE: + return "resources/blackhole.png"; + default: + return ""; + } +} + +bool TextureManager::initialize() { + for (size_t i = 0; i < SIZE; ++i) { + GameTextureID id = static_cast(i); + const char* filename = getTextureFilename(id); + sf::Texture* texture = &textures[i]; + if (!texture->loadFromFile(filename)) { + std::cerr << "Could not open file " << filename << "\n"; + return false; + } + if (id == GameTextureID::SPACE) { + texture->setRepeated(true); + } + } + return true; +} + +const sf::Texture* TextureManager::getTexture(GameTextureID id) const { + return &textures[static_cast(id)]; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/utils.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/utils.cpp new file mode 100644 index 0000000..783fc7a --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/src/utils.cpp @@ -0,0 +1,51 @@ +#include "utils.hpp" + +#include +#include +#include + +float distance(Point2D a, Point2D b) { + float dx = a.x - b.x; + float dy = a.y - b.y; + return sqrt(dx * dx + dy * dy); +} + +float generateFloat(float min, float max) { + return min + (rand() / (RAND_MAX / (max - min))); +} + +int generateInt(int min, int max) { + return min + rand() % (max - min + 1); +} + +bool generateBool(float prob) { + return generateFloat(0.0f, 1.0f) < prob; +} + +Point2D generatePoint(const Rectangle& boundingBox) { + Point2D point = { 0.0f, 0.0f, }; + point.x = generateFloat(boundingBox.topLeft.x, boundingBox.botRight.x); + point.y = generateFloat(boundingBox.topLeft.y, boundingBox.botRight.y); + return point; +} + +Circle generateCircle(float radius, const Rectangle& boundingBox) { + Circle circle = { { 0.0f, 0.0f }, 0.0f }; + if (radius > std::min(width(boundingBox), height(boundingBox))) { + return circle; + } + circle.center.x = generateFloat( + boundingBox.topLeft.x + radius, + boundingBox.botRight.x - radius + ); + circle.center.y = generateFloat( + boundingBox.topLeft.x + radius, + boundingBox.botRight.x - radius + ); + circle.radius = radius; + return circle; +} + +Rectangle generateRectangle(const Rectangle& boundingBox) { + return createRectangle(generatePoint(boundingBox), generatePoint(boundingBox)); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/task-info.yaml b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/task-info.yaml new file mode 100644 index 0000000..2d169a0 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/task-info.yaml @@ -0,0 +1,46 @@ +type: edu +custom_name: Inheritance +files: +- name: src/cgobject.cpp + visible: true +- name: src/engine.cpp + visible: true +- name: src/scenes.cpp + visible: true +- name: src/textures.cpp + visible: true +- name: src/scene.cpp + visible: true +- name: src/statscene.cpp + visible: true +- name: src/dynscene.cpp + visible: true +- name: src/gobject.cpp + visible: true +- name: src/gobjectlist.cpp + visible: true +- name: src/player.cpp + visible: true +- name: src/consumable.cpp + visible: true +- name: src/enemy.cpp + visible: true +- name: src/collision.cpp + visible: true +- name: src/direction.cpp + visible: true +- name: src/rectangle.cpp + visible: true +- name: src/point.cpp + visible: true +- name: src/operators.cpp + visible: true +- name: src/utils.cpp + visible: true +- name: CMakeLists.txt + visible: false +- name: src/main.cpp + visible: true + editable: false +- name: test/test.cpp + visible: false diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/task-remote-info.yaml b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/task-remote-info.yaml new file mode 100644 index 0000000..50eaea2 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/task-remote-info.yaml @@ -0,0 +1 @@ +id: 980879362 diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/task.md b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/task.md new file mode 100644 index 0000000..c1314e0 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/task.md @@ -0,0 +1,122 @@ +As we have mentioned, the `GameObject` class itself +does not specify the state of game objects — it just describes their behavior. +We need another class to extend `GameObject` with an actual state. + +Fortunately, object-oriented programming has a suitable concept for this job — +it is called class _inheritance_. +The inheritance mechanism allows extending an existing class +and providing concrete implementations for its virtual functions. +A _derived_ class, also called a _subclass_ (_subtype_), +inherits all the method and fields of the _base class_ (_base type_), +and can add its own methods and fields. + +Returning to our problem, let us define +a `CircleGameObject` subclass of the `GameObject` class. +Instances of the `CircleGameObject` class represent +game objects of a circular shape — like the planet object controlled by the player. + +Have a look at the `CircleGameObject` class definition. +The semicolon syntax: + +``` +class CircleGameObject : public GameObject +``` + +indicates that `CircleGameObject` is a subclass of `GameObject`. + +For the time being, let us again ignore the `public` and `private` keywords +used in the `CircleGameObject` class definition. + +Instead, note that `CircleGameObject` declares not only methods, but also two fields: +* the `circle` field stores its shape data; +* the `status` field stores its current status. + +The very first method of the `CircleGameObject` class is a special method called a _constructor_. +Constructors have the same name as the class itself, +and, in this case, it takes a single `circle` argument: `CircleGameObject(Circle circle)`. +A constructor is called to create an instance of an object and initialize its state. + +
+ +The `explicit` [specifier](https://en.cppreference.com/w/cpp/language/explicit) +before a constructor prevents implicit type casts. +In C++, if a class has a constructor with a single argument +that is not marked with the `explicit` keyword, +then the compiler can automatically convert the argument type to the class type +when necessary. + +For example, if the constructor `CircleGameObject(Circle circle)` had not been +marked as `explicit`, the following code would be able to compile: + +```c++ +void foo(CircleGameObject object) { /* ... */ } + +int main() { + Circle circle = { { 0.0f, 0.0f, }, 10.0f }; + // `Circle` will be implicitly converted into `CircleGameObject` + // by calling the `CircleGameObject` constructor. + foo(circle); +} +``` + +However, with the constructor marked as `explicit`, the code fragment above would not compile, +and it should be rewritten as follows: + +```c++ +void foo(CircleGameObject object) { /* ... */ } + +int main() { + Circle circle = { { 0.0f, 0.0f, }, 10.0f }; + CircleGameObject object(circle); + foo(object); +} +``` + +In the C++ language, the use of `explicit` constructors is generally encouraged, +as it results in more predictable behavior. + +
+ +The definition of the `CircleGameObject` constructor body contains some new interesting syntax: + +```c++ +CircleGameObject::CircleGameObject(Circle circle) + : circle(circle) + , status(GameObjectStatus::NORMAL) +{} +``` + +After the argument list comes the colon `:`, followed by a list of the object's fields. +The value provided in the brackets next to the field's name is used to initialize the corresponding field. +Please note that the order of the fields in the _constructor initializer list_ is important. +It should match the order in which the fields are declared in the class. +After the constructor initializer list comes the constructor body `{}` (empty in this case). +Just like regular methods, it can contain any C++ statements. + +A constructor has its counterpart — the _destructor_ method, +which should have the same name as the class prefixed with `~`. +This method is automatically called before the destruction of an object to perform some clean-up routines. +A class can have several constructors with different arguments, +but there can be only one destructor, which does not take any arguments. + +In fact, you may have already seen a destructor in the previous step: +the `GameObject` class has a virtual destructor `~GameObject()`. +The `= default` syntax at the end of its definition indicates that +this destructor has a default auto-generated implementation. + +As we will see later in the course, constructors and destructors have a pivotal role in C++. + +Going back to the `CircleGameObject` class, consider its methods. +Some of them, like `getPosition` and `setPosition`, are just redeclarations of methods from the `GameObject` class. +The keyword `override` at the end of these methods' declarations indicates this fact. + +However, unlike the `GameObject` class, the `CircleGameObject` class can actually define the behavior of these methods. +To be precise, it is your task to implement some of them, +namely `getPosition`, `setPosition`, `getStatus`, `setStatus`, and `getCircle`. + +
+ +Note that the position of a `CircleGameObject` corresponds to the center of its circle. + +
+ diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/test/test.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/test/test.cpp new file mode 100644 index 0000000..ce201e3 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Inheritance/test/test.cpp @@ -0,0 +1,144 @@ +#include + +#include + +#include "gobject.hpp" +#include "operators.hpp" + +#include "testscene.hpp" +#include "testing.hpp" + +testing::Environment* const env = + testing::AddGlobalTestEnvironment(new TestEnvironment); + +const float MIN = -10e3; +const float MAX = 10e3; + +const Rectangle generateArea = { { MIN, MIN }, { MAX, MAX } }; + +std::string getPosition_error_msg(CircleGameObject* object, Point2D expected, Point2D actual) { + std::ostringstream stream; + stream << "Testing expression:\n" + << " object.getPosition()" << "\n"; + stream << "Expected result:\n" + << " object.getPosition() = " << expected << "\n"; + stream << "Actual result:\n" + << " object.getPosition() = " << actual << "\n"; + return stream.str(); +} + +TEST(CircleGameObjectTest, GetPositionTest) { + Point2D center = { 10.0f, 10.0f }; + float radius = 30.0f; + TestCircleGameObject object = TestCircleGameObject( + Circle { center, radius }, + Point2D { 0.0f, 0.0f }, + GameObjectKind::CONSUMABLE + ); + ASSERT_FLOAT_EQ(center.x, object.getPosition().x) + << getPosition_error_msg(&object, center, object.getPosition()); + ASSERT_FLOAT_EQ(center.y, object.getPosition().y) + << getPosition_error_msg(&object, center, object.getPosition()); +} + +std::string setPosition_error_msg(CircleGameObject* object, Point2D position, Point2D expected, Point2D actual) { + std::ostringstream stream; + stream << "Testing expression:\n" + << " object.setPosition(p)" << "\n"; + stream << "Test data:" << "\n" + << " object.getPosition() = " << object->getPosition() << "\n" + << " p = " << position << "\n"; + stream << "Expected result:\n" + << " object.getPosition() = " << expected << "\n"; + stream << "Actual result:\n" + << " object.getPosition() = " << actual << "\n"; + return stream.str(); +} + +TEST(CircleGameObjectTest, SetPositionTest) { + Point2D position = { 10.0f, 10.0f }; + TestCircleGameObject object = TestCircleGameObject(); + object.performSetPosition(position); + ASSERT_FLOAT_EQ(position.x, object.getPosition().x) + << setPosition_error_msg(&object, position, position, object.getPosition()); + ASSERT_FLOAT_EQ(position.y, object.getPosition().y) + << setPosition_error_msg(&object, position, position, object.getPosition()); +} + +std::string getStatus_error_msg(CircleGameObject* object, GameObjectStatus expected, GameObjectStatus actual) { + std::ostringstream stream; + stream << "Testing expression:\n" + << " object.getStatus()" << "\n"; + stream << "Expected result:\n" + << " object.getStatus() = " << expected << "\n"; + stream << "Actual result:\n" + << " object.getStatus() = " << actual << "\n"; + return stream.str(); +} + +TEST(CircleGameObjectTest, GetStatusTest) { + TestCircleGameObject object = TestCircleGameObject(); + ASSERT_EQ(GameObjectStatus::NORMAL, object.getStatus()) + << getStatus_error_msg(&object, GameObjectStatus::NORMAL, object.getStatus()); +} + +std::string setStatus_error_msg(CircleGameObject* object, GameObjectStatus status, GameObjectStatus expected, GameObjectStatus actual) { + std::ostringstream stream; + stream << "Testing expression:\n" + << " object.setStatus(status)" << "\n"; + stream << "Test data:" << "\n" + << " object.getStatus() = " << object->getStatus() << "\n" + << " status = " << status << "\n"; + stream << "Expected result:\n" + << " object.getStatus() = " << expected << "\n"; + stream << "Actual result:\n" + << " object.getStatus() = " << actual << "\n"; + return stream.str(); +} + +TEST(CircleGameObjectTest, SetStatusTest) { + TestCircleGameObject object = TestCircleGameObject(); + + TestCircleGameObject actual1 = TestCircleGameObject(); + actual1.performSetStatus(GameObjectStatus::NORMAL); + ASSERT_EQ(GameObjectStatus::NORMAL, actual1.getStatus()) + << setStatus_error_msg(&object, GameObjectStatus::NORMAL, GameObjectStatus::NORMAL, actual1.getStatus()); + + TestCircleGameObject actual2 = TestCircleGameObject(); + actual2.performSetStatus(GameObjectStatus::WARNED); + ASSERT_EQ(GameObjectStatus::WARNED, actual2.getStatus()) + << setStatus_error_msg(&object, GameObjectStatus::WARNED, GameObjectStatus::WARNED, actual2.getStatus()); + + TestCircleGameObject actual3 = TestCircleGameObject(); + actual3.performSetStatus(GameObjectStatus::DESTROYED); + ASSERT_EQ(GameObjectStatus::DESTROYED, actual3.getStatus()) + << setStatus_error_msg(&object, GameObjectStatus::DESTROYED, GameObjectStatus::DESTROYED, actual3.getStatus()); +} + +std::string getCircle_error_msg(CircleGameObject* object, Point2D position, Circle expected, Circle actual) { + std::ostringstream stream; + stream << "Testing expression:\n" + << " object.setPosition(p)" << "\n"; + stream << "Test data:" << "\n" + << " object.getCircle() = " << object->getCircle() << "\n" + << " p = " << position << "\n"; + stream << "Expected result:\n" + << " object.getCircle() = " << expected << "\n"; + stream << "Actual result:\n" + << " object.getCircle() = " << actual << "\n"; + return stream.str(); +} + +TEST(CircleGameObjectTest, GetCircleTest) { + TestCircleGameObject object = TestCircleGameObject(); + Point2D position = Point2D { 150.0f, 300.0f }; + Circle expected = Circle { position, CONSUMABLE_RADIUS }; + TestCircleGameObject actual = TestCircleGameObject(); + actual.performSetPosition(position); + ASSERT_FLOAT_EQ(expected.center.x, actual.getCircle().center.x) + << getCircle_error_msg(&object, position, expected, actual.getCircle()); + ASSERT_FLOAT_EQ(expected.center.y, actual.getCircle().center.y) + << getCircle_error_msg(&object, position, expected, actual.getCircle()); + ASSERT_FLOAT_EQ(expected.radius, actual.getCircle().radius) + << getCircle_error_msg(&object, position, expected, actual.getCircle()); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/CMakeLists.txt b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/CMakeLists.txt new file mode 100644 index 0000000..c675121 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.25) + +project(ObjectOrientedProgramming-ClassesAndObjects-IntroducingObjects) + +set(SRC + src/main.cpp + src/engine.cpp src/scenes.cpp src/textures.cpp + src/scene.cpp src/statscene.cpp src/dynscene.cpp + src/gobject.cpp src/gobjectlist.cpp src/cgobject.cpp + src/player.cpp src/consumable.cpp src/enemy.cpp + src/collision.cpp src/direction.cpp + src/rectangle.cpp src/point.cpp + src/operators.cpp src/utils.cpp +) + +set(TEST + test/test.cpp) + +add_executable(${PROJECT_NAME}-run ${SRC}) + +configure_test_target(${PROJECT_NAME}-test "${SRC}" ${TEST}) + +prepare_sfml_framework_lesson_task( + "${CMAKE_CURRENT_SOURCE_DIR}/.." + ${PROJECT_NAME}-run + ${PROJECT_NAME}-test +) diff --git a/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/cgobject.cpp b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/cgobject.cpp new file mode 100644 index 0000000..6b24599 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/cgobject.cpp @@ -0,0 +1,47 @@ +#include "cgobject.hpp" + +#include "operators.hpp" + +CircleGameObject::CircleGameObject(Circle circle) + : circle(circle) + , status(GameObjectStatus::NORMAL) +{} + +Point2D CircleGameObject::getPosition() const { + // TODO: write your solution here +} + +void CircleGameObject::setPosition(Point2D position) { + // TODO: write your solution here +} + +GameObjectStatus CircleGameObject::getStatus() const { + // TODO: write your solution here +} + +void CircleGameObject::setStatus(GameObjectStatus newStatus) { + // TODO: write your solution here +} + +Circle CircleGameObject::getCircle() const { + // TODO: write your solution here +} + +Rectangle CircleGameObject::getBoundingBox() const { + Point2D offset = { circle.radius, circle.radius }; + Point2D p1 = circle.center - offset; + Point2D p2 = circle.center + offset; + return createRectangle(p1, p2); +} + +void CircleGameObject::draw(sf::RenderWindow &window, TextureManager& textureManager) const { + const sf::Texture* texture = getTexture(textureManager); + if (texture == nullptr) + return; + sf::CircleShape shape; + shape.setPosition(circle.center.x, circle.center.y); + shape.setOrigin(circle.radius, circle.radius); + shape.setRadius(circle.radius); + shape.setTexture(texture); + window.draw(shape); +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/collision.cpp b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/collision.cpp new file mode 100644 index 0000000..9b56990 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/collision.cpp @@ -0,0 +1,21 @@ +#include + +#include "collision.hpp" +#include "cgobject.hpp" +#include "utils.hpp" + +CollisionInfo collisionInfo(const Circle& circle1, const Circle& circle2) { + CollisionInfo info; + info.distance = distance(circle1.center, circle2.center); + info.collide = (info.distance < circle1.radius + circle2.radius); + return info; +} + +CollisionInfo collisionInfo(const GameObject& object1, const GameObject& object2) { + const CircleGameObject* circleObject1 = dynamic_cast(&object1); + const CircleGameObject* circleObject2 = dynamic_cast(&object2); + if (circleObject1 && circleObject2) { + return collisionInfo(circleObject1->getCircle(), circleObject2->getCircle()); + } + assert(false); +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/consumable.cpp b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/consumable.cpp new file mode 100644 index 0000000..7bd2eaa --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/consumable.cpp @@ -0,0 +1,27 @@ +#include "consumable.hpp" + +#include "constants.hpp" + +ConsumableObject::ConsumableObject() + : CircleGameObject({ { CONSUMABLE_START_X, CONSUMABLE_START_Y }, CONSUMABLE_RADIUS }) +{} + +GameObjectKind ConsumableObject::getKind() const { + return GameObjectKind::CONSUMABLE; +} + +Point2D ConsumableObject::getVelocity() const { + return { 0.0f, 0.0f }; +} + +void ConsumableObject::update(sf::Time delta) { + // TODO: write your solution here +} + +void ConsumableObject::onCollision(const GameObject &object, const CollisionInfo &info) { + // TODO: write your solution here +} + +const sf::Texture* ConsumableObject::getTexture(TextureManager& textureManager) const { + // TODO: write your solution here +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/direction.cpp b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/direction.cpp new file mode 100644 index 0000000..0a2b606 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/direction.cpp @@ -0,0 +1,16 @@ +#include "direction.hpp" + +Point2D getDirection(Direction direction) { + switch (direction) { + case North: + return { 0.0f, -1.0f }; + case East: + return { 1.0f, 0.0f }; + case South: + return { 0.0f, 1.0f }; + case West: + return { -1.0f, 0.0f }; + default: + return { 0.0f, 0.0f }; + } +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/dynscene.cpp b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/dynscene.cpp new file mode 100644 index 0000000..1dbcf2e --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/dynscene.cpp @@ -0,0 +1,144 @@ +#include "dynscene.hpp" + +#include "constants.hpp" +#include "player.hpp" +#include "consumable.hpp" +#include "enemy.hpp" +#include "utils.hpp" + +const int MAX_DYNAMIC_OBJECTS_ON_SCENE = 10; +const int MAX_ENEMY_OBJECTS_ON_SCENE = 4; + +const int NEW_DYNAMIC_OBJECT_PROB = 1; +const int NEW_ENEMY_OBJECT_PROB = 10; + +GameplayDynamicScene::GameplayDynamicScene() : Scene(SCENE_WIDTH, SCENE_HEIGHT) {} + +void GameplayDynamicScene::activate() { + addNewGameObject(GameObjectKind::PLAYER); +} + +void GameplayDynamicScene::deactivate() { + // remove all the objects from the list + objects.remove([&] (const GameObject& object) { + return true; + }); +} + +SceneID GameplayDynamicScene::getID() const { + return SceneID::DYNAMIC_GAME_FIELD; +} + +SceneID GameplayDynamicScene::getNextSceneID() const { + return SceneID::DYNAMIC_GAME_FIELD; +} + +void GameplayDynamicScene::processEvent(const sf::Event& event) { + return; +} + +void GameplayDynamicScene::update(sf::Time delta) { + // update the objects' state + objects.foreach([delta] (GameObject& object) { + object.update(delta); + }); + // move objects according to their velocity and elapsed time + objects.foreach([this, delta] (GameObject& object) { + move(object, delta); + }); + // compute collision information for each pair of objects + objects.foreach([this] (GameObject& object) { + objects.foreach([this, &object] (GameObject& other) { + if (&object != &other) { + detectCollision(object, other); + } + }); + }); + // update the list of objects + updateObjectsList(); +} + +void GameplayDynamicScene::updateObjectsList() { + // remove destroyed objects from the list + objects.remove([] (const GameObject& object) { + return (object.getStatus() == GameObjectStatus::DESTROYED) + && (object.getKind() != GameObjectKind::PLAYER); + }); + // count the number of the different kinds of objects present on the scene + int consumableCount = 0; + int enemyCount = 0; + objects.foreach([&] (const GameObject& object) { + switch (object.getKind()) { + case GameObjectKind::CONSUMABLE: + ++consumableCount; + break; + case GameObjectKind::ENEMY: + ++enemyCount; + break; + default: + break; + } + }); + // add new objects of randomly chosen kind if there is enough room for them on the scene + int dynamicObjectsCount = consumableCount + enemyCount; + if (dynamicObjectsCount < MAX_DYNAMIC_OBJECTS_ON_SCENE) { + int r = rand() % 100; + int k = rand() % 100; + if (r < NEW_DYNAMIC_OBJECT_PROB) { + if (k < NEW_ENEMY_OBJECT_PROB && enemyCount < MAX_ENEMY_OBJECTS_ON_SCENE) { + addNewGameObject(GameObjectKind::ENEMY); + } else if (k > NEW_ENEMY_OBJECT_PROB) { + addNewGameObject(GameObjectKind::CONSUMABLE); + } + } + } +} + +std::shared_ptr GameplayDynamicScene::addNewGameObject(GameObjectKind kind) { + std::shared_ptr object; + while (!object) { + // create an object with default position + switch (kind) { + case GameObjectKind::PLAYER: { + object = std::make_shared(); + break; + } + case GameObjectKind::CONSUMABLE: { + object = std::make_shared(); + break; + } + case GameObjectKind::ENEMY: { + object = std::make_shared(); + break; + } + } + // set random position for consumable and enemy objects + if (kind == GameObjectKind::CONSUMABLE || + kind == GameObjectKind::ENEMY) { + setObjectPosition(*object, generatePoint(getBoundingBox())); + } else { + fitInto(*object); + } + // check that object does not collide with existing objects + bool collide = false; + objects.foreach([&collide, &object](GameObject& other) { + collide |= collisionInfo(*object, other).collide; + }); + // reset a colliding object + if (collide) { + object = nullptr; + } + } + objects.insert(object); + return object; +} + +void GameplayDynamicScene::draw(sf::RenderWindow &window, TextureManager& textureManager) { + // draw background + drawBackground(window, textureManager.getTexture(GameTextureID::SPACE)); + // draw all objects on the scene + objects.foreach([&] (const GameObject& object) { + object.draw(window, textureManager); + }); +} + diff --git a/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/enemy.cpp b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/enemy.cpp new file mode 100644 index 0000000..3bb34d8 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/enemy.cpp @@ -0,0 +1,44 @@ +#include "enemy.hpp" + +#include "constants.hpp" +#include "operators.hpp" +#include "utils.hpp" + +EnemyObject::EnemyObject() + : CircleGameObject({ { ENEMY_START_X, ENEMY_START_Y }, ENEMY_RADIUS }) +{} + +GameObjectKind EnemyObject::getKind() const { + return GameObjectKind::ENEMY; +} + +Point2D EnemyObject::getVelocity() const { + return velocity; +} + +void EnemyObject::update(sf::Time delta) { + if (CircleGameObject::getStatus() == GameObjectStatus::DESTROYED) + return; + updateTimer += delta; + if (updateTimer < sf::seconds(1.0f)) + return; + updateTimer = sf::milliseconds(0.0f); + updateVelocity(); +} + +void EnemyObject::setVelocity(Point2D velocity) { + this->velocity = velocity; +} + +void EnemyObject::updateVelocity() { + // TODO: write your solution here +} + +void EnemyObject::onCollision(const GameObject &object, const CollisionInfo &info) { + return; +} + +const sf::Texture* EnemyObject::getTexture(TextureManager& textureManager) const { + // TODO: write your solution here + return nullptr; +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/engine.cpp b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/engine.cpp new file mode 100644 index 0000000..c506cdf --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/engine.cpp @@ -0,0 +1,89 @@ +#include "engine.hpp" + +#include "constants.hpp" + +GameEngine *GameEngine::create() { + // TODO: write your solution here + return nullptr; +} + +GameEngine::GameEngine() + : scene(nullptr) + , active(true) +{ + // initialize random number generator + srand(time(nullptr)); + // initialize resource managers + active &= sceneManager.initialize(); + active &= textureManager.initialize(); + if (!active) { + return; + } + // set the current scene + scene = sceneManager.getCurrentScene(); + // initialize the application window + window.create(sf::VideoMode(WINDOW_WIDTH, WINDOW_HEIGHT), "Space Game"); + window.setFramerateLimit(60); +} + +void GameEngine::run() { + sf::Clock clock; + while (isActive()) { + sf::Time delta = clock.restart(); + processInput(); + update(delta); + render(); + sceneTransition(); + } +} + +bool GameEngine::isActive() const { + return active && window.isOpen(); +} + +void GameEngine::close() { + active = false; + window.close(); +} + +void GameEngine::processInput() { + sf::Event event; + while (window.pollEvent(event)) { + processEvent(event); + } +} + +void GameEngine::processEvent(const sf::Event &event) { + switch (event.type) { + case sf::Event::Closed: + close(); + break; + default: + scene->processEvent(event); + break; + } +} + +void GameEngine::update(sf::Time delta) { + scene->update(delta); +} + +void GameEngine::render() { + window.clear(sf::Color::White); + scene->draw(window, textureManager); + window.display(); +} + +void GameEngine::sceneTransition() { + if (scene->getNextSceneID() != scene->getID()) { + sceneManager.transitionScene(scene->getNextSceneID()); + scene = sceneManager.getCurrentScene(); + resizeWindow(); + } +} + +void GameEngine::resizeWindow() { + Rectangle sceneBox = scene->getBoundingBox(); + sf::Vector2u sceneSize = sf::Vector2u(width(sceneBox), height(sceneBox)); + window.setSize(sceneSize); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/gobject.cpp b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/gobject.cpp new file mode 100644 index 0000000..a673ef5 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/gobject.cpp @@ -0,0 +1,7 @@ +#include "gobject.hpp" + +#include "operators.hpp" + +void GameObject::move(Point2D vector) { + setPosition(getPosition() + vector); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/gobjectlist.cpp b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/gobjectlist.cpp new file mode 100644 index 0000000..5b8b35e --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/gobjectlist.cpp @@ -0,0 +1,54 @@ +#include "gobjectlist.hpp" + +void GameObjectList::link(GameObjectList::Node *cursor, std::unique_ptr &&node) { + // TODO: write your solution here +} + +void GameObjectList::unlink(GameObjectList::Node *node) { + // TODO: write your solution here +} + +GameObjectList::GameObjectList() { + // TODO: write your solution here +} + +void GameObjectList::insert(const std::shared_ptr &object) { + // TODO: write your solution here +} + +void GameObjectList::remove(const std::function &pred) { + GameObjectList::Node* curr = head->next.get(); + while (curr != tail) { + Node* next = curr->next.get(); + if (pred(*curr->object)) { + unlink(curr); + } + curr = next; + } +} + +void GameObjectList::foreach(const std::function& apply) { + Node* curr = head->next.get(); + while (curr != tail) { + apply(*curr->object); + curr = curr->next.get(); + } +} + +GameObjectList::GameObjectList(const GameObjectList &other) : GameObjectList() { + // TODO: write your solution here +} + +GameObjectList::GameObjectList(GameObjectList &&other) noexcept : GameObjectList() { + // TODO: write your solution here +} + +GameObjectList &GameObjectList::operator=(GameObjectList other) { + // TODO: write your solution here + return *this; +} + +void swap(GameObjectList& first, GameObjectList& second) { + using std::swap; + // TODO: write your solution here +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/main.cpp b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/main.cpp new file mode 100644 index 0000000..b192818 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/main.cpp @@ -0,0 +1,13 @@ +#include "engine.hpp" + +#include + +int main() { + GameEngine* engine = GameEngine::create(); + if (!engine) { + std::cout << "Game engine is not created!\n"; + return 1; + } + GameEngine::create()->run(); + return 0; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/operators.cpp b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/operators.cpp new file mode 100644 index 0000000..4dc9097 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/operators.cpp @@ -0,0 +1,42 @@ +#include "game.hpp" + +Point2D operator+(Point2D a, Point2D b) { + return add(a, b); +} + +Point2D operator-(Point2D a) { + return { -a.x, -a.y }; +} + +Point2D operator-(Point2D a, Point2D b) { + return add(a, -b); +} + +Point2D operator*(float s, Point2D a) { + return mul(s, a); +} + +Circle operator+(Circle c, Point2D v) { + return { c.center + v, c.radius }; +} + +Circle operator-(Circle c, Point2D v) { + return { c.center - v, c.radius }; +} + +Rectangle operator+(Rectangle r, Point2D v) { + return { r.topLeft + v, r.botRight + v }; +} + +Rectangle operator-(Rectangle r, Point2D v) { + return { r.topLeft - v, r.botRight - v }; +} + +Circle operator*(float s, Circle c) { + return { c.center, s * c.radius }; +} + +Rectangle operator*(float s, Rectangle r) { + Point2D v = { width(r), height(r) }; + return { r.topLeft, r.topLeft + s * v }; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/player.cpp b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/player.cpp new file mode 100644 index 0000000..e335cf0 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/player.cpp @@ -0,0 +1,46 @@ +#include "player.hpp" + +#include "constants.hpp" +#include "direction.hpp" +#include "operators.hpp" + +PlayerObject::PlayerObject() + : CircleGameObject({ { PLAYER_START_X, PLAYER_START_Y }, RADIUS }) +{} + +GameObjectKind PlayerObject::getKind() const { + return GameObjectKind::PLAYER; +} + +Point2D PlayerObject::getVelocity() const { + Point2D velocity = { 0.0f, 0.0f }; + if (CircleGameObject::getStatus() == GameObjectStatus::DESTROYED) + return velocity; + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) { + velocity = velocity + getDirection(North); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) { + velocity = velocity + getDirection(East); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) { + velocity = velocity + getDirection(South); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) { + velocity = velocity + getDirection(West); + } + velocity = SPEED * velocity; + return velocity; +} + +void PlayerObject::update(sf::Time delta) { + return; +} + +const sf::Texture* PlayerObject::getTexture(TextureManager& textureManager) const { + // TODO: write your solution here + return nullptr; +} + +void PlayerObject::onCollision(const GameObject &object, const CollisionInfo &info) { + // TODO: write your solution here +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/point.cpp b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/point.cpp new file mode 100644 index 0000000..5c74f69 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/point.cpp @@ -0,0 +1,19 @@ +#include "game.hpp" + +Point2D add(Point2D a, Point2D b) { + Point2D c = { 0, 0 }; + c.x = a.x + b.x; + c.y = a.y + b.y; + return c; +} + +Point2D mul(float s, Point2D a) { + Point2D b = { 0, 0 }; + b.x = s * a.x; + b.y = s * a.y; + return b; +} + +Point2D move(Point2D position, Point2D velocity, float delta) { + return add(position, mul(delta, velocity)); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/rectangle.cpp b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/rectangle.cpp new file mode 100644 index 0000000..2b5a1ec --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/rectangle.cpp @@ -0,0 +1,35 @@ +#include "rectangle.hpp" + +#include "operators.hpp" + +Point2D center(const Rectangle& rect) { + // TODO: explain this C++ initialization syntax + return rect.topLeft + 0.5f * Point2D { width(rect), height(rect) }; +} + +Rectangle createRectangle(Point2D p1, Point2D p2) { + Rectangle rect; + rect.topLeft = { std::min(p1.x, p2.x), std::min(p1.y, p2.y) }; + rect.botRight = { std::max(p1.x, p2.x), std::max(p1.y, p2.y) }; + return rect; +} + +Rectangle fitInto(const Rectangle& rect, const Rectangle& intoRect) { + if (width(rect) > width(intoRect) || height(rect) > height(intoRect)) { + return rect; + } + Point2D vector = { 0.0f, 0.0f }; + if (rect.topLeft.x < intoRect.topLeft.x) { + vector.x += intoRect.topLeft.x - rect.topLeft.x; + } + if (rect.topLeft.y < intoRect.topLeft.y) { + vector.y += intoRect.topLeft.y - rect.topLeft.y; + } + if (rect.botRight.x > intoRect.botRight.x) { + vector.x += intoRect.botRight.x - rect.botRight.x; + } + if (rect.botRight.y > intoRect.botRight.y) { + vector.y += intoRect.botRight.y - rect.botRight.y; + } + return rect + vector; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/scene.cpp b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/scene.cpp new file mode 100644 index 0000000..81a4470 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/scene.cpp @@ -0,0 +1,45 @@ +#include "scene.hpp" + +#include "operators.hpp" + +Scene::Scene(float width, float height) + : width(width) + , height(height) +{} + +Rectangle Scene::getBoundingBox() const { + Rectangle box; + box.topLeft = { 0.0f, 0.0f }; + box.botRight = { width, height }; + return box; +} + +void Scene::setObjectPosition(GameObject& object, Point2D position) { + // TODO: write your solution here +} + +void Scene::move(GameObject &object, Point2D vector) { + // TODO: write your solution here +} + +void Scene::move(GameObject& object, sf::Time delta) { + move(object, 0.001f * delta.asMilliseconds() * object.getVelocity()); +} + +void Scene::fitInto(GameObject &object) { + Rectangle rect = ::fitInto(object.getBoundingBox(), getBoundingBox()); + object.setPosition(center(rect)); +} + +void Scene::detectCollision(GameObject& object1, GameObject& object2) { + CollisionInfo info = collisionInfo(object1, object2); + object1.onCollision(object2, info); + object2.onCollision(object1, info); +} + +void Scene::drawBackground(sf::RenderWindow &window, const sf::Texture* texture) const { + sf::Sprite background; + background.setTexture(*texture); + background.setTextureRect(sf::IntRect(0, 0, width, height)); + window.draw(background); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/scenes.cpp b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/scenes.cpp new file mode 100644 index 0000000..7125bb8 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/scenes.cpp @@ -0,0 +1,14 @@ +#include "scenes.hpp" + +bool SceneManager::initialize() { + staticScene.activate(); + return true; +} + +Scene* SceneManager::getCurrentScene() { + return &staticScene; +} + +void SceneManager::transitionScene(SceneID id) { + return; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/statscene.cpp b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/statscene.cpp new file mode 100644 index 0000000..a6abdfa --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/statscene.cpp @@ -0,0 +1,46 @@ +#include "statscene.hpp" + +#include "constants.hpp" + +GameplayStaticScene::GameplayStaticScene() : Scene(SCENE_WIDTH, SCENE_HEIGHT) {} + +void GameplayStaticScene::activate() { + fitInto(player); + fitInto(consumable); + fitInto(enemy); +} + +void GameplayStaticScene::deactivate() { + return; +} + +SceneID GameplayStaticScene::getID() const { + return SceneID::STATIC_GAME_FIELD; +} + +SceneID GameplayStaticScene::getNextSceneID() const { + return SceneID::STATIC_GAME_FIELD; +} + +void GameplayStaticScene::processEvent(const sf::Event& event) { + return; +} + +void GameplayStaticScene::update(sf::Time delta) { + player.update(delta); + consumable.update(delta); + enemy.update(delta); + move(player, delta); + move(consumable, delta); + move(enemy, delta); + detectCollision(player, consumable); + detectCollision(enemy, player); + detectCollision(enemy, consumable); +} + +void GameplayStaticScene::draw(sf::RenderWindow &window, TextureManager& textureManager) { + drawBackground(window, textureManager.getTexture(GameTextureID::SPACE)); + player.draw(window, textureManager); + consumable.draw(window, textureManager); + enemy.draw(window, textureManager); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/textures.cpp b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/textures.cpp new file mode 100644 index 0000000..2b5abcc --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/textures.cpp @@ -0,0 +1,42 @@ +#include "textures.hpp" + +#include + +const char* getTextureFilename(GameTextureID id) { + switch (id) { + case GameTextureID::SPACE: + return "resources/space.png"; + case GameTextureID::PLANET: + return "resources/planet.png"; + case GameTextureID::PLANET_DEAD: + return "resources/planetDead.png"; + case GameTextureID::STAR: + return "resources/star.png"; + case GameTextureID::STAR_CONCERNED: + return "resources/starConcerned.png"; + case GameTextureID::BLACKHOLE: + return "resources/blackhole.png"; + default: + return ""; + } +} + +bool TextureManager::initialize() { + for (size_t i = 0; i < SIZE; ++i) { + GameTextureID id = static_cast(i); + const char* filename = getTextureFilename(id); + sf::Texture* texture = &textures[i]; + if (!texture->loadFromFile(filename)) { + std::cerr << "Could not open file " << filename << "\n"; + return false; + } + if (id == GameTextureID::SPACE) { + texture->setRepeated(true); + } + } + return true; +} + +const sf::Texture* TextureManager::getTexture(GameTextureID id) const { + return &textures[static_cast(id)]; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/utils.cpp b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/utils.cpp new file mode 100644 index 0000000..783fc7a --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/src/utils.cpp @@ -0,0 +1,51 @@ +#include "utils.hpp" + +#include +#include +#include + +float distance(Point2D a, Point2D b) { + float dx = a.x - b.x; + float dy = a.y - b.y; + return sqrt(dx * dx + dy * dy); +} + +float generateFloat(float min, float max) { + return min + (rand() / (RAND_MAX / (max - min))); +} + +int generateInt(int min, int max) { + return min + rand() % (max - min + 1); +} + +bool generateBool(float prob) { + return generateFloat(0.0f, 1.0f) < prob; +} + +Point2D generatePoint(const Rectangle& boundingBox) { + Point2D point = { 0.0f, 0.0f, }; + point.x = generateFloat(boundingBox.topLeft.x, boundingBox.botRight.x); + point.y = generateFloat(boundingBox.topLeft.y, boundingBox.botRight.y); + return point; +} + +Circle generateCircle(float radius, const Rectangle& boundingBox) { + Circle circle = { { 0.0f, 0.0f }, 0.0f }; + if (radius > std::min(width(boundingBox), height(boundingBox))) { + return circle; + } + circle.center.x = generateFloat( + boundingBox.topLeft.x + radius, + boundingBox.botRight.x - radius + ); + circle.center.y = generateFloat( + boundingBox.topLeft.x + radius, + boundingBox.botRight.x - radius + ); + circle.radius = radius; + return circle; +} + +Rectangle generateRectangle(const Rectangle& boundingBox) { + return createRectangle(generatePoint(boundingBox), generatePoint(boundingBox)); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/task-info.yaml b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/task-info.yaml new file mode 100644 index 0000000..5295a52 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/task-info.yaml @@ -0,0 +1,46 @@ +type: edu +custom_name: Introducing Objects +files: +- name: src/gobject.cpp + visible: true +- name: src/engine.cpp + visible: true +- name: src/scenes.cpp + visible: true +- name: src/textures.cpp + visible: true +- name: src/scene.cpp + visible: true +- name: src/statscene.cpp + visible: true +- name: src/dynscene.cpp + visible: true +- name: src/gobjectlist.cpp + visible: true +- name: src/cgobject.cpp + visible: true +- name: src/player.cpp + visible: true +- name: src/consumable.cpp + visible: true +- name: src/enemy.cpp + visible: true +- name: src/collision.cpp + visible: true +- name: src/direction.cpp + visible: true +- name: src/rectangle.cpp + visible: true +- name: src/point.cpp + visible: true +- name: src/operators.cpp + visible: true +- name: src/utils.cpp + visible: true +- name: CMakeLists.txt + visible: false +- name: src/main.cpp + visible: true + editable: false +- name: test/test.cpp + visible: false diff --git a/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/task-remote-info.yaml b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/task-remote-info.yaml new file mode 100644 index 0000000..02f9ab0 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/task-remote-info.yaml @@ -0,0 +1 @@ +id: 1543486324 diff --git a/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/task.md b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/task.md new file mode 100644 index 0000000..391836a --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/task.md @@ -0,0 +1,87 @@ +Let us finally dive into the object-oriented programming paradigm. + +At its core lies the concept of an _object_. +An object groups together some data, called the object's _state_, +with a set of functions operating on this data. +These functions are also called the _methods_ of the object. +In this way, objects are similar to structures, but unlike plain structures, +they also allow the addition of functions into their definition. + +Objects are grouped into _classes_. +A class defines a blueprint from which objects are created. +In other words, a _class_ is just a type of objects. + +For example, let us consider the `GameObject` class. +The objects of this class represent entities appearing on the game scene, +such as the planet object controlled by the player, +consumable star objects, and others that we will add later. +Instances of this class will store data related to a game object, +such as its position on the scene, and some methods to manipulate the object. + +Let us have a look at the `GameObject` class definition. +First, note that in C++, a new class is defined with the help of the `class` keyword. +Next comes the keyword `public` — we will describe its meaning in the later steps. +After that come the methods of the class. +There are plenty of these, and you can get the meaning of each method +by consulting its _documentation_, given as a docstring comment in front of the method declaration. + +
+ +A docstring is a specially formated comment used to document a specific segment of code. +This format is widely used in C/C++ libraries and frameworks to document their API. +Dedicated tools like [Doxygen](https://www.doxygen.nl/index.html) +can scan the source code of your program and extract these docstring comments +to produce documentation in various formats, such as HTML or PDF. + +The docstring format supports various annotations used to document +specific aspects of a given code block. +For example, the `@param` annotation can be used to document the arguments of a function, +the `@return` annotation can be used to document the return value of a function, etc. +You can learn more about the docstring format by consulting +the dedicated [page](https://www.doxygen.nl/manual/docblocks.html). + +
+ +The `GameObject` class itself does not define any data fields, only methods. +It, however, implicitly defines a bunch of _properties_ of an object, for example, its position. +A property's value can be requested using its _getter_ method (e.g., `getPosition`), +and it can be changed using its _setter_ method (e.g., `setPosition`). +Note that some object properties have both _getter_ and _setter_ methods, +like the aforementioned `getPosition` and `setPosition` methods, +while others have only a _getter_, for example, `getVelocity`. +There's a reason for this — some properties are derivatives of the objects' current status +and they cannot be directly changed from the outside. + +Another piece of unfamiliar syntax here is the `const` keyword coming after the arguments of a method. +It denotes _constant methods_ — those methods that cannot change the state of the object. + +Finally, the `virtual` keyword denotes _virtual_ methods — these are methods +that can be _overridden_ by inheritors of the class +(we will delve back into inheritance in the next task). +The `= 0` syntax at the end of the virtual method indicates that +it is a _pure virtual_ method. +Such a method is not implemented for the given class — +it is just a stub for an actual method implementation. + +Classes that do not declare any data fields and contain pure virtual methods are also called _interfaces_. +In some sense, interfaces just provide a description of the objects' behavior, +without actually specifying their internal status. +This leaves a programmer an opportunity to define several _subclasses_ +of an interface that provide different implementations of its behavior. +For example, in the following steps of this lesson, you will need to implement +subclasses for playable objects, consumable objects, and others. + +Before moving to the following step, please complete a small programming exercise. +One of the methods of the `GameObject` class — the `move` method — is actually not `virtual`. +This is because it can be implemented using other methods of this class, namely `getPosition` and `setObjectPosition`. +For the programming assignment, please provide an implementation of this method. + +The implementation of the method should be put into the `gobject.cpp` file. +Note that the methods' definitions given in this file contains both +the name of the class and the name of the method, separated by `::`. + +```c++ +void GameObject::move(Point2D vector) { + ... +} +``` diff --git a/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/test/test.cpp b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/test/test.cpp new file mode 100644 index 0000000..c084b46 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/IntroducingObjects/test/test.cpp @@ -0,0 +1,85 @@ +#include + +#include + +#include "gobject.hpp" +#include "operators.hpp" + +#include "testscene.hpp" +#include "testing.hpp" + +testing::Environment* const env = + testing::AddGlobalTestEnvironment(new TestEnvironment); + +const float MIN = -10e3; +const float MAX = 10e3; + +const Rectangle generateArea = { { MIN, MIN }, { MAX, MAX } }; + +std::string move_error_msg(GameObject* object, Point2D vector, Point2D expected, Point2D actual) { + std::ostringstream stream; + stream << "Testing expression:\n" + << " object.move(vector)" << "\n"; + stream << "Test data:" << "\n" + << " object.getPosition() = " << object->getPosition() << "\n" + << " vector = " << vector << "\n"; + stream << "Expected result:\n" + << " object.getPosition() = " << expected << "\n"; + stream << "Actual result:\n" + << " object.getPosition() = " << actual << "\n"; + return stream.str(); +} + +TEST(MoveTest, MoveTest0) { + property_test( + [] () { + Point2D position = generatePoint(generateArea); + Point2D velocity = Point2D { 0.0f, 0.0f }; + TestGameObject object = TestGameObject(position, velocity, + GameObjectStatus::NORMAL, + GameObjectKind::CONSUMABLE + ); + return std::make_tuple(object); + }, + [] (std::tuple data) { + TestGameObject object; + std::tie(object) = data; + TestGameObject expected = object; + TestGameObject actual = object; + Point2D vector0 = Point2D { 0.0f, 0.0f }; + actual.performMove(vector0); + ASSERT_FLOAT_EQ(expected.getPosition().x, actual.getPosition().x) + << move_error_msg(&object, vector0, expected.getPosition(), actual.getPosition()); + ASSERT_FLOAT_EQ(expected.getPosition().y, actual.getPosition().y) + << move_error_msg(&object, vector0, expected.getPosition(), actual.getPosition()); + } + ); +} + +TEST(MoveTest, MoveTestRandom) { + property_test( + [] () { + Point2D position = generatePoint(generateArea); + Point2D velocity = Point2D { 0.0f, 0.0f }; + TestGameObject object = TestGameObject(position, velocity, + GameObjectStatus::NORMAL, + GameObjectKind::CONSUMABLE + ); + Point2D vector = generatePoint(generateArea); + return std::make_tuple(object, vector); + }, + [] (std::tuple data) { + TestGameObject object; + Point2D vector; + std::tie(object, vector) = data; + TestGameObject expected = object; + expected.setPosition(expected.getPosition() + vector); + TestGameObject actual = object; + actual.performMove(vector); + ASSERT_FLOAT_EQ(expected.getPosition().x, actual.getPosition().x) + << move_error_msg(&object, vector, expected.getPosition(), actual.getPosition()); + ASSERT_FLOAT_EQ(expected.getPosition().y, actual.getPosition().y) + << move_error_msg(&object, vector, expected.getPosition(), actual.getPosition()); + } + ); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Introduction/CMakeLists.txt b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/CMakeLists.txt new file mode 100644 index 0000000..3af07ae --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.25) + +project(ObjectOrientedProgramming-ClassesAndObjects-Introduction) + +set(SRC + src/main.cpp + src/engine.cpp src/scenes.cpp src/textures.cpp + src/scene.cpp src/statscene.cpp src/dynscene.cpp + src/gobject.cpp src/gobjectlist.cpp src/cgobject.cpp + src/player.cpp src/consumable.cpp src/enemy.cpp + src/collision.cpp src/direction.cpp + src/rectangle.cpp src/point.cpp + src/operators.cpp src/utils.cpp +) + +add_executable(${PROJECT_NAME}-run ${SRC}) + +prepare_sfml_framework_lesson_task( + "${CMAKE_CURRENT_SOURCE_DIR}/.." + ${PROJECT_NAME}-run + "" +) diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/cgobject.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/cgobject.cpp new file mode 100644 index 0000000..6b24599 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/cgobject.cpp @@ -0,0 +1,47 @@ +#include "cgobject.hpp" + +#include "operators.hpp" + +CircleGameObject::CircleGameObject(Circle circle) + : circle(circle) + , status(GameObjectStatus::NORMAL) +{} + +Point2D CircleGameObject::getPosition() const { + // TODO: write your solution here +} + +void CircleGameObject::setPosition(Point2D position) { + // TODO: write your solution here +} + +GameObjectStatus CircleGameObject::getStatus() const { + // TODO: write your solution here +} + +void CircleGameObject::setStatus(GameObjectStatus newStatus) { + // TODO: write your solution here +} + +Circle CircleGameObject::getCircle() const { + // TODO: write your solution here +} + +Rectangle CircleGameObject::getBoundingBox() const { + Point2D offset = { circle.radius, circle.radius }; + Point2D p1 = circle.center - offset; + Point2D p2 = circle.center + offset; + return createRectangle(p1, p2); +} + +void CircleGameObject::draw(sf::RenderWindow &window, TextureManager& textureManager) const { + const sf::Texture* texture = getTexture(textureManager); + if (texture == nullptr) + return; + sf::CircleShape shape; + shape.setPosition(circle.center.x, circle.center.y); + shape.setOrigin(circle.radius, circle.radius); + shape.setRadius(circle.radius); + shape.setTexture(texture); + window.draw(shape); +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/collision.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/collision.cpp new file mode 100644 index 0000000..9b56990 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/collision.cpp @@ -0,0 +1,21 @@ +#include + +#include "collision.hpp" +#include "cgobject.hpp" +#include "utils.hpp" + +CollisionInfo collisionInfo(const Circle& circle1, const Circle& circle2) { + CollisionInfo info; + info.distance = distance(circle1.center, circle2.center); + info.collide = (info.distance < circle1.radius + circle2.radius); + return info; +} + +CollisionInfo collisionInfo(const GameObject& object1, const GameObject& object2) { + const CircleGameObject* circleObject1 = dynamic_cast(&object1); + const CircleGameObject* circleObject2 = dynamic_cast(&object2); + if (circleObject1 && circleObject2) { + return collisionInfo(circleObject1->getCircle(), circleObject2->getCircle()); + } + assert(false); +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/consumable.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/consumable.cpp new file mode 100644 index 0000000..7bd2eaa --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/consumable.cpp @@ -0,0 +1,27 @@ +#include "consumable.hpp" + +#include "constants.hpp" + +ConsumableObject::ConsumableObject() + : CircleGameObject({ { CONSUMABLE_START_X, CONSUMABLE_START_Y }, CONSUMABLE_RADIUS }) +{} + +GameObjectKind ConsumableObject::getKind() const { + return GameObjectKind::CONSUMABLE; +} + +Point2D ConsumableObject::getVelocity() const { + return { 0.0f, 0.0f }; +} + +void ConsumableObject::update(sf::Time delta) { + // TODO: write your solution here +} + +void ConsumableObject::onCollision(const GameObject &object, const CollisionInfo &info) { + // TODO: write your solution here +} + +const sf::Texture* ConsumableObject::getTexture(TextureManager& textureManager) const { + // TODO: write your solution here +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/direction.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/direction.cpp new file mode 100644 index 0000000..0a2b606 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/direction.cpp @@ -0,0 +1,16 @@ +#include "direction.hpp" + +Point2D getDirection(Direction direction) { + switch (direction) { + case North: + return { 0.0f, -1.0f }; + case East: + return { 1.0f, 0.0f }; + case South: + return { 0.0f, 1.0f }; + case West: + return { -1.0f, 0.0f }; + default: + return { 0.0f, 0.0f }; + } +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/dynscene.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/dynscene.cpp new file mode 100644 index 0000000..1dbcf2e --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/dynscene.cpp @@ -0,0 +1,144 @@ +#include "dynscene.hpp" + +#include "constants.hpp" +#include "player.hpp" +#include "consumable.hpp" +#include "enemy.hpp" +#include "utils.hpp" + +const int MAX_DYNAMIC_OBJECTS_ON_SCENE = 10; +const int MAX_ENEMY_OBJECTS_ON_SCENE = 4; + +const int NEW_DYNAMIC_OBJECT_PROB = 1; +const int NEW_ENEMY_OBJECT_PROB = 10; + +GameplayDynamicScene::GameplayDynamicScene() : Scene(SCENE_WIDTH, SCENE_HEIGHT) {} + +void GameplayDynamicScene::activate() { + addNewGameObject(GameObjectKind::PLAYER); +} + +void GameplayDynamicScene::deactivate() { + // remove all the objects from the list + objects.remove([&] (const GameObject& object) { + return true; + }); +} + +SceneID GameplayDynamicScene::getID() const { + return SceneID::DYNAMIC_GAME_FIELD; +} + +SceneID GameplayDynamicScene::getNextSceneID() const { + return SceneID::DYNAMIC_GAME_FIELD; +} + +void GameplayDynamicScene::processEvent(const sf::Event& event) { + return; +} + +void GameplayDynamicScene::update(sf::Time delta) { + // update the objects' state + objects.foreach([delta] (GameObject& object) { + object.update(delta); + }); + // move objects according to their velocity and elapsed time + objects.foreach([this, delta] (GameObject& object) { + move(object, delta); + }); + // compute collision information for each pair of objects + objects.foreach([this] (GameObject& object) { + objects.foreach([this, &object] (GameObject& other) { + if (&object != &other) { + detectCollision(object, other); + } + }); + }); + // update the list of objects + updateObjectsList(); +} + +void GameplayDynamicScene::updateObjectsList() { + // remove destroyed objects from the list + objects.remove([] (const GameObject& object) { + return (object.getStatus() == GameObjectStatus::DESTROYED) + && (object.getKind() != GameObjectKind::PLAYER); + }); + // count the number of the different kinds of objects present on the scene + int consumableCount = 0; + int enemyCount = 0; + objects.foreach([&] (const GameObject& object) { + switch (object.getKind()) { + case GameObjectKind::CONSUMABLE: + ++consumableCount; + break; + case GameObjectKind::ENEMY: + ++enemyCount; + break; + default: + break; + } + }); + // add new objects of randomly chosen kind if there is enough room for them on the scene + int dynamicObjectsCount = consumableCount + enemyCount; + if (dynamicObjectsCount < MAX_DYNAMIC_OBJECTS_ON_SCENE) { + int r = rand() % 100; + int k = rand() % 100; + if (r < NEW_DYNAMIC_OBJECT_PROB) { + if (k < NEW_ENEMY_OBJECT_PROB && enemyCount < MAX_ENEMY_OBJECTS_ON_SCENE) { + addNewGameObject(GameObjectKind::ENEMY); + } else if (k > NEW_ENEMY_OBJECT_PROB) { + addNewGameObject(GameObjectKind::CONSUMABLE); + } + } + } +} + +std::shared_ptr GameplayDynamicScene::addNewGameObject(GameObjectKind kind) { + std::shared_ptr object; + while (!object) { + // create an object with default position + switch (kind) { + case GameObjectKind::PLAYER: { + object = std::make_shared(); + break; + } + case GameObjectKind::CONSUMABLE: { + object = std::make_shared(); + break; + } + case GameObjectKind::ENEMY: { + object = std::make_shared(); + break; + } + } + // set random position for consumable and enemy objects + if (kind == GameObjectKind::CONSUMABLE || + kind == GameObjectKind::ENEMY) { + setObjectPosition(*object, generatePoint(getBoundingBox())); + } else { + fitInto(*object); + } + // check that object does not collide with existing objects + bool collide = false; + objects.foreach([&collide, &object](GameObject& other) { + collide |= collisionInfo(*object, other).collide; + }); + // reset a colliding object + if (collide) { + object = nullptr; + } + } + objects.insert(object); + return object; +} + +void GameplayDynamicScene::draw(sf::RenderWindow &window, TextureManager& textureManager) { + // draw background + drawBackground(window, textureManager.getTexture(GameTextureID::SPACE)); + // draw all objects on the scene + objects.foreach([&] (const GameObject& object) { + object.draw(window, textureManager); + }); +} + diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/enemy.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/enemy.cpp new file mode 100644 index 0000000..3bb34d8 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/enemy.cpp @@ -0,0 +1,44 @@ +#include "enemy.hpp" + +#include "constants.hpp" +#include "operators.hpp" +#include "utils.hpp" + +EnemyObject::EnemyObject() + : CircleGameObject({ { ENEMY_START_X, ENEMY_START_Y }, ENEMY_RADIUS }) +{} + +GameObjectKind EnemyObject::getKind() const { + return GameObjectKind::ENEMY; +} + +Point2D EnemyObject::getVelocity() const { + return velocity; +} + +void EnemyObject::update(sf::Time delta) { + if (CircleGameObject::getStatus() == GameObjectStatus::DESTROYED) + return; + updateTimer += delta; + if (updateTimer < sf::seconds(1.0f)) + return; + updateTimer = sf::milliseconds(0.0f); + updateVelocity(); +} + +void EnemyObject::setVelocity(Point2D velocity) { + this->velocity = velocity; +} + +void EnemyObject::updateVelocity() { + // TODO: write your solution here +} + +void EnemyObject::onCollision(const GameObject &object, const CollisionInfo &info) { + return; +} + +const sf::Texture* EnemyObject::getTexture(TextureManager& textureManager) const { + // TODO: write your solution here + return nullptr; +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/engine.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/engine.cpp new file mode 100644 index 0000000..c506cdf --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/engine.cpp @@ -0,0 +1,89 @@ +#include "engine.hpp" + +#include "constants.hpp" + +GameEngine *GameEngine::create() { + // TODO: write your solution here + return nullptr; +} + +GameEngine::GameEngine() + : scene(nullptr) + , active(true) +{ + // initialize random number generator + srand(time(nullptr)); + // initialize resource managers + active &= sceneManager.initialize(); + active &= textureManager.initialize(); + if (!active) { + return; + } + // set the current scene + scene = sceneManager.getCurrentScene(); + // initialize the application window + window.create(sf::VideoMode(WINDOW_WIDTH, WINDOW_HEIGHT), "Space Game"); + window.setFramerateLimit(60); +} + +void GameEngine::run() { + sf::Clock clock; + while (isActive()) { + sf::Time delta = clock.restart(); + processInput(); + update(delta); + render(); + sceneTransition(); + } +} + +bool GameEngine::isActive() const { + return active && window.isOpen(); +} + +void GameEngine::close() { + active = false; + window.close(); +} + +void GameEngine::processInput() { + sf::Event event; + while (window.pollEvent(event)) { + processEvent(event); + } +} + +void GameEngine::processEvent(const sf::Event &event) { + switch (event.type) { + case sf::Event::Closed: + close(); + break; + default: + scene->processEvent(event); + break; + } +} + +void GameEngine::update(sf::Time delta) { + scene->update(delta); +} + +void GameEngine::render() { + window.clear(sf::Color::White); + scene->draw(window, textureManager); + window.display(); +} + +void GameEngine::sceneTransition() { + if (scene->getNextSceneID() != scene->getID()) { + sceneManager.transitionScene(scene->getNextSceneID()); + scene = sceneManager.getCurrentScene(); + resizeWindow(); + } +} + +void GameEngine::resizeWindow() { + Rectangle sceneBox = scene->getBoundingBox(); + sf::Vector2u sceneSize = sf::Vector2u(width(sceneBox), height(sceneBox)); + window.setSize(sceneSize); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/gobject.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/gobject.cpp new file mode 100644 index 0000000..b1abc1e --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/gobject.cpp @@ -0,0 +1,7 @@ +#include "gobject.hpp" + +#include "operators.hpp" + +void GameObject::move(Point2D vector) { + // TODO: write your solution here +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/gobjectlist.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/gobjectlist.cpp new file mode 100644 index 0000000..5b8b35e --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/gobjectlist.cpp @@ -0,0 +1,54 @@ +#include "gobjectlist.hpp" + +void GameObjectList::link(GameObjectList::Node *cursor, std::unique_ptr &&node) { + // TODO: write your solution here +} + +void GameObjectList::unlink(GameObjectList::Node *node) { + // TODO: write your solution here +} + +GameObjectList::GameObjectList() { + // TODO: write your solution here +} + +void GameObjectList::insert(const std::shared_ptr &object) { + // TODO: write your solution here +} + +void GameObjectList::remove(const std::function &pred) { + GameObjectList::Node* curr = head->next.get(); + while (curr != tail) { + Node* next = curr->next.get(); + if (pred(*curr->object)) { + unlink(curr); + } + curr = next; + } +} + +void GameObjectList::foreach(const std::function& apply) { + Node* curr = head->next.get(); + while (curr != tail) { + apply(*curr->object); + curr = curr->next.get(); + } +} + +GameObjectList::GameObjectList(const GameObjectList &other) : GameObjectList() { + // TODO: write your solution here +} + +GameObjectList::GameObjectList(GameObjectList &&other) noexcept : GameObjectList() { + // TODO: write your solution here +} + +GameObjectList &GameObjectList::operator=(GameObjectList other) { + // TODO: write your solution here + return *this; +} + +void swap(GameObjectList& first, GameObjectList& second) { + using std::swap; + // TODO: write your solution here +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/main.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/main.cpp new file mode 100644 index 0000000..b192818 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/main.cpp @@ -0,0 +1,13 @@ +#include "engine.hpp" + +#include + +int main() { + GameEngine* engine = GameEngine::create(); + if (!engine) { + std::cout << "Game engine is not created!\n"; + return 1; + } + GameEngine::create()->run(); + return 0; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/operators.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/operators.cpp new file mode 100644 index 0000000..e746b55 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/operators.cpp @@ -0,0 +1,41 @@ +#include "game.hpp" + +Point2D operator+(Point2D a, Point2D b) { + // TODO: write your solution here +} + +Point2D operator-(Point2D a) { + // TODO: write your solution here +} + +Point2D operator-(Point2D a, Point2D b) { + // TODO: write your solution here +} + +Point2D operator*(float s, Point2D a) { + // TODO: write your solution here +} + +Circle operator+(Circle c, Point2D v) { + // TODO: write your solution here +} + +Circle operator-(Circle c, Point2D v) { + // TODO: write your solution here +} + +Rectangle operator+(Rectangle r, Point2D v) { + // TODO: write your solution here +} + +Rectangle operator-(Rectangle r, Point2D v) { + // TODO: write your solution here +} + +Circle operator*(float s, Circle c) { + // TODO: write your solution here +} + +Rectangle operator*(float s, Rectangle r) { + // TODO: write your solution here +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/player.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/player.cpp new file mode 100644 index 0000000..e335cf0 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/player.cpp @@ -0,0 +1,46 @@ +#include "player.hpp" + +#include "constants.hpp" +#include "direction.hpp" +#include "operators.hpp" + +PlayerObject::PlayerObject() + : CircleGameObject({ { PLAYER_START_X, PLAYER_START_Y }, RADIUS }) +{} + +GameObjectKind PlayerObject::getKind() const { + return GameObjectKind::PLAYER; +} + +Point2D PlayerObject::getVelocity() const { + Point2D velocity = { 0.0f, 0.0f }; + if (CircleGameObject::getStatus() == GameObjectStatus::DESTROYED) + return velocity; + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) { + velocity = velocity + getDirection(North); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) { + velocity = velocity + getDirection(East); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) { + velocity = velocity + getDirection(South); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) { + velocity = velocity + getDirection(West); + } + velocity = SPEED * velocity; + return velocity; +} + +void PlayerObject::update(sf::Time delta) { + return; +} + +const sf::Texture* PlayerObject::getTexture(TextureManager& textureManager) const { + // TODO: write your solution here + return nullptr; +} + +void PlayerObject::onCollision(const GameObject &object, const CollisionInfo &info) { + // TODO: write your solution here +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/point.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/point.cpp new file mode 100644 index 0000000..5c74f69 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/point.cpp @@ -0,0 +1,19 @@ +#include "game.hpp" + +Point2D add(Point2D a, Point2D b) { + Point2D c = { 0, 0 }; + c.x = a.x + b.x; + c.y = a.y + b.y; + return c; +} + +Point2D mul(float s, Point2D a) { + Point2D b = { 0, 0 }; + b.x = s * a.x; + b.y = s * a.y; + return b; +} + +Point2D move(Point2D position, Point2D velocity, float delta) { + return add(position, mul(delta, velocity)); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/rectangle.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/rectangle.cpp new file mode 100644 index 0000000..2b5a1ec --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/rectangle.cpp @@ -0,0 +1,35 @@ +#include "rectangle.hpp" + +#include "operators.hpp" + +Point2D center(const Rectangle& rect) { + // TODO: explain this C++ initialization syntax + return rect.topLeft + 0.5f * Point2D { width(rect), height(rect) }; +} + +Rectangle createRectangle(Point2D p1, Point2D p2) { + Rectangle rect; + rect.topLeft = { std::min(p1.x, p2.x), std::min(p1.y, p2.y) }; + rect.botRight = { std::max(p1.x, p2.x), std::max(p1.y, p2.y) }; + return rect; +} + +Rectangle fitInto(const Rectangle& rect, const Rectangle& intoRect) { + if (width(rect) > width(intoRect) || height(rect) > height(intoRect)) { + return rect; + } + Point2D vector = { 0.0f, 0.0f }; + if (rect.topLeft.x < intoRect.topLeft.x) { + vector.x += intoRect.topLeft.x - rect.topLeft.x; + } + if (rect.topLeft.y < intoRect.topLeft.y) { + vector.y += intoRect.topLeft.y - rect.topLeft.y; + } + if (rect.botRight.x > intoRect.botRight.x) { + vector.x += intoRect.botRight.x - rect.botRight.x; + } + if (rect.botRight.y > intoRect.botRight.y) { + vector.y += intoRect.botRight.y - rect.botRight.y; + } + return rect + vector; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/scene.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/scene.cpp new file mode 100644 index 0000000..81a4470 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/scene.cpp @@ -0,0 +1,45 @@ +#include "scene.hpp" + +#include "operators.hpp" + +Scene::Scene(float width, float height) + : width(width) + , height(height) +{} + +Rectangle Scene::getBoundingBox() const { + Rectangle box; + box.topLeft = { 0.0f, 0.0f }; + box.botRight = { width, height }; + return box; +} + +void Scene::setObjectPosition(GameObject& object, Point2D position) { + // TODO: write your solution here +} + +void Scene::move(GameObject &object, Point2D vector) { + // TODO: write your solution here +} + +void Scene::move(GameObject& object, sf::Time delta) { + move(object, 0.001f * delta.asMilliseconds() * object.getVelocity()); +} + +void Scene::fitInto(GameObject &object) { + Rectangle rect = ::fitInto(object.getBoundingBox(), getBoundingBox()); + object.setPosition(center(rect)); +} + +void Scene::detectCollision(GameObject& object1, GameObject& object2) { + CollisionInfo info = collisionInfo(object1, object2); + object1.onCollision(object2, info); + object2.onCollision(object1, info); +} + +void Scene::drawBackground(sf::RenderWindow &window, const sf::Texture* texture) const { + sf::Sprite background; + background.setTexture(*texture); + background.setTextureRect(sf::IntRect(0, 0, width, height)); + window.draw(background); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/scenes.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/scenes.cpp new file mode 100644 index 0000000..7125bb8 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/scenes.cpp @@ -0,0 +1,14 @@ +#include "scenes.hpp" + +bool SceneManager::initialize() { + staticScene.activate(); + return true; +} + +Scene* SceneManager::getCurrentScene() { + return &staticScene; +} + +void SceneManager::transitionScene(SceneID id) { + return; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/statscene.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/statscene.cpp new file mode 100644 index 0000000..a6abdfa --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/statscene.cpp @@ -0,0 +1,46 @@ +#include "statscene.hpp" + +#include "constants.hpp" + +GameplayStaticScene::GameplayStaticScene() : Scene(SCENE_WIDTH, SCENE_HEIGHT) {} + +void GameplayStaticScene::activate() { + fitInto(player); + fitInto(consumable); + fitInto(enemy); +} + +void GameplayStaticScene::deactivate() { + return; +} + +SceneID GameplayStaticScene::getID() const { + return SceneID::STATIC_GAME_FIELD; +} + +SceneID GameplayStaticScene::getNextSceneID() const { + return SceneID::STATIC_GAME_FIELD; +} + +void GameplayStaticScene::processEvent(const sf::Event& event) { + return; +} + +void GameplayStaticScene::update(sf::Time delta) { + player.update(delta); + consumable.update(delta); + enemy.update(delta); + move(player, delta); + move(consumable, delta); + move(enemy, delta); + detectCollision(player, consumable); + detectCollision(enemy, player); + detectCollision(enemy, consumable); +} + +void GameplayStaticScene::draw(sf::RenderWindow &window, TextureManager& textureManager) { + drawBackground(window, textureManager.getTexture(GameTextureID::SPACE)); + player.draw(window, textureManager); + consumable.draw(window, textureManager); + enemy.draw(window, textureManager); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/textures.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/textures.cpp new file mode 100644 index 0000000..2b5abcc --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/textures.cpp @@ -0,0 +1,42 @@ +#include "textures.hpp" + +#include + +const char* getTextureFilename(GameTextureID id) { + switch (id) { + case GameTextureID::SPACE: + return "resources/space.png"; + case GameTextureID::PLANET: + return "resources/planet.png"; + case GameTextureID::PLANET_DEAD: + return "resources/planetDead.png"; + case GameTextureID::STAR: + return "resources/star.png"; + case GameTextureID::STAR_CONCERNED: + return "resources/starConcerned.png"; + case GameTextureID::BLACKHOLE: + return "resources/blackhole.png"; + default: + return ""; + } +} + +bool TextureManager::initialize() { + for (size_t i = 0; i < SIZE; ++i) { + GameTextureID id = static_cast(i); + const char* filename = getTextureFilename(id); + sf::Texture* texture = &textures[i]; + if (!texture->loadFromFile(filename)) { + std::cerr << "Could not open file " << filename << "\n"; + return false; + } + if (id == GameTextureID::SPACE) { + texture->setRepeated(true); + } + } + return true; +} + +const sf::Texture* TextureManager::getTexture(GameTextureID id) const { + return &textures[static_cast(id)]; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/utils.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/utils.cpp new file mode 100644 index 0000000..783fc7a --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/src/utils.cpp @@ -0,0 +1,51 @@ +#include "utils.hpp" + +#include +#include +#include + +float distance(Point2D a, Point2D b) { + float dx = a.x - b.x; + float dy = a.y - b.y; + return sqrt(dx * dx + dy * dy); +} + +float generateFloat(float min, float max) { + return min + (rand() / (RAND_MAX / (max - min))); +} + +int generateInt(int min, int max) { + return min + rand() % (max - min + 1); +} + +bool generateBool(float prob) { + return generateFloat(0.0f, 1.0f) < prob; +} + +Point2D generatePoint(const Rectangle& boundingBox) { + Point2D point = { 0.0f, 0.0f, }; + point.x = generateFloat(boundingBox.topLeft.x, boundingBox.botRight.x); + point.y = generateFloat(boundingBox.topLeft.y, boundingBox.botRight.y); + return point; +} + +Circle generateCircle(float radius, const Rectangle& boundingBox) { + Circle circle = { { 0.0f, 0.0f }, 0.0f }; + if (radius > std::min(width(boundingBox), height(boundingBox))) { + return circle; + } + circle.center.x = generateFloat( + boundingBox.topLeft.x + radius, + boundingBox.botRight.x - radius + ); + circle.center.y = generateFloat( + boundingBox.topLeft.x + radius, + boundingBox.botRight.x - radius + ); + circle.radius = radius; + return circle; +} + +Rectangle generateRectangle(const Rectangle& boundingBox) { + return createRectangle(generatePoint(boundingBox), generatePoint(boundingBox)); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Introduction/task-info.yaml b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/task-info.yaml new file mode 100644 index 0000000..15c9b54 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/task-info.yaml @@ -0,0 +1,44 @@ +type: theory +custom_name: Introduction +files: +- name: src/engine.cpp + visible: true +- name: src/scenes.cpp + visible: true +- name: src/textures.cpp + visible: true +- name: src/scene.cpp + visible: true +- name: src/statscene.cpp + visible: true +- name: src/dynscene.cpp + visible: true +- name: src/gobject.cpp + visible: true +- name: src/gobjectlist.cpp + visible: true +- name: src/cgobject.cpp + visible: true +- name: src/player.cpp + visible: true +- name: src/consumable.cpp + visible: true +- name: src/enemy.cpp + visible: true +- name: src/collision.cpp + visible: true +- name: src/direction.cpp + visible: true +- name: src/rectangle.cpp + visible: true +- name: src/point.cpp + visible: true +- name: src/operators.cpp + visible: true +- name: src/utils.cpp + visible: true +- name: CMakeLists.txt + visible: false +- name: src/main.cpp + visible: true + editable: false diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Introduction/task-remote-info.yaml b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/task-remote-info.yaml new file mode 100644 index 0000000..c5f42d3 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/task-remote-info.yaml @@ -0,0 +1 @@ +id: 643261225 diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Introduction/task.md b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/task.md new file mode 100644 index 0000000..fb651ea --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Introduction/task.md @@ -0,0 +1,23 @@ +In the next few lessons, you will learn about the object-oriented programming paradigm +in the context of the C++ language. As you will see, this paradigm helps +to organize code into independent subcomponents, manage the complexity +of applications, and streamline the addition of new functionality. + +We will reimplement our game using features of object-oriented programming. +This effort will help us to easily extend the game by adding new kinds of objects. +In particular, we will add enemy objects to make our little game a bit more challenging. +There is a lot of fun coming, so get ready for your journey into the world of objects! + +Here is a list of specific topics that we'll be covering: + +* Operator overloading +* Classes and objects +* Abstract classes and interfaces +* Constructors and destructors +* Inheritance and subtyping +* Subtype polymorphism +* Virtual methods +* Visibility modifiers: `public`, `protected`, and `private` +* Encapsulation and class invariants +* Static methods, fields, and variables +* Objects vs structures \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/CMakeLists.txt b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/CMakeLists.txt new file mode 100644 index 0000000..c688d7f --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.25) + +project(ObjectOrientedProgramming-ClassesAndObjects-NewChallenge) + +set(SRC + src/main.cpp + src/engine.cpp src/scenes.cpp src/textures.cpp + src/scene.cpp src/statscene.cpp src/dynscene.cpp + src/gobject.cpp src/gobjectlist.cpp src/cgobject.cpp + src/player.cpp src/consumable.cpp src/enemy.cpp + src/collision.cpp src/direction.cpp + src/rectangle.cpp src/point.cpp + src/operators.cpp src/utils.cpp +) + +set(TEST + test/test.cpp) + +add_executable(${PROJECT_NAME}-run ${SRC}) + +configure_test_target(${PROJECT_NAME}-test "${SRC}" ${TEST}) + +prepare_sfml_framework_lesson_task( + "${CMAKE_CURRENT_SOURCE_DIR}/.." + ${PROJECT_NAME}-run + ${PROJECT_NAME}-test +) diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/cgobject.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/cgobject.cpp new file mode 100644 index 0000000..1d49ba3 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/cgobject.cpp @@ -0,0 +1,47 @@ +#include "cgobject.hpp" + +#include "operators.hpp" + +CircleGameObject::CircleGameObject(Circle circle) + : circle(circle) + , status(GameObjectStatus::NORMAL) +{} + +Point2D CircleGameObject::getPosition() const { + return circle.center; +} + +void CircleGameObject::setPosition(Point2D position) { + circle.center = position; +} + +GameObjectStatus CircleGameObject::getStatus() const { + return status; +} + +void CircleGameObject::setStatus(GameObjectStatus newStatus) { + status = newStatus; +} + +Circle CircleGameObject::getCircle() const { + return circle; +} + +Rectangle CircleGameObject::getBoundingBox() const { + Point2D offset = { circle.radius, circle.radius }; + Point2D p1 = circle.center - offset; + Point2D p2 = circle.center + offset; + return createRectangle(p1, p2); +} + +void CircleGameObject::draw(sf::RenderWindow &window, TextureManager& textureManager) const { + const sf::Texture* texture = getTexture(textureManager); + if (texture == nullptr) + return; + sf::CircleShape shape; + shape.setPosition(circle.center.x, circle.center.y); + shape.setOrigin(circle.radius, circle.radius); + shape.setRadius(circle.radius); + shape.setTexture(texture); + window.draw(shape); +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/collision.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/collision.cpp new file mode 100644 index 0000000..9b56990 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/collision.cpp @@ -0,0 +1,21 @@ +#include + +#include "collision.hpp" +#include "cgobject.hpp" +#include "utils.hpp" + +CollisionInfo collisionInfo(const Circle& circle1, const Circle& circle2) { + CollisionInfo info; + info.distance = distance(circle1.center, circle2.center); + info.collide = (info.distance < circle1.radius + circle2.radius); + return info; +} + +CollisionInfo collisionInfo(const GameObject& object1, const GameObject& object2) { + const CircleGameObject* circleObject1 = dynamic_cast(&object1); + const CircleGameObject* circleObject2 = dynamic_cast(&object2); + if (circleObject1 && circleObject2) { + return collisionInfo(circleObject1->getCircle(), circleObject2->getCircle()); + } + assert(false); +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/consumable.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/consumable.cpp new file mode 100644 index 0000000..d03f046 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/consumable.cpp @@ -0,0 +1,46 @@ +#include "consumable.hpp" + +#include "constants.hpp" + +ConsumableObject::ConsumableObject() + : CircleGameObject({ { CONSUMABLE_START_X, CONSUMABLE_START_Y }, CONSUMABLE_RADIUS }) +{} + +GameObjectKind ConsumableObject::getKind() const { + return GameObjectKind::CONSUMABLE; +} + +Point2D ConsumableObject::getVelocity() const { + return { 0.0f, 0.0f }; +} + +void ConsumableObject::update(sf::Time delta) { + if (getStatus() != GameObjectStatus::DESTROYED) { + setStatus(GameObjectStatus::NORMAL); + } +} + +void ConsumableObject::onCollision(const GameObject &object, const CollisionInfo &info) { + if (getStatus() == GameObjectStatus::DESTROYED || object.getKind() == GameObjectKind::CONSUMABLE) { + return; + } + if (info.collide) { + setStatus(GameObjectStatus::DESTROYED); + return; + } + if (info.distance < CONSUMABLE_WARNED_MULTIPLIER * getCircle().radius) { + setStatus(GameObjectStatus::WARNED); + return; + } +} + +const sf::Texture* ConsumableObject::getTexture(TextureManager& textureManager) const { + switch (getStatus()) { + case GameObjectStatus::NORMAL: + return textureManager.getTexture(GameTextureID::STAR); + case GameObjectStatus::WARNED: + return textureManager.getTexture(GameTextureID::STAR_CONCERNED); + case GameObjectStatus::DESTROYED: + return nullptr; + } +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/direction.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/direction.cpp new file mode 100644 index 0000000..0a2b606 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/direction.cpp @@ -0,0 +1,16 @@ +#include "direction.hpp" + +Point2D getDirection(Direction direction) { + switch (direction) { + case North: + return { 0.0f, -1.0f }; + case East: + return { 1.0f, 0.0f }; + case South: + return { 0.0f, 1.0f }; + case West: + return { -1.0f, 0.0f }; + default: + return { 0.0f, 0.0f }; + } +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/dynscene.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/dynscene.cpp new file mode 100644 index 0000000..1dbcf2e --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/dynscene.cpp @@ -0,0 +1,144 @@ +#include "dynscene.hpp" + +#include "constants.hpp" +#include "player.hpp" +#include "consumable.hpp" +#include "enemy.hpp" +#include "utils.hpp" + +const int MAX_DYNAMIC_OBJECTS_ON_SCENE = 10; +const int MAX_ENEMY_OBJECTS_ON_SCENE = 4; + +const int NEW_DYNAMIC_OBJECT_PROB = 1; +const int NEW_ENEMY_OBJECT_PROB = 10; + +GameplayDynamicScene::GameplayDynamicScene() : Scene(SCENE_WIDTH, SCENE_HEIGHT) {} + +void GameplayDynamicScene::activate() { + addNewGameObject(GameObjectKind::PLAYER); +} + +void GameplayDynamicScene::deactivate() { + // remove all the objects from the list + objects.remove([&] (const GameObject& object) { + return true; + }); +} + +SceneID GameplayDynamicScene::getID() const { + return SceneID::DYNAMIC_GAME_FIELD; +} + +SceneID GameplayDynamicScene::getNextSceneID() const { + return SceneID::DYNAMIC_GAME_FIELD; +} + +void GameplayDynamicScene::processEvent(const sf::Event& event) { + return; +} + +void GameplayDynamicScene::update(sf::Time delta) { + // update the objects' state + objects.foreach([delta] (GameObject& object) { + object.update(delta); + }); + // move objects according to their velocity and elapsed time + objects.foreach([this, delta] (GameObject& object) { + move(object, delta); + }); + // compute collision information for each pair of objects + objects.foreach([this] (GameObject& object) { + objects.foreach([this, &object] (GameObject& other) { + if (&object != &other) { + detectCollision(object, other); + } + }); + }); + // update the list of objects + updateObjectsList(); +} + +void GameplayDynamicScene::updateObjectsList() { + // remove destroyed objects from the list + objects.remove([] (const GameObject& object) { + return (object.getStatus() == GameObjectStatus::DESTROYED) + && (object.getKind() != GameObjectKind::PLAYER); + }); + // count the number of the different kinds of objects present on the scene + int consumableCount = 0; + int enemyCount = 0; + objects.foreach([&] (const GameObject& object) { + switch (object.getKind()) { + case GameObjectKind::CONSUMABLE: + ++consumableCount; + break; + case GameObjectKind::ENEMY: + ++enemyCount; + break; + default: + break; + } + }); + // add new objects of randomly chosen kind if there is enough room for them on the scene + int dynamicObjectsCount = consumableCount + enemyCount; + if (dynamicObjectsCount < MAX_DYNAMIC_OBJECTS_ON_SCENE) { + int r = rand() % 100; + int k = rand() % 100; + if (r < NEW_DYNAMIC_OBJECT_PROB) { + if (k < NEW_ENEMY_OBJECT_PROB && enemyCount < MAX_ENEMY_OBJECTS_ON_SCENE) { + addNewGameObject(GameObjectKind::ENEMY); + } else if (k > NEW_ENEMY_OBJECT_PROB) { + addNewGameObject(GameObjectKind::CONSUMABLE); + } + } + } +} + +std::shared_ptr GameplayDynamicScene::addNewGameObject(GameObjectKind kind) { + std::shared_ptr object; + while (!object) { + // create an object with default position + switch (kind) { + case GameObjectKind::PLAYER: { + object = std::make_shared(); + break; + } + case GameObjectKind::CONSUMABLE: { + object = std::make_shared(); + break; + } + case GameObjectKind::ENEMY: { + object = std::make_shared(); + break; + } + } + // set random position for consumable and enemy objects + if (kind == GameObjectKind::CONSUMABLE || + kind == GameObjectKind::ENEMY) { + setObjectPosition(*object, generatePoint(getBoundingBox())); + } else { + fitInto(*object); + } + // check that object does not collide with existing objects + bool collide = false; + objects.foreach([&collide, &object](GameObject& other) { + collide |= collisionInfo(*object, other).collide; + }); + // reset a colliding object + if (collide) { + object = nullptr; + } + } + objects.insert(object); + return object; +} + +void GameplayDynamicScene::draw(sf::RenderWindow &window, TextureManager& textureManager) { + // draw background + drawBackground(window, textureManager.getTexture(GameTextureID::SPACE)); + // draw all objects on the scene + objects.foreach([&] (const GameObject& object) { + object.draw(window, textureManager); + }); +} + diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/enemy.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/enemy.cpp new file mode 100644 index 0000000..ce5ba50 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/enemy.cpp @@ -0,0 +1,48 @@ +#include "enemy.hpp" + +#include "constants.hpp" +#include "operators.hpp" +#include "utils.hpp" + +EnemyObject::EnemyObject() + : CircleGameObject({ { ENEMY_START_X, ENEMY_START_Y }, ENEMY_RADIUS }) +{} + +GameObjectKind EnemyObject::getKind() const { + return GameObjectKind::ENEMY; +} + +Point2D EnemyObject::getVelocity() const { + return velocity; +} + +void EnemyObject::update(sf::Time delta) { + if (CircleGameObject::getStatus() == GameObjectStatus::DESTROYED) + return; + updateTimer += delta; + if (updateTimer < sf::seconds(1.0f)) + return; + updateTimer = sf::milliseconds(0.0f); + updateVelocity(); +} + +void EnemyObject::setVelocity(Point2D velocity) { + this->velocity = velocity; +} + +void EnemyObject::updateVelocity() { + Direction direction1 = static_cast(generateInt(0, 3)); + Direction direction2 = static_cast(generateInt(0, 3)); + Point2D directionVector = (direction1 == direction2) + ? getDirection(direction1) + : (getDirection(direction1) + getDirection(direction2)); + velocity = SPEED * directionVector; +} + +void EnemyObject::onCollision(const GameObject &object, const CollisionInfo &info) { + return; +} + +const sf::Texture* EnemyObject::getTexture(TextureManager& textureManager) const { + return textureManager.getTexture(GameTextureID::BLACKHOLE); +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/engine.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/engine.cpp new file mode 100644 index 0000000..32ca01d --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/engine.cpp @@ -0,0 +1,89 @@ +#include "engine.hpp" + +#include "constants.hpp" + +GameEngine *GameEngine::create() { + static GameEngine engine; + return &engine; +} + +GameEngine::GameEngine() + : scene(nullptr) + , active(true) +{ + // initialize random number generator + srand(time(nullptr)); + // initialize resource managers + active &= sceneManager.initialize(); + active &= textureManager.initialize(); + if (!active) { + return; + } + // set the current scene + scene = sceneManager.getCurrentScene(); + // initialize the application window + window.create(sf::VideoMode(WINDOW_WIDTH, WINDOW_HEIGHT), "Space Game"); + window.setFramerateLimit(60); +} + +void GameEngine::run() { + sf::Clock clock; + while (isActive()) { + sf::Time delta = clock.restart(); + processInput(); + update(delta); + render(); + sceneTransition(); + } +} + +bool GameEngine::isActive() const { + return active && window.isOpen(); +} + +void GameEngine::close() { + active = false; + window.close(); +} + +void GameEngine::processInput() { + sf::Event event; + while (window.pollEvent(event)) { + processEvent(event); + } +} + +void GameEngine::processEvent(const sf::Event &event) { + switch (event.type) { + case sf::Event::Closed: + close(); + break; + default: + scene->processEvent(event); + break; + } +} + +void GameEngine::update(sf::Time delta) { + scene->update(delta); +} + +void GameEngine::render() { + window.clear(sf::Color::White); + scene->draw(window, textureManager); + window.display(); +} + +void GameEngine::sceneTransition() { + if (scene->getNextSceneID() != scene->getID()) { + sceneManager.transitionScene(scene->getNextSceneID()); + scene = sceneManager.getCurrentScene(); + resizeWindow(); + } +} + +void GameEngine::resizeWindow() { + Rectangle sceneBox = scene->getBoundingBox(); + sf::Vector2u sceneSize = sf::Vector2u(width(sceneBox), height(sceneBox)); + window.setSize(sceneSize); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/gobject.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/gobject.cpp new file mode 100644 index 0000000..a673ef5 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/gobject.cpp @@ -0,0 +1,7 @@ +#include "gobject.hpp" + +#include "operators.hpp" + +void GameObject::move(Point2D vector) { + setPosition(getPosition() + vector); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/gobjectlist.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/gobjectlist.cpp new file mode 100644 index 0000000..5b8b35e --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/gobjectlist.cpp @@ -0,0 +1,54 @@ +#include "gobjectlist.hpp" + +void GameObjectList::link(GameObjectList::Node *cursor, std::unique_ptr &&node) { + // TODO: write your solution here +} + +void GameObjectList::unlink(GameObjectList::Node *node) { + // TODO: write your solution here +} + +GameObjectList::GameObjectList() { + // TODO: write your solution here +} + +void GameObjectList::insert(const std::shared_ptr &object) { + // TODO: write your solution here +} + +void GameObjectList::remove(const std::function &pred) { + GameObjectList::Node* curr = head->next.get(); + while (curr != tail) { + Node* next = curr->next.get(); + if (pred(*curr->object)) { + unlink(curr); + } + curr = next; + } +} + +void GameObjectList::foreach(const std::function& apply) { + Node* curr = head->next.get(); + while (curr != tail) { + apply(*curr->object); + curr = curr->next.get(); + } +} + +GameObjectList::GameObjectList(const GameObjectList &other) : GameObjectList() { + // TODO: write your solution here +} + +GameObjectList::GameObjectList(GameObjectList &&other) noexcept : GameObjectList() { + // TODO: write your solution here +} + +GameObjectList &GameObjectList::operator=(GameObjectList other) { + // TODO: write your solution here + return *this; +} + +void swap(GameObjectList& first, GameObjectList& second) { + using std::swap; + // TODO: write your solution here +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/main.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/main.cpp new file mode 100644 index 0000000..b192818 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/main.cpp @@ -0,0 +1,13 @@ +#include "engine.hpp" + +#include + +int main() { + GameEngine* engine = GameEngine::create(); + if (!engine) { + std::cout << "Game engine is not created!\n"; + return 1; + } + GameEngine::create()->run(); + return 0; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/operators.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/operators.cpp new file mode 100644 index 0000000..4dc9097 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/operators.cpp @@ -0,0 +1,42 @@ +#include "game.hpp" + +Point2D operator+(Point2D a, Point2D b) { + return add(a, b); +} + +Point2D operator-(Point2D a) { + return { -a.x, -a.y }; +} + +Point2D operator-(Point2D a, Point2D b) { + return add(a, -b); +} + +Point2D operator*(float s, Point2D a) { + return mul(s, a); +} + +Circle operator+(Circle c, Point2D v) { + return { c.center + v, c.radius }; +} + +Circle operator-(Circle c, Point2D v) { + return { c.center - v, c.radius }; +} + +Rectangle operator+(Rectangle r, Point2D v) { + return { r.topLeft + v, r.botRight + v }; +} + +Rectangle operator-(Rectangle r, Point2D v) { + return { r.topLeft - v, r.botRight - v }; +} + +Circle operator*(float s, Circle c) { + return { c.center, s * c.radius }; +} + +Rectangle operator*(float s, Rectangle r) { + Point2D v = { width(r), height(r) }; + return { r.topLeft, r.topLeft + s * v }; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/player.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/player.cpp new file mode 100644 index 0000000..2908e92 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/player.cpp @@ -0,0 +1,56 @@ +#include "player.hpp" + +#include "constants.hpp" +#include "direction.hpp" +#include "operators.hpp" + +PlayerObject::PlayerObject() + : CircleGameObject({ { PLAYER_START_X, PLAYER_START_Y }, RADIUS }) +{} + +GameObjectKind PlayerObject::getKind() const { + return GameObjectKind::PLAYER; +} + +Point2D PlayerObject::getVelocity() const { + Point2D velocity = { 0.0f, 0.0f }; + if (CircleGameObject::getStatus() == GameObjectStatus::DESTROYED) + return velocity; + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) { + velocity = velocity + getDirection(North); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) { + velocity = velocity + getDirection(East); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) { + velocity = velocity + getDirection(South); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) { + velocity = velocity + getDirection(West); + } + velocity = SPEED * velocity; + return velocity; +} + +void PlayerObject::update(sf::Time delta) { + return; +} + +const sf::Texture* PlayerObject::getTexture(TextureManager& textureManager) const { + switch (getStatus()) { + case GameObjectStatus::NORMAL: + return textureManager.getTexture(GameTextureID::PLANET); + case GameObjectStatus::WARNED: + return textureManager.getTexture(GameTextureID::PLANET); + case GameObjectStatus::DESTROYED: + return textureManager.getTexture(GameTextureID::PLANET_DEAD); + default: + return nullptr; + } +} + +void PlayerObject::onCollision(const GameObject &object, const CollisionInfo &info) { + if (info.collide && object.getKind() == GameObjectKind::ENEMY) { + setStatus(GameObjectStatus::DESTROYED); + } +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/point.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/point.cpp new file mode 100644 index 0000000..5c74f69 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/point.cpp @@ -0,0 +1,19 @@ +#include "game.hpp" + +Point2D add(Point2D a, Point2D b) { + Point2D c = { 0, 0 }; + c.x = a.x + b.x; + c.y = a.y + b.y; + return c; +} + +Point2D mul(float s, Point2D a) { + Point2D b = { 0, 0 }; + b.x = s * a.x; + b.y = s * a.y; + return b; +} + +Point2D move(Point2D position, Point2D velocity, float delta) { + return add(position, mul(delta, velocity)); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/rectangle.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/rectangle.cpp new file mode 100644 index 0000000..2b5a1ec --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/rectangle.cpp @@ -0,0 +1,35 @@ +#include "rectangle.hpp" + +#include "operators.hpp" + +Point2D center(const Rectangle& rect) { + // TODO: explain this C++ initialization syntax + return rect.topLeft + 0.5f * Point2D { width(rect), height(rect) }; +} + +Rectangle createRectangle(Point2D p1, Point2D p2) { + Rectangle rect; + rect.topLeft = { std::min(p1.x, p2.x), std::min(p1.y, p2.y) }; + rect.botRight = { std::max(p1.x, p2.x), std::max(p1.y, p2.y) }; + return rect; +} + +Rectangle fitInto(const Rectangle& rect, const Rectangle& intoRect) { + if (width(rect) > width(intoRect) || height(rect) > height(intoRect)) { + return rect; + } + Point2D vector = { 0.0f, 0.0f }; + if (rect.topLeft.x < intoRect.topLeft.x) { + vector.x += intoRect.topLeft.x - rect.topLeft.x; + } + if (rect.topLeft.y < intoRect.topLeft.y) { + vector.y += intoRect.topLeft.y - rect.topLeft.y; + } + if (rect.botRight.x > intoRect.botRight.x) { + vector.x += intoRect.botRight.x - rect.botRight.x; + } + if (rect.botRight.y > intoRect.botRight.y) { + vector.y += intoRect.botRight.y - rect.botRight.y; + } + return rect + vector; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/scene.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/scene.cpp new file mode 100644 index 0000000..1457361 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/scene.cpp @@ -0,0 +1,47 @@ +#include "scene.hpp" + +#include "operators.hpp" + +Scene::Scene(float width, float height) + : width(width) + , height(height) +{} + +Rectangle Scene::getBoundingBox() const { + Rectangle box; + box.topLeft = { 0.0f, 0.0f }; + box.botRight = { width, height }; + return box; +} + +void Scene::setObjectPosition(GameObject& object, Point2D position) { + object.setPosition(position); + fitInto(object); +} + +void Scene::move(GameObject &object, Point2D vector) { + object.move(vector); + fitInto(object); +} + +void Scene::move(GameObject& object, sf::Time delta) { + move(object, 0.001f * delta.asMilliseconds() * object.getVelocity()); +} + +void Scene::fitInto(GameObject &object) { + Rectangle rect = ::fitInto(object.getBoundingBox(), getBoundingBox()); + object.setPosition(center(rect)); +} + +void Scene::detectCollision(GameObject& object1, GameObject& object2) { + CollisionInfo info = collisionInfo(object1, object2); + object1.onCollision(object2, info); + object2.onCollision(object1, info); +} + +void Scene::drawBackground(sf::RenderWindow &window, const sf::Texture* texture) const { + sf::Sprite background; + background.setTexture(*texture); + background.setTextureRect(sf::IntRect(0, 0, width, height)); + window.draw(background); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/scenes.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/scenes.cpp new file mode 100644 index 0000000..7125bb8 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/scenes.cpp @@ -0,0 +1,14 @@ +#include "scenes.hpp" + +bool SceneManager::initialize() { + staticScene.activate(); + return true; +} + +Scene* SceneManager::getCurrentScene() { + return &staticScene; +} + +void SceneManager::transitionScene(SceneID id) { + return; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/statscene.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/statscene.cpp new file mode 100644 index 0000000..a6abdfa --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/statscene.cpp @@ -0,0 +1,46 @@ +#include "statscene.hpp" + +#include "constants.hpp" + +GameplayStaticScene::GameplayStaticScene() : Scene(SCENE_WIDTH, SCENE_HEIGHT) {} + +void GameplayStaticScene::activate() { + fitInto(player); + fitInto(consumable); + fitInto(enemy); +} + +void GameplayStaticScene::deactivate() { + return; +} + +SceneID GameplayStaticScene::getID() const { + return SceneID::STATIC_GAME_FIELD; +} + +SceneID GameplayStaticScene::getNextSceneID() const { + return SceneID::STATIC_GAME_FIELD; +} + +void GameplayStaticScene::processEvent(const sf::Event& event) { + return; +} + +void GameplayStaticScene::update(sf::Time delta) { + player.update(delta); + consumable.update(delta); + enemy.update(delta); + move(player, delta); + move(consumable, delta); + move(enemy, delta); + detectCollision(player, consumable); + detectCollision(enemy, player); + detectCollision(enemy, consumable); +} + +void GameplayStaticScene::draw(sf::RenderWindow &window, TextureManager& textureManager) { + drawBackground(window, textureManager.getTexture(GameTextureID::SPACE)); + player.draw(window, textureManager); + consumable.draw(window, textureManager); + enemy.draw(window, textureManager); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/textures.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/textures.cpp new file mode 100644 index 0000000..2b5abcc --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/textures.cpp @@ -0,0 +1,42 @@ +#include "textures.hpp" + +#include + +const char* getTextureFilename(GameTextureID id) { + switch (id) { + case GameTextureID::SPACE: + return "resources/space.png"; + case GameTextureID::PLANET: + return "resources/planet.png"; + case GameTextureID::PLANET_DEAD: + return "resources/planetDead.png"; + case GameTextureID::STAR: + return "resources/star.png"; + case GameTextureID::STAR_CONCERNED: + return "resources/starConcerned.png"; + case GameTextureID::BLACKHOLE: + return "resources/blackhole.png"; + default: + return ""; + } +} + +bool TextureManager::initialize() { + for (size_t i = 0; i < SIZE; ++i) { + GameTextureID id = static_cast(i); + const char* filename = getTextureFilename(id); + sf::Texture* texture = &textures[i]; + if (!texture->loadFromFile(filename)) { + std::cerr << "Could not open file " << filename << "\n"; + return false; + } + if (id == GameTextureID::SPACE) { + texture->setRepeated(true); + } + } + return true; +} + +const sf::Texture* TextureManager::getTexture(GameTextureID id) const { + return &textures[static_cast(id)]; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/utils.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/utils.cpp new file mode 100644 index 0000000..783fc7a --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/src/utils.cpp @@ -0,0 +1,51 @@ +#include "utils.hpp" + +#include +#include +#include + +float distance(Point2D a, Point2D b) { + float dx = a.x - b.x; + float dy = a.y - b.y; + return sqrt(dx * dx + dy * dy); +} + +float generateFloat(float min, float max) { + return min + (rand() / (RAND_MAX / (max - min))); +} + +int generateInt(int min, int max) { + return min + rand() % (max - min + 1); +} + +bool generateBool(float prob) { + return generateFloat(0.0f, 1.0f) < prob; +} + +Point2D generatePoint(const Rectangle& boundingBox) { + Point2D point = { 0.0f, 0.0f, }; + point.x = generateFloat(boundingBox.topLeft.x, boundingBox.botRight.x); + point.y = generateFloat(boundingBox.topLeft.y, boundingBox.botRight.y); + return point; +} + +Circle generateCircle(float radius, const Rectangle& boundingBox) { + Circle circle = { { 0.0f, 0.0f }, 0.0f }; + if (radius > std::min(width(boundingBox), height(boundingBox))) { + return circle; + } + circle.center.x = generateFloat( + boundingBox.topLeft.x + radius, + boundingBox.botRight.x - radius + ); + circle.center.y = generateFloat( + boundingBox.topLeft.x + radius, + boundingBox.botRight.x - radius + ); + circle.radius = radius; + return circle; +} + +Rectangle generateRectangle(const Rectangle& boundingBox) { + return createRectangle(generatePoint(boundingBox), generatePoint(boundingBox)); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/task-info.yaml b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/task-info.yaml new file mode 100644 index 0000000..49bd412 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/task-info.yaml @@ -0,0 +1,46 @@ +type: edu +custom_name: New Challenge +files: +- name: src/enemy.cpp + visible: true +- name: src/engine.cpp + visible: true +- name: src/scenes.cpp + visible: true +- name: src/textures.cpp + visible: true +- name: src/scene.cpp + visible: true +- name: src/statscene.cpp + visible: true +- name: src/dynscene.cpp + visible: true +- name: src/gobject.cpp + visible: true +- name: src/gobjectlist.cpp + visible: true +- name: src/cgobject.cpp + visible: true +- name: src/player.cpp + visible: true +- name: src/consumable.cpp + visible: true +- name: src/collision.cpp + visible: true +- name: src/direction.cpp + visible: true +- name: src/rectangle.cpp + visible: true +- name: src/point.cpp + visible: true +- name: src/operators.cpp + visible: true +- name: src/utils.cpp + visible: true +- name: CMakeLists.txt + visible: false +- name: src/main.cpp + visible: true + editable: false +- name: test/test.cpp + visible: false diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/task-remote-info.yaml b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/task-remote-info.yaml new file mode 100644 index 0000000..5060dbb --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/task-remote-info.yaml @@ -0,0 +1 @@ +id: 1572869330 diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/task.md b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/task.md new file mode 100644 index 0000000..100a954 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/task.md @@ -0,0 +1,89 @@ +Now let us apply the newly acquired knowledge +to extend the mechanics of our game by adding a new kind of game objects — +enemy objects visualized as black holes. +As you will see, with the help of object-oriented programming tricks, +this task can be accomplished quite easily. + +The enemy object should behave as follows: +- it should move on a scene in a random direction, changing it periodically; +- it should consume star objects upon collision; +- star objects should change their status to `WARNED` when + the enemy object is approaching them + (similar to how they behave when the player object approaches); +- when the enemy object collides with the player object, the latter + should change its status to `DESTROYED`, becoming effectively immovable. + +Let us proceed to the implementation of this behavior. +First, take a look at the declaration of the `EnemyObject` class. +As it inherits from the `CircleGameObject` class, +a corresponding circular shape and behavior are already attached to it. +Moreover, the `getKind()` method of the `EnemyObject` class +distinguishes enemy objects from other kinds of objects (player and consumable) +by returning the `GameObjectKind::ENEMY` value. + +We have already partly implemented the movement behavior of the `EnemyObject` for you. +To do so, we have added two new fields to the objects of this class: +- the `velocity` field is a vector storing the direction and speed of the object's current velocity; +- the `updateTimer` field stores the time elapsed since the last update of the object's velocity. + +The `getVelocity()` method simply returns the value of the `velocity` field. +The `update(sf::Time delta)` method is responsible for periodically updating the object's velocity. +It takes as an argument the amount of time elapsed since the last update. +The implementation simply checks if the overall amount of elapsed time +is greater than the predefined time period (1 second), +and if so, it resets the velocity to a new randomly generated one +by calling the `updateVelocity()` method. +Your task is to implement this method. + +The method should generate a random direction vector, multiply it by the speed scalar constant, +and assign the result to the `velocity` field. +That is, the following formula should be used: +``` +v` = S * d +``` +where: +* `` v` `` is the new velocity vector, +* `S` is the speed constant `SPEED`, +* `d` is a direction vector. + +You have the freedom to define a direction vector as you like, +but it should satisfy the following constraints: +* the `x` and `y` coordinates should either be equal to `1`, `0`, or `-1`, and +* the vector should be randomly generated: several consecutive invocations of a method + should not likely result in the same direction being generated. + +To generate a random direction vector, you might find the following functions useful: + +```c++ +bool generateBool(float prob = 0.5f); +int generateInt(int min, int max); +float generateFloat(float min, float max); +Point2D generatePoint(const Rectangle& boundingBox); +``` + +A detailed description of these functions can be found in the documentation +alongside their definitions in the `utils.hpp` file. + +Moving forward with the `Enemy` class, +the easy part is to implement the `getTexture` method. +It should return a new special texture for the enemy objects. +This texture has a corresponding id — `GameTextureID::BLACKHOLE`. + +The harder part is to implement the collision behavior. +When an enemy object collides with the player object, the player object should become inactive. +This can be achieved by setting the status of the player object to `DESTROYED`. +However, an enemy object does not have direct access to the `setStatus` method of the player object. +To implement the desired behavior, you actually need to +modify the `onCollision` method of the `PlayerObject` class, not the `EnemyObject` class! + +
+ +In the implementation of the `PlayerObject::onCollision` method, +remember to check that the collision occurred with an enemy object, +not an object of some other kind! + +
+ +Notice that from the perspective of the consumable objects, +both the player and the enemy objects behave similarly, +so you do not need to modify their behavior. \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/test/test.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/test/test.cpp new file mode 100644 index 0000000..149906c --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewChallenge/test/test.cpp @@ -0,0 +1,188 @@ +#include + +#include "enemy.hpp" +#include "operators.hpp" + +#include "testscene.hpp" +#include "testing.hpp" + +testing::Environment* const env = + testing::AddGlobalTestEnvironment(new TestEnvironment); + +const float MIN = -10e3; +const float MAX = 10e3; + +const Rectangle generateArea = { { MIN, MIN }, { MAX, MAX } }; + +std::string updateVelocity_error_msg(GameObject* object, Point2D velocity) { + std::ostringstream stream; + stream << "Testing expression:\n" + << " object.updateVelocity()" << "\n"; + stream << "Test data:" << "\n" + << " object.getKind() = " << object->getKind() << "\n" + << " object.getStatus() = " << object->getStatus() << "\n" + << " object.getVelocity() = " << object->getVelocity() << "\n"; + stream << "Result:\n" + << " object.getVelocity() = " << velocity << "\n"; + return stream.str(); +} + +TEST(EnemyObjectTest, UpdateVelocityChangedTest) { + TestEnemyObject object = TestEnemyObject(); + // set some initial velocity, which does not satisfy + // the constraints specified in the task description; + // thus the call to `updateVelocity` should change it to some other value + Point2D initialVelocity = { -5.0f, -5.0f }; + object.performSetStatus(GameObjectStatus::NORMAL); + object.performSetVelocity(initialVelocity); + + TestEnemyObject actual = object; + actual.performUpdateVelocity(); + + Point2D velocity = actual.getVelocity(); + ASSERT_FALSE(velocity.x == initialVelocity.x && velocity.y == initialVelocity.y) + << updateVelocity_error_msg(&object, actual.getVelocity()) + << "The velocity has not changed!"; +} + +TEST(EnemyObjectTest, UpdateVelocityInBoundsTest) { + property_test( + [] () { + TestEnemyObject object = TestEnemyObject(); + object.performSetStatus(GameObjectStatus::NORMAL); + object.performSetVelocity(Point2D { 0.0f, 0.0f }); + return std::make_tuple(object); + }, + [] (std::tuple data) { + TestEnemyObject object; + std::tie(object) = data; + TestEnemyObject actual = object; + actual.performUpdateVelocity(); + Point2D velocity = actual.getVelocity(); + ASSERT_TRUE(abs(velocity.x) < EPS || abs(abs(velocity.x) - SPEED) < EPS) + << updateVelocity_error_msg(&object, actual.getVelocity()) + << "The speed of velocity along x-axis is invalid!"; + ASSERT_TRUE(abs(velocity.y) < EPS || abs(abs(velocity.y) - SPEED) < EPS) + << updateVelocity_error_msg(&object, actual.getVelocity()) + << "The speed of velocity along y-axis is invalid!"; + } + ); +} + +TEST(EnemyObjectTest, UpdateVelocityRandomnessTest) { + randomness_test( + [] () { + TestEnemyObject object = TestEnemyObject(); + object.performSetStatus(GameObjectStatus::NORMAL); + object.performSetVelocity(Point2D { 0.0f, 0.0f }); + object.performUpdateVelocity(); + return object; + }, + [] (const TestEnemyObject& object1, const TestEnemyObject& object2) { + Point2D velocity1 = object1.getVelocity(); + Point2D velocity2 = object2.getVelocity(); + return (abs(velocity1.x - velocity2.x) < EPS) && (abs(velocity1.y - velocity2.y) < EPS); + }, + [] (const TestEnemyObject& object) { + std::stringstream stream; + stream << "object.getVelocity() = " << object.getVelocity(); + return stream.str(); + } + ); +} + +TEST(EnemyObjectTest, GetTextureTest) { + TextureManager textureManager = TextureManager(); + bool init = textureManager.initialize(); + ASSERT_TRUE(init) << "Internal error: failed to initialize texture manager"; + + TestEnemyObject object = TestEnemyObject(); + + object.performSetStatus(GameObjectStatus::NORMAL); + const sf::Texture* normalTexture = object.getTexture(textureManager); + ASSERT_EQ(textureManager.getTexture(GameTextureID::BLACKHOLE), normalTexture) + << "Texture of enemy object in GameObjectStatus::NORMAL status is not equal to GameTextureID::BLACKHOLE"; +} + +std::string onCollision_error_msg(GameObject* object, GameObject* other, const CollisionInfo& info, + GameObjectStatus expected, GameObjectStatus actual) { + std::ostringstream stream; + stream << "Testing expression:\n" + << " object.onCollision(other, info)" << "\n"; + stream << "Test data:" << "\n" + << " object.getKind() = " << object->getKind() << "\n" + << " object.getStatus() = " << object->getStatus() << "\n" + << " other.getKind() = " << other->getKind() << "\n" + << " other.getStatus() = " << other->getStatus() << "\n" + << " info = " << info << "\n"; + stream << "Expected result:\n" + << " object.getStatus() = " << expected << "\n"; + stream << "Actual result:\n" + << " object.getStatus() = " << actual << "\n"; + return stream.str(); +} + +TEST(EnemyObjectTest, OnCollisionPlayerDestroyedTest) { + TestEnemyObject enemy = TestEnemyObject(); + PlayerObject player = PlayerObject(); + + CollisionInfo info = CollisionInfo { true, 0.0f }; + PlayerObject actual = player; + actual.onCollision(enemy, info); + + ASSERT_EQ(GameObjectStatus::DESTROYED, actual.getStatus()) + << onCollision_error_msg(&player, &enemy, info, GameObjectStatus::DESTROYED, actual.getStatus()) + << "The status of player object after a collision with enemy object should become GameObjectStatus::DESTROYED"; +} + +TEST(EnemyObjectTest, OnCollisionPlayerNoCollisionTest) { + TestEnemyObject enemy = TestEnemyObject(); + PlayerObject player = PlayerObject(); + + CollisionInfo info = CollisionInfo { false, player.getCircle().radius + enemy.getCircle().radius + 10.0f }; + PlayerObject actual = player; + actual.onCollision(enemy, info); + + ASSERT_EQ(GameObjectStatus::NORMAL, actual.getStatus()) + << onCollision_error_msg(&player, &enemy, info, GameObjectStatus::NORMAL, actual.getStatus()) + << "The status of player object after should not change in case when enemy approaches it, but does not collide"; +} + +TEST(EnemyObjectTest, OnCollisionConsumableDestroyedTest) { + TestEnemyObject enemy = TestEnemyObject(); + ConsumableObject consumable = ConsumableObject(); + + CollisionInfo info = CollisionInfo { true, 0.0f }; + ConsumableObject actual = consumable; + actual.onCollision(enemy, info); + + ASSERT_EQ(GameObjectStatus::DESTROYED, actual.getStatus()) + << onCollision_error_msg(&consumable, &enemy, info, GameObjectStatus::DESTROYED, actual.getStatus()) + << "The status of consumable object after a collision with enemy object should become GameObjectStatus::DESTROYED"; +} + +TEST(EnemyObjectTest, OnCollisionConsumableWarnedTest) { + TestEnemyObject enemy = TestEnemyObject(); + ConsumableObject consumable = ConsumableObject(); + + CollisionInfo info = CollisionInfo { false, consumable.getCircle().radius + enemy.getCircle().radius + 10.0f }; + ConsumableObject actual = consumable; + actual.onCollision(enemy, info); + + ASSERT_EQ(GameObjectStatus::WARNED, actual.getStatus()) + << onCollision_error_msg(&consumable, &enemy, info, GameObjectStatus::WARNED, actual.getStatus()) + << "The status of consumable object after a enemy object approached it should become GameObjectStatus::WARNED"; +} + +TEST(EnemyObjectTest, OnCollisionConsumableNormalTest) { + TestEnemyObject enemy = TestEnemyObject(); + ConsumableObject consumable = ConsumableObject(); + + CollisionInfo info = CollisionInfo { false, 2 * CONSUMABLE_WARNED_MULTIPLIER * consumable.getCircle().radius }; + ConsumableObject actual = consumable; + actual.onCollision(enemy, info); + + ASSERT_EQ(GameObjectStatus::NORMAL, actual.getStatus()) + << onCollision_error_msg(&consumable, &enemy, info, GameObjectStatus::NORMAL, actual.getStatus()) + << "The status of consumable object should not have changed, because the enemy object is too far away from it"; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/CMakeLists.txt b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/CMakeLists.txt new file mode 100644 index 0000000..a404d81 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.25) + +project(ObjectOrientedProgramming-ClassesAndObjects-NewDynamics) + +set(SRC + src/main.cpp + src/engine.cpp src/scenes.cpp src/textures.cpp + src/scene.cpp src/statscene.cpp src/dynscene.cpp + src/gobject.cpp src/gobjectlist.cpp src/cgobject.cpp + src/player.cpp src/consumable.cpp src/enemy.cpp + src/collision.cpp src/direction.cpp + src/rectangle.cpp src/point.cpp + src/operators.cpp src/utils.cpp +) + +set(TEST + test/test.cpp) + +add_executable(${PROJECT_NAME}-run ${SRC}) + +configure_test_target(${PROJECT_NAME}-test "${SRC}" ${TEST}) + +prepare_sfml_framework_lesson_task( + "${CMAKE_CURRENT_SOURCE_DIR}/.." + ${PROJECT_NAME}-run + ${PROJECT_NAME}-test +) diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/cgobject.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/cgobject.cpp new file mode 100644 index 0000000..1d49ba3 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/cgobject.cpp @@ -0,0 +1,47 @@ +#include "cgobject.hpp" + +#include "operators.hpp" + +CircleGameObject::CircleGameObject(Circle circle) + : circle(circle) + , status(GameObjectStatus::NORMAL) +{} + +Point2D CircleGameObject::getPosition() const { + return circle.center; +} + +void CircleGameObject::setPosition(Point2D position) { + circle.center = position; +} + +GameObjectStatus CircleGameObject::getStatus() const { + return status; +} + +void CircleGameObject::setStatus(GameObjectStatus newStatus) { + status = newStatus; +} + +Circle CircleGameObject::getCircle() const { + return circle; +} + +Rectangle CircleGameObject::getBoundingBox() const { + Point2D offset = { circle.radius, circle.radius }; + Point2D p1 = circle.center - offset; + Point2D p2 = circle.center + offset; + return createRectangle(p1, p2); +} + +void CircleGameObject::draw(sf::RenderWindow &window, TextureManager& textureManager) const { + const sf::Texture* texture = getTexture(textureManager); + if (texture == nullptr) + return; + sf::CircleShape shape; + shape.setPosition(circle.center.x, circle.center.y); + shape.setOrigin(circle.radius, circle.radius); + shape.setRadius(circle.radius); + shape.setTexture(texture); + window.draw(shape); +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/collision.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/collision.cpp new file mode 100644 index 0000000..9b56990 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/collision.cpp @@ -0,0 +1,21 @@ +#include + +#include "collision.hpp" +#include "cgobject.hpp" +#include "utils.hpp" + +CollisionInfo collisionInfo(const Circle& circle1, const Circle& circle2) { + CollisionInfo info; + info.distance = distance(circle1.center, circle2.center); + info.collide = (info.distance < circle1.radius + circle2.radius); + return info; +} + +CollisionInfo collisionInfo(const GameObject& object1, const GameObject& object2) { + const CircleGameObject* circleObject1 = dynamic_cast(&object1); + const CircleGameObject* circleObject2 = dynamic_cast(&object2); + if (circleObject1 && circleObject2) { + return collisionInfo(circleObject1->getCircle(), circleObject2->getCircle()); + } + assert(false); +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/consumable.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/consumable.cpp new file mode 100644 index 0000000..d03f046 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/consumable.cpp @@ -0,0 +1,46 @@ +#include "consumable.hpp" + +#include "constants.hpp" + +ConsumableObject::ConsumableObject() + : CircleGameObject({ { CONSUMABLE_START_X, CONSUMABLE_START_Y }, CONSUMABLE_RADIUS }) +{} + +GameObjectKind ConsumableObject::getKind() const { + return GameObjectKind::CONSUMABLE; +} + +Point2D ConsumableObject::getVelocity() const { + return { 0.0f, 0.0f }; +} + +void ConsumableObject::update(sf::Time delta) { + if (getStatus() != GameObjectStatus::DESTROYED) { + setStatus(GameObjectStatus::NORMAL); + } +} + +void ConsumableObject::onCollision(const GameObject &object, const CollisionInfo &info) { + if (getStatus() == GameObjectStatus::DESTROYED || object.getKind() == GameObjectKind::CONSUMABLE) { + return; + } + if (info.collide) { + setStatus(GameObjectStatus::DESTROYED); + return; + } + if (info.distance < CONSUMABLE_WARNED_MULTIPLIER * getCircle().radius) { + setStatus(GameObjectStatus::WARNED); + return; + } +} + +const sf::Texture* ConsumableObject::getTexture(TextureManager& textureManager) const { + switch (getStatus()) { + case GameObjectStatus::NORMAL: + return textureManager.getTexture(GameTextureID::STAR); + case GameObjectStatus::WARNED: + return textureManager.getTexture(GameTextureID::STAR_CONCERNED); + case GameObjectStatus::DESTROYED: + return nullptr; + } +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/direction.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/direction.cpp new file mode 100644 index 0000000..0a2b606 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/direction.cpp @@ -0,0 +1,16 @@ +#include "direction.hpp" + +Point2D getDirection(Direction direction) { + switch (direction) { + case North: + return { 0.0f, -1.0f }; + case East: + return { 1.0f, 0.0f }; + case South: + return { 0.0f, 1.0f }; + case West: + return { -1.0f, 0.0f }; + default: + return { 0.0f, 0.0f }; + } +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/dynscene.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/dynscene.cpp new file mode 100644 index 0000000..1dbcf2e --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/dynscene.cpp @@ -0,0 +1,144 @@ +#include "dynscene.hpp" + +#include "constants.hpp" +#include "player.hpp" +#include "consumable.hpp" +#include "enemy.hpp" +#include "utils.hpp" + +const int MAX_DYNAMIC_OBJECTS_ON_SCENE = 10; +const int MAX_ENEMY_OBJECTS_ON_SCENE = 4; + +const int NEW_DYNAMIC_OBJECT_PROB = 1; +const int NEW_ENEMY_OBJECT_PROB = 10; + +GameplayDynamicScene::GameplayDynamicScene() : Scene(SCENE_WIDTH, SCENE_HEIGHT) {} + +void GameplayDynamicScene::activate() { + addNewGameObject(GameObjectKind::PLAYER); +} + +void GameplayDynamicScene::deactivate() { + // remove all the objects from the list + objects.remove([&] (const GameObject& object) { + return true; + }); +} + +SceneID GameplayDynamicScene::getID() const { + return SceneID::DYNAMIC_GAME_FIELD; +} + +SceneID GameplayDynamicScene::getNextSceneID() const { + return SceneID::DYNAMIC_GAME_FIELD; +} + +void GameplayDynamicScene::processEvent(const sf::Event& event) { + return; +} + +void GameplayDynamicScene::update(sf::Time delta) { + // update the objects' state + objects.foreach([delta] (GameObject& object) { + object.update(delta); + }); + // move objects according to their velocity and elapsed time + objects.foreach([this, delta] (GameObject& object) { + move(object, delta); + }); + // compute collision information for each pair of objects + objects.foreach([this] (GameObject& object) { + objects.foreach([this, &object] (GameObject& other) { + if (&object != &other) { + detectCollision(object, other); + } + }); + }); + // update the list of objects + updateObjectsList(); +} + +void GameplayDynamicScene::updateObjectsList() { + // remove destroyed objects from the list + objects.remove([] (const GameObject& object) { + return (object.getStatus() == GameObjectStatus::DESTROYED) + && (object.getKind() != GameObjectKind::PLAYER); + }); + // count the number of the different kinds of objects present on the scene + int consumableCount = 0; + int enemyCount = 0; + objects.foreach([&] (const GameObject& object) { + switch (object.getKind()) { + case GameObjectKind::CONSUMABLE: + ++consumableCount; + break; + case GameObjectKind::ENEMY: + ++enemyCount; + break; + default: + break; + } + }); + // add new objects of randomly chosen kind if there is enough room for them on the scene + int dynamicObjectsCount = consumableCount + enemyCount; + if (dynamicObjectsCount < MAX_DYNAMIC_OBJECTS_ON_SCENE) { + int r = rand() % 100; + int k = rand() % 100; + if (r < NEW_DYNAMIC_OBJECT_PROB) { + if (k < NEW_ENEMY_OBJECT_PROB && enemyCount < MAX_ENEMY_OBJECTS_ON_SCENE) { + addNewGameObject(GameObjectKind::ENEMY); + } else if (k > NEW_ENEMY_OBJECT_PROB) { + addNewGameObject(GameObjectKind::CONSUMABLE); + } + } + } +} + +std::shared_ptr GameplayDynamicScene::addNewGameObject(GameObjectKind kind) { + std::shared_ptr object; + while (!object) { + // create an object with default position + switch (kind) { + case GameObjectKind::PLAYER: { + object = std::make_shared(); + break; + } + case GameObjectKind::CONSUMABLE: { + object = std::make_shared(); + break; + } + case GameObjectKind::ENEMY: { + object = std::make_shared(); + break; + } + } + // set random position for consumable and enemy objects + if (kind == GameObjectKind::CONSUMABLE || + kind == GameObjectKind::ENEMY) { + setObjectPosition(*object, generatePoint(getBoundingBox())); + } else { + fitInto(*object); + } + // check that object does not collide with existing objects + bool collide = false; + objects.foreach([&collide, &object](GameObject& other) { + collide |= collisionInfo(*object, other).collide; + }); + // reset a colliding object + if (collide) { + object = nullptr; + } + } + objects.insert(object); + return object; +} + +void GameplayDynamicScene::draw(sf::RenderWindow &window, TextureManager& textureManager) { + // draw background + drawBackground(window, textureManager.getTexture(GameTextureID::SPACE)); + // draw all objects on the scene + objects.foreach([&] (const GameObject& object) { + object.draw(window, textureManager); + }); +} + diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/enemy.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/enemy.cpp new file mode 100644 index 0000000..ce5ba50 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/enemy.cpp @@ -0,0 +1,48 @@ +#include "enemy.hpp" + +#include "constants.hpp" +#include "operators.hpp" +#include "utils.hpp" + +EnemyObject::EnemyObject() + : CircleGameObject({ { ENEMY_START_X, ENEMY_START_Y }, ENEMY_RADIUS }) +{} + +GameObjectKind EnemyObject::getKind() const { + return GameObjectKind::ENEMY; +} + +Point2D EnemyObject::getVelocity() const { + return velocity; +} + +void EnemyObject::update(sf::Time delta) { + if (CircleGameObject::getStatus() == GameObjectStatus::DESTROYED) + return; + updateTimer += delta; + if (updateTimer < sf::seconds(1.0f)) + return; + updateTimer = sf::milliseconds(0.0f); + updateVelocity(); +} + +void EnemyObject::setVelocity(Point2D velocity) { + this->velocity = velocity; +} + +void EnemyObject::updateVelocity() { + Direction direction1 = static_cast(generateInt(0, 3)); + Direction direction2 = static_cast(generateInt(0, 3)); + Point2D directionVector = (direction1 == direction2) + ? getDirection(direction1) + : (getDirection(direction1) + getDirection(direction2)); + velocity = SPEED * directionVector; +} + +void EnemyObject::onCollision(const GameObject &object, const CollisionInfo &info) { + return; +} + +const sf::Texture* EnemyObject::getTexture(TextureManager& textureManager) const { + return textureManager.getTexture(GameTextureID::BLACKHOLE); +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/engine.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/engine.cpp new file mode 100644 index 0000000..32ca01d --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/engine.cpp @@ -0,0 +1,89 @@ +#include "engine.hpp" + +#include "constants.hpp" + +GameEngine *GameEngine::create() { + static GameEngine engine; + return &engine; +} + +GameEngine::GameEngine() + : scene(nullptr) + , active(true) +{ + // initialize random number generator + srand(time(nullptr)); + // initialize resource managers + active &= sceneManager.initialize(); + active &= textureManager.initialize(); + if (!active) { + return; + } + // set the current scene + scene = sceneManager.getCurrentScene(); + // initialize the application window + window.create(sf::VideoMode(WINDOW_WIDTH, WINDOW_HEIGHT), "Space Game"); + window.setFramerateLimit(60); +} + +void GameEngine::run() { + sf::Clock clock; + while (isActive()) { + sf::Time delta = clock.restart(); + processInput(); + update(delta); + render(); + sceneTransition(); + } +} + +bool GameEngine::isActive() const { + return active && window.isOpen(); +} + +void GameEngine::close() { + active = false; + window.close(); +} + +void GameEngine::processInput() { + sf::Event event; + while (window.pollEvent(event)) { + processEvent(event); + } +} + +void GameEngine::processEvent(const sf::Event &event) { + switch (event.type) { + case sf::Event::Closed: + close(); + break; + default: + scene->processEvent(event); + break; + } +} + +void GameEngine::update(sf::Time delta) { + scene->update(delta); +} + +void GameEngine::render() { + window.clear(sf::Color::White); + scene->draw(window, textureManager); + window.display(); +} + +void GameEngine::sceneTransition() { + if (scene->getNextSceneID() != scene->getID()) { + sceneManager.transitionScene(scene->getNextSceneID()); + scene = sceneManager.getCurrentScene(); + resizeWindow(); + } +} + +void GameEngine::resizeWindow() { + Rectangle sceneBox = scene->getBoundingBox(); + sf::Vector2u sceneSize = sf::Vector2u(width(sceneBox), height(sceneBox)); + window.setSize(sceneSize); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/gobject.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/gobject.cpp new file mode 100644 index 0000000..a673ef5 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/gobject.cpp @@ -0,0 +1,7 @@ +#include "gobject.hpp" + +#include "operators.hpp" + +void GameObject::move(Point2D vector) { + setPosition(getPosition() + vector); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/gobjectlist.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/gobjectlist.cpp new file mode 100644 index 0000000..d4f4541 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/gobjectlist.cpp @@ -0,0 +1,74 @@ +#include "gobjectlist.hpp" + +void GameObjectList::link(GameObjectList::Node *cursor, std::unique_ptr &&node) { + cursor->next->prev = node.get(); + node->next = std::move(cursor->next); + node->prev = cursor; + cursor->next = std::move(node); +} + +void GameObjectList::unlink(GameObjectList::Node *node) { + node->next->prev = node->prev; + node->prev->next = std::move(node->next); +} + +GameObjectList::GameObjectList() { + head = std::make_unique(); + head->next = std::make_unique(); + tail = head->next.get(); + tail->prev = head.get(); +} + +void GameObjectList::insert(const std::shared_ptr &object) { + if (!object) { + return; + } + std::unique_ptr node = std::make_unique(); + node->object = object; + link(tail->prev, std::move(node)); +} + +void GameObjectList::remove(const std::function &pred) { + GameObjectList::Node* curr = head->next.get(); + while (curr != tail) { + Node* next = curr->next.get(); + if (pred(*curr->object)) { + unlink(curr); + } + curr = next; + } +} + +void GameObjectList::foreach(const std::function& apply) { + Node* curr = head->next.get(); + while (curr != tail) { + apply(*curr->object); + curr = curr->next.get(); + } +} + +GameObjectList::GameObjectList(const GameObjectList &other) : GameObjectList() { + Node* cursor = head.get(); + Node* curr = other.head->next.get(); + while (curr != other.tail) { + link(cursor, std::make_unique()); + cursor = cursor->next.get(); + cursor->object = curr->object; + curr = curr->next.get(); + } +} + +GameObjectList::GameObjectList(GameObjectList &&other) noexcept : GameObjectList() { + swap(*this, other); +} + +GameObjectList &GameObjectList::operator=(GameObjectList other) { + swap(*this, other); + return *this; +} + +void swap(GameObjectList& first, GameObjectList& second) { + using std::swap; + swap(first.head, second.head); + swap(first.tail, second.tail); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/main.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/main.cpp new file mode 100644 index 0000000..b192818 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/main.cpp @@ -0,0 +1,13 @@ +#include "engine.hpp" + +#include + +int main() { + GameEngine* engine = GameEngine::create(); + if (!engine) { + std::cout << "Game engine is not created!\n"; + return 1; + } + GameEngine::create()->run(); + return 0; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/operators.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/operators.cpp new file mode 100644 index 0000000..4dc9097 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/operators.cpp @@ -0,0 +1,42 @@ +#include "game.hpp" + +Point2D operator+(Point2D a, Point2D b) { + return add(a, b); +} + +Point2D operator-(Point2D a) { + return { -a.x, -a.y }; +} + +Point2D operator-(Point2D a, Point2D b) { + return add(a, -b); +} + +Point2D operator*(float s, Point2D a) { + return mul(s, a); +} + +Circle operator+(Circle c, Point2D v) { + return { c.center + v, c.radius }; +} + +Circle operator-(Circle c, Point2D v) { + return { c.center - v, c.radius }; +} + +Rectangle operator+(Rectangle r, Point2D v) { + return { r.topLeft + v, r.botRight + v }; +} + +Rectangle operator-(Rectangle r, Point2D v) { + return { r.topLeft - v, r.botRight - v }; +} + +Circle operator*(float s, Circle c) { + return { c.center, s * c.radius }; +} + +Rectangle operator*(float s, Rectangle r) { + Point2D v = { width(r), height(r) }; + return { r.topLeft, r.topLeft + s * v }; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/player.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/player.cpp new file mode 100644 index 0000000..2908e92 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/player.cpp @@ -0,0 +1,56 @@ +#include "player.hpp" + +#include "constants.hpp" +#include "direction.hpp" +#include "operators.hpp" + +PlayerObject::PlayerObject() + : CircleGameObject({ { PLAYER_START_X, PLAYER_START_Y }, RADIUS }) +{} + +GameObjectKind PlayerObject::getKind() const { + return GameObjectKind::PLAYER; +} + +Point2D PlayerObject::getVelocity() const { + Point2D velocity = { 0.0f, 0.0f }; + if (CircleGameObject::getStatus() == GameObjectStatus::DESTROYED) + return velocity; + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) { + velocity = velocity + getDirection(North); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) { + velocity = velocity + getDirection(East); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) { + velocity = velocity + getDirection(South); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) { + velocity = velocity + getDirection(West); + } + velocity = SPEED * velocity; + return velocity; +} + +void PlayerObject::update(sf::Time delta) { + return; +} + +const sf::Texture* PlayerObject::getTexture(TextureManager& textureManager) const { + switch (getStatus()) { + case GameObjectStatus::NORMAL: + return textureManager.getTexture(GameTextureID::PLANET); + case GameObjectStatus::WARNED: + return textureManager.getTexture(GameTextureID::PLANET); + case GameObjectStatus::DESTROYED: + return textureManager.getTexture(GameTextureID::PLANET_DEAD); + default: + return nullptr; + } +} + +void PlayerObject::onCollision(const GameObject &object, const CollisionInfo &info) { + if (info.collide && object.getKind() == GameObjectKind::ENEMY) { + setStatus(GameObjectStatus::DESTROYED); + } +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/point.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/point.cpp new file mode 100644 index 0000000..5c74f69 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/point.cpp @@ -0,0 +1,19 @@ +#include "game.hpp" + +Point2D add(Point2D a, Point2D b) { + Point2D c = { 0, 0 }; + c.x = a.x + b.x; + c.y = a.y + b.y; + return c; +} + +Point2D mul(float s, Point2D a) { + Point2D b = { 0, 0 }; + b.x = s * a.x; + b.y = s * a.y; + return b; +} + +Point2D move(Point2D position, Point2D velocity, float delta) { + return add(position, mul(delta, velocity)); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/rectangle.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/rectangle.cpp new file mode 100644 index 0000000..2b5a1ec --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/rectangle.cpp @@ -0,0 +1,35 @@ +#include "rectangle.hpp" + +#include "operators.hpp" + +Point2D center(const Rectangle& rect) { + // TODO: explain this C++ initialization syntax + return rect.topLeft + 0.5f * Point2D { width(rect), height(rect) }; +} + +Rectangle createRectangle(Point2D p1, Point2D p2) { + Rectangle rect; + rect.topLeft = { std::min(p1.x, p2.x), std::min(p1.y, p2.y) }; + rect.botRight = { std::max(p1.x, p2.x), std::max(p1.y, p2.y) }; + return rect; +} + +Rectangle fitInto(const Rectangle& rect, const Rectangle& intoRect) { + if (width(rect) > width(intoRect) || height(rect) > height(intoRect)) { + return rect; + } + Point2D vector = { 0.0f, 0.0f }; + if (rect.topLeft.x < intoRect.topLeft.x) { + vector.x += intoRect.topLeft.x - rect.topLeft.x; + } + if (rect.topLeft.y < intoRect.topLeft.y) { + vector.y += intoRect.topLeft.y - rect.topLeft.y; + } + if (rect.botRight.x > intoRect.botRight.x) { + vector.x += intoRect.botRight.x - rect.botRight.x; + } + if (rect.botRight.y > intoRect.botRight.y) { + vector.y += intoRect.botRight.y - rect.botRight.y; + } + return rect + vector; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/scene.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/scene.cpp new file mode 100644 index 0000000..1457361 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/scene.cpp @@ -0,0 +1,47 @@ +#include "scene.hpp" + +#include "operators.hpp" + +Scene::Scene(float width, float height) + : width(width) + , height(height) +{} + +Rectangle Scene::getBoundingBox() const { + Rectangle box; + box.topLeft = { 0.0f, 0.0f }; + box.botRight = { width, height }; + return box; +} + +void Scene::setObjectPosition(GameObject& object, Point2D position) { + object.setPosition(position); + fitInto(object); +} + +void Scene::move(GameObject &object, Point2D vector) { + object.move(vector); + fitInto(object); +} + +void Scene::move(GameObject& object, sf::Time delta) { + move(object, 0.001f * delta.asMilliseconds() * object.getVelocity()); +} + +void Scene::fitInto(GameObject &object) { + Rectangle rect = ::fitInto(object.getBoundingBox(), getBoundingBox()); + object.setPosition(center(rect)); +} + +void Scene::detectCollision(GameObject& object1, GameObject& object2) { + CollisionInfo info = collisionInfo(object1, object2); + object1.onCollision(object2, info); + object2.onCollision(object1, info); +} + +void Scene::drawBackground(sf::RenderWindow &window, const sf::Texture* texture) const { + sf::Sprite background; + background.setTexture(*texture); + background.setTextureRect(sf::IntRect(0, 0, width, height)); + window.draw(background); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/scenes.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/scenes.cpp new file mode 100644 index 0000000..7125bb8 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/scenes.cpp @@ -0,0 +1,14 @@ +#include "scenes.hpp" + +bool SceneManager::initialize() { + staticScene.activate(); + return true; +} + +Scene* SceneManager::getCurrentScene() { + return &staticScene; +} + +void SceneManager::transitionScene(SceneID id) { + return; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/statscene.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/statscene.cpp new file mode 100644 index 0000000..a6abdfa --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/statscene.cpp @@ -0,0 +1,46 @@ +#include "statscene.hpp" + +#include "constants.hpp" + +GameplayStaticScene::GameplayStaticScene() : Scene(SCENE_WIDTH, SCENE_HEIGHT) {} + +void GameplayStaticScene::activate() { + fitInto(player); + fitInto(consumable); + fitInto(enemy); +} + +void GameplayStaticScene::deactivate() { + return; +} + +SceneID GameplayStaticScene::getID() const { + return SceneID::STATIC_GAME_FIELD; +} + +SceneID GameplayStaticScene::getNextSceneID() const { + return SceneID::STATIC_GAME_FIELD; +} + +void GameplayStaticScene::processEvent(const sf::Event& event) { + return; +} + +void GameplayStaticScene::update(sf::Time delta) { + player.update(delta); + consumable.update(delta); + enemy.update(delta); + move(player, delta); + move(consumable, delta); + move(enemy, delta); + detectCollision(player, consumable); + detectCollision(enemy, player); + detectCollision(enemy, consumable); +} + +void GameplayStaticScene::draw(sf::RenderWindow &window, TextureManager& textureManager) { + drawBackground(window, textureManager.getTexture(GameTextureID::SPACE)); + player.draw(window, textureManager); + consumable.draw(window, textureManager); + enemy.draw(window, textureManager); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/textures.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/textures.cpp new file mode 100644 index 0000000..2b5abcc --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/textures.cpp @@ -0,0 +1,42 @@ +#include "textures.hpp" + +#include + +const char* getTextureFilename(GameTextureID id) { + switch (id) { + case GameTextureID::SPACE: + return "resources/space.png"; + case GameTextureID::PLANET: + return "resources/planet.png"; + case GameTextureID::PLANET_DEAD: + return "resources/planetDead.png"; + case GameTextureID::STAR: + return "resources/star.png"; + case GameTextureID::STAR_CONCERNED: + return "resources/starConcerned.png"; + case GameTextureID::BLACKHOLE: + return "resources/blackhole.png"; + default: + return ""; + } +} + +bool TextureManager::initialize() { + for (size_t i = 0; i < SIZE; ++i) { + GameTextureID id = static_cast(i); + const char* filename = getTextureFilename(id); + sf::Texture* texture = &textures[i]; + if (!texture->loadFromFile(filename)) { + std::cerr << "Could not open file " << filename << "\n"; + return false; + } + if (id == GameTextureID::SPACE) { + texture->setRepeated(true); + } + } + return true; +} + +const sf::Texture* TextureManager::getTexture(GameTextureID id) const { + return &textures[static_cast(id)]; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/utils.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/utils.cpp new file mode 100644 index 0000000..783fc7a --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/src/utils.cpp @@ -0,0 +1,51 @@ +#include "utils.hpp" + +#include +#include +#include + +float distance(Point2D a, Point2D b) { + float dx = a.x - b.x; + float dy = a.y - b.y; + return sqrt(dx * dx + dy * dy); +} + +float generateFloat(float min, float max) { + return min + (rand() / (RAND_MAX / (max - min))); +} + +int generateInt(int min, int max) { + return min + rand() % (max - min + 1); +} + +bool generateBool(float prob) { + return generateFloat(0.0f, 1.0f) < prob; +} + +Point2D generatePoint(const Rectangle& boundingBox) { + Point2D point = { 0.0f, 0.0f, }; + point.x = generateFloat(boundingBox.topLeft.x, boundingBox.botRight.x); + point.y = generateFloat(boundingBox.topLeft.y, boundingBox.botRight.y); + return point; +} + +Circle generateCircle(float radius, const Rectangle& boundingBox) { + Circle circle = { { 0.0f, 0.0f }, 0.0f }; + if (radius > std::min(width(boundingBox), height(boundingBox))) { + return circle; + } + circle.center.x = generateFloat( + boundingBox.topLeft.x + radius, + boundingBox.botRight.x - radius + ); + circle.center.y = generateFloat( + boundingBox.topLeft.x + radius, + boundingBox.botRight.x - radius + ); + circle.radius = radius; + return circle; +} + +Rectangle generateRectangle(const Rectangle& boundingBox) { + return createRectangle(generatePoint(boundingBox), generatePoint(boundingBox)); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/task-info.yaml b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/task-info.yaml new file mode 100644 index 0000000..674f828 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/task-info.yaml @@ -0,0 +1,46 @@ +type: edu +custom_name: New Dynamics +files: +- name: src/gobjectlist.cpp + visible: true +- name: src/engine.cpp + visible: true +- name: src/scenes.cpp + visible: true +- name: src/textures.cpp + visible: true +- name: src/scene.cpp + visible: true +- name: src/statscene.cpp + visible: true +- name: src/dynscene.cpp + visible: true +- name: src/gobject.cpp + visible: true +- name: src/cgobject.cpp + visible: true +- name: src/player.cpp + visible: true +- name: src/consumable.cpp + visible: true +- name: src/enemy.cpp + visible: true +- name: src/collision.cpp + visible: true +- name: src/direction.cpp + visible: true +- name: src/rectangle.cpp + visible: true +- name: src/point.cpp + visible: true +- name: src/operators.cpp + visible: true +- name: src/utils.cpp + visible: true +- name: CMakeLists.txt + visible: false +- name: src/main.cpp + visible: true + editable: false +- name: test/test.cpp + visible: false diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/task-remote-info.yaml b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/task-remote-info.yaml new file mode 100644 index 0000000..4c6a4c9 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/task-remote-info.yaml @@ -0,0 +1 @@ +id: 187428144 diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/task.md b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/task.md new file mode 100644 index 0000000..92c6db1 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/task.md @@ -0,0 +1,152 @@ +Next, we need to restore the dynamic behavior of our game — the ability +to add and remove objects dynamically during play. +To do so, we will reimplement the doubly linked list data structure +in terms of object-oriented programming. +Before proceeding with this task, please first complete the `Ownership` module, +as we will need some concepts taught there. + +First of all, instead of completely rewriting the `GameplayStaticScene` class, +we will just add a new class — `GameplayDynamicScene`, where we will implement the new dynamic functionality. + +Please have a look at the declaration of the `GameplayDynamicScene` class (file `dynscene.hpp`), +and its definition (file `dynscene.cpp`). +You can find brief description of its methods in the documentation comments. + +The `GameplayDynamicScene` class has a field `objects` of the `GameObjectList` class. +This is the main class we will be working with in this task — it will implement the doubly linked list. +Its declaration and definition can be found in the `gobjectlist.hpp` and `gobjectlist.cpp` files, respectively. + +This time, using object-oriented programming and the ownership model, +we will implement a list that will own its nodes. +The nodes will be automatically destructed and deallocated upon destruction of the list itself, +and copying of the list will result in the copying of all of its nodes as well. + +The nodes of the list are represented by the `Node` structure, +which implements ownership semantics — each node owns its successor. +Let us have a closer look at the fields of the `Node` structure. + +- the `next` field is an owning pointer `std::unique_ptr`. + Whenever a node is destructed, all its succeeding nodes will also be destructed, + due to its ownership semantics. +- the `prev` field is a plain pointer `Node*`, storing the pointer to the previous node. + It is a non-owning pointer because if it were an owning pointer, it would + result in an ownership cycle. + Indeed, given a node `x`, `x.prev` could point to a node `y`, such that `y.next` points to `x` — + this is clearly a cycle. +- the `object` field is a shared pointer `std::shared_ptr` to a game object stored in the node. + It has shared ownership semantics, so that shared pointers to game objects + can be safely returned from the methods of the `GameObjectList` class. + Also, the shared ownership of game objects will allow us to implement + the copying of a list — a copy would simply store copies of shared pointers. + + +The static methods `link` and `unlink` implement the linking and unlinking of nodes, respectively. +You have already seen these functions in the `LinkedList` task. +Here is a reminder of their semantics: + +```c++ +static void link(Node* cursor, std::unique_ptr&& node); +``` + +- Links together the `cursor` and `node` nodes, putting `node` right after `cursor`. + +```c++ +static void unlink(Node* node); +``` + +- Unlinks the node from its neighboring nodes. + +Please implement these methods. +Pay attention to the different ownership semantics of the `next`, `prev`, and `object` pointers, +and use `std::move` to manage ownership. +Remember that `std::unique_ptr` transitions to a `nullptr` state after an ownership transfer, +so the order of `std::move` and pointer dereferences becomes important! + +Next, consider the fields of the `GameObjectList` class. + +- `head` is an owning pointer, `std::unique_ptr`, to the first node of the list. +- `tail` is a non-owning pointer, `Node*`, to the last node of the list. + It is a non-owning pointer because the node it points to is actually owned + by its predecessor via its `next` pointer. + +Since the `head` pointer of the list is an owning one, the whole sequence of nodes +belonging to the list will be destroyed automatically upon the list's destruction. +This is the reason why we've kept the default implementation of the class' destructor: + +```c++ +~GameObjectList() = default; +``` + +
+ +Indeed, the destructor of `GameObjectList` will call +the `~std::unique_ptr()` destructor of the `head` field. +In turn, it will call the destructor of `Node`, +which will then call the `~std::unique_ptr()` destructor of its `next` field, +and so on, until all nodes are destructed. + +
+ +Note that in the previous implementation of the list (in the `LinkedList` task), +we used a single sentinel node to simplify the implementation of some list operating functions. +Moreover, under the hood, the list was organized into a cyclic list: +the `next` field of the last node pointed to the first (sentinel) node. +This time, we cannot reuse this trick, since a cyclic list would result in an ownership cycle. +Therefore, we would need two sentinel nodes — one as the first node, and the second as the last node. + +Take a look at the pre-defined `foreach` and `remove` methods of the list, +which utilize this list representation: +- `foreach` applies the function given as an argument to every game object stored in the list; +- `remove` unlinks nodes (effectively removing them) whose game object satisfies the predicate given as an argument. + +
+ +You might find the `std::function<...>` type unfamiliar. +In essence, it is just an object-oriented counterpart of a function pointer. +We will have a closer look at this type in later modules of the course. + +
+ +Now, please implement the default constructor of the `GameObjectList` class, +which should initialize two sentinel nodes of the list: + +```c++ +GameObjectList(); +``` + +Next, implement the method for inserting a game object at the end of the list +(just before the last sentinel node): + +```c++ +void insert(const std::shared_ptr& object); +``` + +Keep in mind that: +- the first and the last nodes should remain sentinel nodes; +- only sentinel nodes can store a null pointer `nullptr` inside the `object` field. + +In order to complete this task, also finish the implementation of the ownership semantics of +the `GameObjectList` class, following the rule-of-file and copy-and-swap idiom. +In particular, please implement: + +- copy constructor +```c++ +GameObjectList(const GameObjectList& other); +``` + +- moving constructor +```c++ +GameObjectList(GameObjectList&& other) noexcept; +``` + +- assignment operator +```c++ +GameObjectList& operator=(GameObjectList other); +``` + +- swap function +```c++ +friend void swap(GameObjectList& first, GameObjectList& second); +``` + +Once you do this, you should be able to run the instance of the game with the new dynamic scene! diff --git a/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/test/test.cpp b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/test/test.cpp new file mode 100644 index 0000000..fbc4d51 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/NewDynamics/test/test.cpp @@ -0,0 +1,260 @@ +#include + +#include "gobjectlist.hpp" + +#include "testscene.hpp" +#include "testing.hpp" + +testing::Environment* const env = + testing::AddGlobalTestEnvironment(new TestEnvironment); + +TEST(GameObjectListTest, DefaultConstructorTest) { + GameObjectList list; + + size_t counter = 0; + list.foreach([&] (GameObject& it) { + counter++; + }); + ASSERT_EQ(0, counter) + << "The default constructed list is not empty\n"; +} + +TEST(GameObjectListTest, InsertTest) { + GameObjectList list; + std::shared_ptr object = std::make_shared(); + list.insert(object); + + size_t counter = 0; + list.foreach([&] (GameObject& it) { + counter++; + }); + ASSERT_EQ(1, counter) + << "The size of the list is not equal to 1\n"; + + list.foreach([&] (GameObject& it) { + ASSERT_EQ(&it, object.get()) + << "The list contains another object (not the one that was inserted)\n"; + }); +} + +TEST(GameObjectListTest, InsertNullPtrTest) { + GameObjectList list; + std::shared_ptr object; + list.insert(object); + + size_t counter = 0; + list.foreach([&] (GameObject& it) { + counter++; + }); + ASSERT_EQ(0, counter) + << "The size of the list is not equal to 1\n"; +} + +TEST(GameObjectListTest, InsertMultipleTest) { + GameObjectList list; + std::shared_ptr object1 = std::make_shared(); + std::shared_ptr object2 = std::make_shared(); + std::shared_ptr object3 = std::make_shared(); + list.insert(object1); + list.insert(object2); + list.insert(object3); + + std::vector expected { object1.get(), object2.get(), object3.get() }; + std::vector actual; + list.foreach([&] (GameObject& it) { + actual.push_back(&it); + }); + ASSERT_EQ(expected, actual); +} + +TEST(GameObjectListTest, RemoveTest) { + GameObjectList list; + std::shared_ptr object1 = std::make_shared(); + std::shared_ptr object2 = std::make_shared(); + std::shared_ptr object3 = std::make_shared(); + list.insert(object1); + list.insert(object2); + list.insert(object3); + + list.remove([&] (const GameObject& it) { + return &it == object2.get(); + }); + std::vector expected1 { object1.get(), object3.get() }; + std::vector actual1; + list.foreach([&] (GameObject& it) { + actual1.push_back(&it); + }); + ASSERT_EQ(expected1, actual1); + + list.remove([&] (const GameObject& it) { + return &it == object3.get(); + }); + std::vector expected2 { object1.get() }; + std::vector actual2; + list.foreach([&] (GameObject& it) { + actual2.push_back(&it); + }); + ASSERT_EQ(expected2, actual2); + + list.remove([&] (const GameObject& it) { + return &it == object1.get(); + }); + std::vector expected3 {}; + std::vector actual3; + list.foreach([&] (GameObject& it) { + actual3.push_back(&it); + }); + ASSERT_EQ(expected3, actual3); +} + +TEST(GameObjectListTest, RemoveAllTest) { + GameObjectList list; + std::shared_ptr object1 = std::make_shared(); + std::shared_ptr object2 = std::make_shared(); + std::shared_ptr object3 = std::make_shared(); + list.insert(object1); + list.insert(object2); + list.insert(object3); + + list.remove([&] (const GameObject& it) { + return true; + }); + std::vector expected { }; + std::vector actual; + list.foreach([&] (GameObject& it) { + actual.push_back(&it); + }); + ASSERT_EQ(expected, actual); +} + +TEST(GameObjectListTest, CopyConstructorTest) { + GameObjectList list; + std::shared_ptr object1 = std::make_shared(); + std::shared_ptr object2 = std::make_shared(); + std::shared_ptr object3 = std::make_shared(); + list.insert(object1); + list.insert(object2); + list.insert(object3); + + GameObjectList copy(list); + + std::vector expected { object1.get(), object2.get(), object3.get() }; + std::vector actual1; + list.foreach([&] (GameObject& it) { + actual1.push_back(&it); + }); + ASSERT_EQ(expected, actual1); + + std::vector actual2; + copy.foreach([&] (GameObject& it) { + actual2.push_back(&it); + }); + ASSERT_EQ(expected, actual2); +} + +TEST(GameObjectListTest, MoveConstructorTest) { + GameObjectList list; + std::shared_ptr object1 = std::make_shared(); + std::shared_ptr object2 = std::make_shared(); + std::shared_ptr object3 = std::make_shared(); + list.insert(object1); + list.insert(object2); + list.insert(object3); + + GameObjectList copy(std::move(list)); + + std::vector expected1 { object1.get(), object2.get(), object3.get() }; + std::vector actual1; + copy.foreach([&] (GameObject& it) { + actual1.push_back(&it); + }); + ASSERT_EQ(expected1, actual1); + + std::vector expected2; + std::vector actual2; + list.foreach([&] (GameObject& it) { + actual2.push_back(&it); + }); + ASSERT_EQ(expected2, actual2); +} + +TEST(GameObjectListTest, CopyAssignmentTest) { + GameObjectList list; + std::shared_ptr object1 = std::make_shared(); + std::shared_ptr object2 = std::make_shared(); + std::shared_ptr object3 = std::make_shared(); + list.insert(object1); + list.insert(object2); + list.insert(object3); + + GameObjectList copy; + copy = list; + + std::vector expected { object1.get(), object2.get(), object3.get() }; + std::vector actual1; + list.foreach([&] (GameObject& it) { + actual1.push_back(&it); + }); + ASSERT_EQ(expected, actual1); + + std::vector actual2; + copy.foreach([&] (GameObject& it) { + actual2.push_back(&it); + }); + ASSERT_EQ(expected, actual2); +} + +TEST(GameObjectListTest, MoveAssignmentTest) { + GameObjectList list; + std::shared_ptr object1 = std::make_shared(); + std::shared_ptr object2 = std::make_shared(); + std::shared_ptr object3 = std::make_shared(); + list.insert(object1); + list.insert(object2); + list.insert(object3); + + GameObjectList copy; + copy = std::move(list); + + std::vector expected1 { object1.get(), object2.get(), object3.get() }; + std::vector actual1; + copy.foreach([&] (GameObject& it) { + actual1.push_back(&it); + }); + ASSERT_EQ(expected1, actual1); + + std::vector expected2; + std::vector actual2; + list.foreach([&] (GameObject& it) { + actual2.push_back(&it); + }); + ASSERT_EQ(expected2, actual2); +} + +TEST(GameObjectListTest, SwapTest) { + GameObjectList list1; + GameObjectList list2; + std::shared_ptr object1 = std::make_shared(); + std::shared_ptr object2 = std::make_shared(); + std::shared_ptr object3 = std::make_shared(); + list1.insert(object1); + list2.insert(object2); + list2.insert(object3); + + using std::swap; + swap(list1, list2); + + std::vector expected1 { object2.get(), object3.get() }; + std::vector actual1; + list1.foreach([&] (GameObject& it) { + actual1.push_back(&it); + }); + ASSERT_EQ(expected1, actual1); + + std::vector expected2 { object1.get() }; + std::vector actual2; + list2.foreach([&] (GameObject& it) { + actual2.push_back(&it); + }); + ASSERT_EQ(expected2, actual2); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/CMakeLists.txt b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/CMakeLists.txt new file mode 100644 index 0000000..ae24761 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 3.25) + +project(ObjectOrientedProgramming-ClassesAndObjects-OperatorsOverloading) + +set(SRC + src/main.cpp + src/engine.cpp src/scenes.cpp src/textures.cpp + src/scene.cpp src/statscene.cpp src/dynscene.cpp + src/gobject.cpp src/gobjectlist.cpp src/cgobject.cpp + src/player.cpp src/consumable.cpp src/enemy.cpp + src/collision.cpp src/direction.cpp + src/rectangle.cpp src/point.cpp + src/operators.cpp src/utils.cpp +) + +set(TASK + src/operators.cpp) + +set(TEST + test/test.cpp) + +add_executable(${PROJECT_NAME}-run ${SRC}) + +configure_test_target(${PROJECT_NAME}-test "${SRC}" ${TEST}) + +prepare_sfml_framework_lesson_task( + "${CMAKE_CURRENT_SOURCE_DIR}/.." + ${PROJECT_NAME}-run + ${PROJECT_NAME}-test +) diff --git a/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/cgobject.cpp b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/cgobject.cpp new file mode 100644 index 0000000..6b24599 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/cgobject.cpp @@ -0,0 +1,47 @@ +#include "cgobject.hpp" + +#include "operators.hpp" + +CircleGameObject::CircleGameObject(Circle circle) + : circle(circle) + , status(GameObjectStatus::NORMAL) +{} + +Point2D CircleGameObject::getPosition() const { + // TODO: write your solution here +} + +void CircleGameObject::setPosition(Point2D position) { + // TODO: write your solution here +} + +GameObjectStatus CircleGameObject::getStatus() const { + // TODO: write your solution here +} + +void CircleGameObject::setStatus(GameObjectStatus newStatus) { + // TODO: write your solution here +} + +Circle CircleGameObject::getCircle() const { + // TODO: write your solution here +} + +Rectangle CircleGameObject::getBoundingBox() const { + Point2D offset = { circle.radius, circle.radius }; + Point2D p1 = circle.center - offset; + Point2D p2 = circle.center + offset; + return createRectangle(p1, p2); +} + +void CircleGameObject::draw(sf::RenderWindow &window, TextureManager& textureManager) const { + const sf::Texture* texture = getTexture(textureManager); + if (texture == nullptr) + return; + sf::CircleShape shape; + shape.setPosition(circle.center.x, circle.center.y); + shape.setOrigin(circle.radius, circle.radius); + shape.setRadius(circle.radius); + shape.setTexture(texture); + window.draw(shape); +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/collision.cpp b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/collision.cpp new file mode 100644 index 0000000..9b56990 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/collision.cpp @@ -0,0 +1,21 @@ +#include + +#include "collision.hpp" +#include "cgobject.hpp" +#include "utils.hpp" + +CollisionInfo collisionInfo(const Circle& circle1, const Circle& circle2) { + CollisionInfo info; + info.distance = distance(circle1.center, circle2.center); + info.collide = (info.distance < circle1.radius + circle2.radius); + return info; +} + +CollisionInfo collisionInfo(const GameObject& object1, const GameObject& object2) { + const CircleGameObject* circleObject1 = dynamic_cast(&object1); + const CircleGameObject* circleObject2 = dynamic_cast(&object2); + if (circleObject1 && circleObject2) { + return collisionInfo(circleObject1->getCircle(), circleObject2->getCircle()); + } + assert(false); +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/consumable.cpp b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/consumable.cpp new file mode 100644 index 0000000..7bd2eaa --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/consumable.cpp @@ -0,0 +1,27 @@ +#include "consumable.hpp" + +#include "constants.hpp" + +ConsumableObject::ConsumableObject() + : CircleGameObject({ { CONSUMABLE_START_X, CONSUMABLE_START_Y }, CONSUMABLE_RADIUS }) +{} + +GameObjectKind ConsumableObject::getKind() const { + return GameObjectKind::CONSUMABLE; +} + +Point2D ConsumableObject::getVelocity() const { + return { 0.0f, 0.0f }; +} + +void ConsumableObject::update(sf::Time delta) { + // TODO: write your solution here +} + +void ConsumableObject::onCollision(const GameObject &object, const CollisionInfo &info) { + // TODO: write your solution here +} + +const sf::Texture* ConsumableObject::getTexture(TextureManager& textureManager) const { + // TODO: write your solution here +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/direction.cpp b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/direction.cpp new file mode 100644 index 0000000..0a2b606 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/direction.cpp @@ -0,0 +1,16 @@ +#include "direction.hpp" + +Point2D getDirection(Direction direction) { + switch (direction) { + case North: + return { 0.0f, -1.0f }; + case East: + return { 1.0f, 0.0f }; + case South: + return { 0.0f, 1.0f }; + case West: + return { -1.0f, 0.0f }; + default: + return { 0.0f, 0.0f }; + } +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/dynscene.cpp b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/dynscene.cpp new file mode 100644 index 0000000..1dbcf2e --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/dynscene.cpp @@ -0,0 +1,144 @@ +#include "dynscene.hpp" + +#include "constants.hpp" +#include "player.hpp" +#include "consumable.hpp" +#include "enemy.hpp" +#include "utils.hpp" + +const int MAX_DYNAMIC_OBJECTS_ON_SCENE = 10; +const int MAX_ENEMY_OBJECTS_ON_SCENE = 4; + +const int NEW_DYNAMIC_OBJECT_PROB = 1; +const int NEW_ENEMY_OBJECT_PROB = 10; + +GameplayDynamicScene::GameplayDynamicScene() : Scene(SCENE_WIDTH, SCENE_HEIGHT) {} + +void GameplayDynamicScene::activate() { + addNewGameObject(GameObjectKind::PLAYER); +} + +void GameplayDynamicScene::deactivate() { + // remove all the objects from the list + objects.remove([&] (const GameObject& object) { + return true; + }); +} + +SceneID GameplayDynamicScene::getID() const { + return SceneID::DYNAMIC_GAME_FIELD; +} + +SceneID GameplayDynamicScene::getNextSceneID() const { + return SceneID::DYNAMIC_GAME_FIELD; +} + +void GameplayDynamicScene::processEvent(const sf::Event& event) { + return; +} + +void GameplayDynamicScene::update(sf::Time delta) { + // update the objects' state + objects.foreach([delta] (GameObject& object) { + object.update(delta); + }); + // move objects according to their velocity and elapsed time + objects.foreach([this, delta] (GameObject& object) { + move(object, delta); + }); + // compute collision information for each pair of objects + objects.foreach([this] (GameObject& object) { + objects.foreach([this, &object] (GameObject& other) { + if (&object != &other) { + detectCollision(object, other); + } + }); + }); + // update the list of objects + updateObjectsList(); +} + +void GameplayDynamicScene::updateObjectsList() { + // remove destroyed objects from the list + objects.remove([] (const GameObject& object) { + return (object.getStatus() == GameObjectStatus::DESTROYED) + && (object.getKind() != GameObjectKind::PLAYER); + }); + // count the number of the different kinds of objects present on the scene + int consumableCount = 0; + int enemyCount = 0; + objects.foreach([&] (const GameObject& object) { + switch (object.getKind()) { + case GameObjectKind::CONSUMABLE: + ++consumableCount; + break; + case GameObjectKind::ENEMY: + ++enemyCount; + break; + default: + break; + } + }); + // add new objects of randomly chosen kind if there is enough room for them on the scene + int dynamicObjectsCount = consumableCount + enemyCount; + if (dynamicObjectsCount < MAX_DYNAMIC_OBJECTS_ON_SCENE) { + int r = rand() % 100; + int k = rand() % 100; + if (r < NEW_DYNAMIC_OBJECT_PROB) { + if (k < NEW_ENEMY_OBJECT_PROB && enemyCount < MAX_ENEMY_OBJECTS_ON_SCENE) { + addNewGameObject(GameObjectKind::ENEMY); + } else if (k > NEW_ENEMY_OBJECT_PROB) { + addNewGameObject(GameObjectKind::CONSUMABLE); + } + } + } +} + +std::shared_ptr GameplayDynamicScene::addNewGameObject(GameObjectKind kind) { + std::shared_ptr object; + while (!object) { + // create an object with default position + switch (kind) { + case GameObjectKind::PLAYER: { + object = std::make_shared(); + break; + } + case GameObjectKind::CONSUMABLE: { + object = std::make_shared(); + break; + } + case GameObjectKind::ENEMY: { + object = std::make_shared(); + break; + } + } + // set random position for consumable and enemy objects + if (kind == GameObjectKind::CONSUMABLE || + kind == GameObjectKind::ENEMY) { + setObjectPosition(*object, generatePoint(getBoundingBox())); + } else { + fitInto(*object); + } + // check that object does not collide with existing objects + bool collide = false; + objects.foreach([&collide, &object](GameObject& other) { + collide |= collisionInfo(*object, other).collide; + }); + // reset a colliding object + if (collide) { + object = nullptr; + } + } + objects.insert(object); + return object; +} + +void GameplayDynamicScene::draw(sf::RenderWindow &window, TextureManager& textureManager) { + // draw background + drawBackground(window, textureManager.getTexture(GameTextureID::SPACE)); + // draw all objects on the scene + objects.foreach([&] (const GameObject& object) { + object.draw(window, textureManager); + }); +} + diff --git a/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/enemy.cpp b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/enemy.cpp new file mode 100644 index 0000000..3bb34d8 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/enemy.cpp @@ -0,0 +1,44 @@ +#include "enemy.hpp" + +#include "constants.hpp" +#include "operators.hpp" +#include "utils.hpp" + +EnemyObject::EnemyObject() + : CircleGameObject({ { ENEMY_START_X, ENEMY_START_Y }, ENEMY_RADIUS }) +{} + +GameObjectKind EnemyObject::getKind() const { + return GameObjectKind::ENEMY; +} + +Point2D EnemyObject::getVelocity() const { + return velocity; +} + +void EnemyObject::update(sf::Time delta) { + if (CircleGameObject::getStatus() == GameObjectStatus::DESTROYED) + return; + updateTimer += delta; + if (updateTimer < sf::seconds(1.0f)) + return; + updateTimer = sf::milliseconds(0.0f); + updateVelocity(); +} + +void EnemyObject::setVelocity(Point2D velocity) { + this->velocity = velocity; +} + +void EnemyObject::updateVelocity() { + // TODO: write your solution here +} + +void EnemyObject::onCollision(const GameObject &object, const CollisionInfo &info) { + return; +} + +const sf::Texture* EnemyObject::getTexture(TextureManager& textureManager) const { + // TODO: write your solution here + return nullptr; +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/engine.cpp b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/engine.cpp new file mode 100644 index 0000000..c506cdf --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/engine.cpp @@ -0,0 +1,89 @@ +#include "engine.hpp" + +#include "constants.hpp" + +GameEngine *GameEngine::create() { + // TODO: write your solution here + return nullptr; +} + +GameEngine::GameEngine() + : scene(nullptr) + , active(true) +{ + // initialize random number generator + srand(time(nullptr)); + // initialize resource managers + active &= sceneManager.initialize(); + active &= textureManager.initialize(); + if (!active) { + return; + } + // set the current scene + scene = sceneManager.getCurrentScene(); + // initialize the application window + window.create(sf::VideoMode(WINDOW_WIDTH, WINDOW_HEIGHT), "Space Game"); + window.setFramerateLimit(60); +} + +void GameEngine::run() { + sf::Clock clock; + while (isActive()) { + sf::Time delta = clock.restart(); + processInput(); + update(delta); + render(); + sceneTransition(); + } +} + +bool GameEngine::isActive() const { + return active && window.isOpen(); +} + +void GameEngine::close() { + active = false; + window.close(); +} + +void GameEngine::processInput() { + sf::Event event; + while (window.pollEvent(event)) { + processEvent(event); + } +} + +void GameEngine::processEvent(const sf::Event &event) { + switch (event.type) { + case sf::Event::Closed: + close(); + break; + default: + scene->processEvent(event); + break; + } +} + +void GameEngine::update(sf::Time delta) { + scene->update(delta); +} + +void GameEngine::render() { + window.clear(sf::Color::White); + scene->draw(window, textureManager); + window.display(); +} + +void GameEngine::sceneTransition() { + if (scene->getNextSceneID() != scene->getID()) { + sceneManager.transitionScene(scene->getNextSceneID()); + scene = sceneManager.getCurrentScene(); + resizeWindow(); + } +} + +void GameEngine::resizeWindow() { + Rectangle sceneBox = scene->getBoundingBox(); + sf::Vector2u sceneSize = sf::Vector2u(width(sceneBox), height(sceneBox)); + window.setSize(sceneSize); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/gobject.cpp b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/gobject.cpp new file mode 100644 index 0000000..b1abc1e --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/gobject.cpp @@ -0,0 +1,7 @@ +#include "gobject.hpp" + +#include "operators.hpp" + +void GameObject::move(Point2D vector) { + // TODO: write your solution here +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/gobjectlist.cpp b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/gobjectlist.cpp new file mode 100644 index 0000000..5b8b35e --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/gobjectlist.cpp @@ -0,0 +1,54 @@ +#include "gobjectlist.hpp" + +void GameObjectList::link(GameObjectList::Node *cursor, std::unique_ptr &&node) { + // TODO: write your solution here +} + +void GameObjectList::unlink(GameObjectList::Node *node) { + // TODO: write your solution here +} + +GameObjectList::GameObjectList() { + // TODO: write your solution here +} + +void GameObjectList::insert(const std::shared_ptr &object) { + // TODO: write your solution here +} + +void GameObjectList::remove(const std::function &pred) { + GameObjectList::Node* curr = head->next.get(); + while (curr != tail) { + Node* next = curr->next.get(); + if (pred(*curr->object)) { + unlink(curr); + } + curr = next; + } +} + +void GameObjectList::foreach(const std::function& apply) { + Node* curr = head->next.get(); + while (curr != tail) { + apply(*curr->object); + curr = curr->next.get(); + } +} + +GameObjectList::GameObjectList(const GameObjectList &other) : GameObjectList() { + // TODO: write your solution here +} + +GameObjectList::GameObjectList(GameObjectList &&other) noexcept : GameObjectList() { + // TODO: write your solution here +} + +GameObjectList &GameObjectList::operator=(GameObjectList other) { + // TODO: write your solution here + return *this; +} + +void swap(GameObjectList& first, GameObjectList& second) { + using std::swap; + // TODO: write your solution here +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/main.cpp b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/main.cpp new file mode 100644 index 0000000..b192818 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/main.cpp @@ -0,0 +1,13 @@ +#include "engine.hpp" + +#include + +int main() { + GameEngine* engine = GameEngine::create(); + if (!engine) { + std::cout << "Game engine is not created!\n"; + return 1; + } + GameEngine::create()->run(); + return 0; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/operators.cpp b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/operators.cpp new file mode 100644 index 0000000..4dc9097 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/operators.cpp @@ -0,0 +1,42 @@ +#include "game.hpp" + +Point2D operator+(Point2D a, Point2D b) { + return add(a, b); +} + +Point2D operator-(Point2D a) { + return { -a.x, -a.y }; +} + +Point2D operator-(Point2D a, Point2D b) { + return add(a, -b); +} + +Point2D operator*(float s, Point2D a) { + return mul(s, a); +} + +Circle operator+(Circle c, Point2D v) { + return { c.center + v, c.radius }; +} + +Circle operator-(Circle c, Point2D v) { + return { c.center - v, c.radius }; +} + +Rectangle operator+(Rectangle r, Point2D v) { + return { r.topLeft + v, r.botRight + v }; +} + +Rectangle operator-(Rectangle r, Point2D v) { + return { r.topLeft - v, r.botRight - v }; +} + +Circle operator*(float s, Circle c) { + return { c.center, s * c.radius }; +} + +Rectangle operator*(float s, Rectangle r) { + Point2D v = { width(r), height(r) }; + return { r.topLeft, r.topLeft + s * v }; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/player.cpp b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/player.cpp new file mode 100644 index 0000000..e335cf0 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/player.cpp @@ -0,0 +1,46 @@ +#include "player.hpp" + +#include "constants.hpp" +#include "direction.hpp" +#include "operators.hpp" + +PlayerObject::PlayerObject() + : CircleGameObject({ { PLAYER_START_X, PLAYER_START_Y }, RADIUS }) +{} + +GameObjectKind PlayerObject::getKind() const { + return GameObjectKind::PLAYER; +} + +Point2D PlayerObject::getVelocity() const { + Point2D velocity = { 0.0f, 0.0f }; + if (CircleGameObject::getStatus() == GameObjectStatus::DESTROYED) + return velocity; + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) { + velocity = velocity + getDirection(North); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) { + velocity = velocity + getDirection(East); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) { + velocity = velocity + getDirection(South); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) { + velocity = velocity + getDirection(West); + } + velocity = SPEED * velocity; + return velocity; +} + +void PlayerObject::update(sf::Time delta) { + return; +} + +const sf::Texture* PlayerObject::getTexture(TextureManager& textureManager) const { + // TODO: write your solution here + return nullptr; +} + +void PlayerObject::onCollision(const GameObject &object, const CollisionInfo &info) { + // TODO: write your solution here +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/point.cpp b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/point.cpp new file mode 100644 index 0000000..5c74f69 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/point.cpp @@ -0,0 +1,19 @@ +#include "game.hpp" + +Point2D add(Point2D a, Point2D b) { + Point2D c = { 0, 0 }; + c.x = a.x + b.x; + c.y = a.y + b.y; + return c; +} + +Point2D mul(float s, Point2D a) { + Point2D b = { 0, 0 }; + b.x = s * a.x; + b.y = s * a.y; + return b; +} + +Point2D move(Point2D position, Point2D velocity, float delta) { + return add(position, mul(delta, velocity)); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/rectangle.cpp b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/rectangle.cpp new file mode 100644 index 0000000..2b5a1ec --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/rectangle.cpp @@ -0,0 +1,35 @@ +#include "rectangle.hpp" + +#include "operators.hpp" + +Point2D center(const Rectangle& rect) { + // TODO: explain this C++ initialization syntax + return rect.topLeft + 0.5f * Point2D { width(rect), height(rect) }; +} + +Rectangle createRectangle(Point2D p1, Point2D p2) { + Rectangle rect; + rect.topLeft = { std::min(p1.x, p2.x), std::min(p1.y, p2.y) }; + rect.botRight = { std::max(p1.x, p2.x), std::max(p1.y, p2.y) }; + return rect; +} + +Rectangle fitInto(const Rectangle& rect, const Rectangle& intoRect) { + if (width(rect) > width(intoRect) || height(rect) > height(intoRect)) { + return rect; + } + Point2D vector = { 0.0f, 0.0f }; + if (rect.topLeft.x < intoRect.topLeft.x) { + vector.x += intoRect.topLeft.x - rect.topLeft.x; + } + if (rect.topLeft.y < intoRect.topLeft.y) { + vector.y += intoRect.topLeft.y - rect.topLeft.y; + } + if (rect.botRight.x > intoRect.botRight.x) { + vector.x += intoRect.botRight.x - rect.botRight.x; + } + if (rect.botRight.y > intoRect.botRight.y) { + vector.y += intoRect.botRight.y - rect.botRight.y; + } + return rect + vector; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/scene.cpp b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/scene.cpp new file mode 100644 index 0000000..81a4470 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/scene.cpp @@ -0,0 +1,45 @@ +#include "scene.hpp" + +#include "operators.hpp" + +Scene::Scene(float width, float height) + : width(width) + , height(height) +{} + +Rectangle Scene::getBoundingBox() const { + Rectangle box; + box.topLeft = { 0.0f, 0.0f }; + box.botRight = { width, height }; + return box; +} + +void Scene::setObjectPosition(GameObject& object, Point2D position) { + // TODO: write your solution here +} + +void Scene::move(GameObject &object, Point2D vector) { + // TODO: write your solution here +} + +void Scene::move(GameObject& object, sf::Time delta) { + move(object, 0.001f * delta.asMilliseconds() * object.getVelocity()); +} + +void Scene::fitInto(GameObject &object) { + Rectangle rect = ::fitInto(object.getBoundingBox(), getBoundingBox()); + object.setPosition(center(rect)); +} + +void Scene::detectCollision(GameObject& object1, GameObject& object2) { + CollisionInfo info = collisionInfo(object1, object2); + object1.onCollision(object2, info); + object2.onCollision(object1, info); +} + +void Scene::drawBackground(sf::RenderWindow &window, const sf::Texture* texture) const { + sf::Sprite background; + background.setTexture(*texture); + background.setTextureRect(sf::IntRect(0, 0, width, height)); + window.draw(background); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/scenes.cpp b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/scenes.cpp new file mode 100644 index 0000000..7125bb8 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/scenes.cpp @@ -0,0 +1,14 @@ +#include "scenes.hpp" + +bool SceneManager::initialize() { + staticScene.activate(); + return true; +} + +Scene* SceneManager::getCurrentScene() { + return &staticScene; +} + +void SceneManager::transitionScene(SceneID id) { + return; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/statscene.cpp b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/statscene.cpp new file mode 100644 index 0000000..a6abdfa --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/statscene.cpp @@ -0,0 +1,46 @@ +#include "statscene.hpp" + +#include "constants.hpp" + +GameplayStaticScene::GameplayStaticScene() : Scene(SCENE_WIDTH, SCENE_HEIGHT) {} + +void GameplayStaticScene::activate() { + fitInto(player); + fitInto(consumable); + fitInto(enemy); +} + +void GameplayStaticScene::deactivate() { + return; +} + +SceneID GameplayStaticScene::getID() const { + return SceneID::STATIC_GAME_FIELD; +} + +SceneID GameplayStaticScene::getNextSceneID() const { + return SceneID::STATIC_GAME_FIELD; +} + +void GameplayStaticScene::processEvent(const sf::Event& event) { + return; +} + +void GameplayStaticScene::update(sf::Time delta) { + player.update(delta); + consumable.update(delta); + enemy.update(delta); + move(player, delta); + move(consumable, delta); + move(enemy, delta); + detectCollision(player, consumable); + detectCollision(enemy, player); + detectCollision(enemy, consumable); +} + +void GameplayStaticScene::draw(sf::RenderWindow &window, TextureManager& textureManager) { + drawBackground(window, textureManager.getTexture(GameTextureID::SPACE)); + player.draw(window, textureManager); + consumable.draw(window, textureManager); + enemy.draw(window, textureManager); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/textures.cpp b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/textures.cpp new file mode 100644 index 0000000..2b5abcc --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/textures.cpp @@ -0,0 +1,42 @@ +#include "textures.hpp" + +#include + +const char* getTextureFilename(GameTextureID id) { + switch (id) { + case GameTextureID::SPACE: + return "resources/space.png"; + case GameTextureID::PLANET: + return "resources/planet.png"; + case GameTextureID::PLANET_DEAD: + return "resources/planetDead.png"; + case GameTextureID::STAR: + return "resources/star.png"; + case GameTextureID::STAR_CONCERNED: + return "resources/starConcerned.png"; + case GameTextureID::BLACKHOLE: + return "resources/blackhole.png"; + default: + return ""; + } +} + +bool TextureManager::initialize() { + for (size_t i = 0; i < SIZE; ++i) { + GameTextureID id = static_cast(i); + const char* filename = getTextureFilename(id); + sf::Texture* texture = &textures[i]; + if (!texture->loadFromFile(filename)) { + std::cerr << "Could not open file " << filename << "\n"; + return false; + } + if (id == GameTextureID::SPACE) { + texture->setRepeated(true); + } + } + return true; +} + +const sf::Texture* TextureManager::getTexture(GameTextureID id) const { + return &textures[static_cast(id)]; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/utils.cpp b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/utils.cpp new file mode 100644 index 0000000..783fc7a --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/src/utils.cpp @@ -0,0 +1,51 @@ +#include "utils.hpp" + +#include +#include +#include + +float distance(Point2D a, Point2D b) { + float dx = a.x - b.x; + float dy = a.y - b.y; + return sqrt(dx * dx + dy * dy); +} + +float generateFloat(float min, float max) { + return min + (rand() / (RAND_MAX / (max - min))); +} + +int generateInt(int min, int max) { + return min + rand() % (max - min + 1); +} + +bool generateBool(float prob) { + return generateFloat(0.0f, 1.0f) < prob; +} + +Point2D generatePoint(const Rectangle& boundingBox) { + Point2D point = { 0.0f, 0.0f, }; + point.x = generateFloat(boundingBox.topLeft.x, boundingBox.botRight.x); + point.y = generateFloat(boundingBox.topLeft.y, boundingBox.botRight.y); + return point; +} + +Circle generateCircle(float radius, const Rectangle& boundingBox) { + Circle circle = { { 0.0f, 0.0f }, 0.0f }; + if (radius > std::min(width(boundingBox), height(boundingBox))) { + return circle; + } + circle.center.x = generateFloat( + boundingBox.topLeft.x + radius, + boundingBox.botRight.x - radius + ); + circle.center.y = generateFloat( + boundingBox.topLeft.x + radius, + boundingBox.botRight.x - radius + ); + circle.radius = radius; + return circle; +} + +Rectangle generateRectangle(const Rectangle& boundingBox) { + return createRectangle(generatePoint(boundingBox), generatePoint(boundingBox)); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/task-info.yaml b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/task-info.yaml new file mode 100644 index 0000000..11b32b3 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/task-info.yaml @@ -0,0 +1,46 @@ +type: edu +custom_name: Operators Overloading +files: +- name: src/operators.cpp + visible: true +- name: src/engine.cpp + visible: true +- name: src/scenes.cpp + visible: true +- name: src/textures.cpp + visible: true +- name: src/scene.cpp + visible: true +- name: src/statscene.cpp + visible: true +- name: src/dynscene.cpp + visible: true +- name: src/gobject.cpp + visible: true +- name: src/gobjectlist.cpp + visible: true +- name: src/cgobject.cpp + visible: true +- name: src/player.cpp + visible: true +- name: src/consumable.cpp + visible: true +- name: src/enemy.cpp + visible: true +- name: src/collision.cpp + visible: true +- name: src/direction.cpp + visible: true +- name: src/rectangle.cpp + visible: true +- name: src/point.cpp + visible: true +- name: src/utils.cpp + visible: true +- name: CMakeLists.txt + visible: false +- name: src/main.cpp + visible: true + editable: false +- name: test/test.cpp + visible: false diff --git a/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/task-remote-info.yaml b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/task-remote-info.yaml new file mode 100644 index 0000000..4154b3f --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/task-remote-info.yaml @@ -0,0 +1 @@ +id: 841812685 diff --git a/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/task.md b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/task.md new file mode 100644 index 0000000..1be691d --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/task.md @@ -0,0 +1,138 @@ +Before we dive into object-oriented programming, +we will learn about another useful feature of the C++ language +that can help make your code easier to read and understand. +This feature is __operator overloading__; it allows you to define various operators, +like arithmetic operators `+`, `-`, `*`, +for your custom data types. + +Recall the `move` function you implemented before. +At first, your task was to implement it to move an object along the `x` axis. +Your code likely looked like this: + +```c++ +float move(float position, float velocity, float delta) { + return position + delta * velocity; +} +``` + +At the next stage, your task was to reimplement `move`, +this time to move an object along both `x` and `y` axes, +using our custom data type `Point2D` and two functions +`add` and `mul` defined for it: + +```c++ +Point2D move(Point2D position, Point2D velocity, float delta) { + return add(position, mul(delta, velocity)); +} +``` + +As you may notice, just by looking at the code, +it is much harder to grasp what is happening in the second code fragment. +Fortunately, with operator overloading, it is possible to make +the two versions of the `move` function look identical! + +```c++ +Point2D move(Point2D position, Point2D velocity, float delta) { + return position + delta * velocity; +} +``` + +To enable this syntax, it is sufficient to define +a special function with the `operator` prefix in its name: + +```c++ +Point2D operator+(Point2D a, Point2D b) { + // your code here +} +``` + +The C++ language allows for the overloading of a [large number of operators](https://en.cppreference.com/w/cpp/language/operators). +Please remember, just like any other feature, the operator overloading feature can be potentially abused. +It is recommended to overload operators only if the corresponding notation +has a natural interpretation for your custom data type! + +Your next task is to implement several overloaded operators for the data types used in our game. + +Let us start with the familiar operations on the `Point2D` data type. +Please overload the arithmetic operators `+`, `-`, `*` for this data type +(their signatures are already given in the task template). +It is allowed to use the `add` and `mul` functions implemented in previous steps. +Note the difference between the subtraction operator and the unary minus operator — +the former subtracts the coordinates of one point from another, +while the latter just changes the sign of both coordinates of a single point. + +Arithmetic operators also have natural interpretations for shape data types, such as `Circle` and `Rectangle`. + +
+ +Please note that a rectangle is defined by two points — its top-left and bottom-right corners: + +```c++ +struct Rectangle { + Point2D topLeft; + Point2D botRight; +}; +``` + +
+ +When adding a point to a shape, the point is interpreted as a vector and +the shape should move in the direction of this vector. +* For the `Circle` shape, it is sufficient to add the point to the center of the circle. +* For `Rectangle`, it is required to add the point to both corners of the rectangle. + +Multiplying a shape by a scalar should result in a scaling operation. +* For `Circle`, it is sufficient to multiply the radius by the scalar. +* For `Rectangle`, the implementation is a bit trickier. + It is required to scale the width and height of the rectangle and then recompute its bottom-right corner. + You might use the predefined functions `width` and `height` to get + the corresponding properties of the rectangle (defined in the file `rectangle.hpp`). + +```c++ +float width(const Rectangle& rect) { + return rect.botRight.x - rect.topLeft.x; +} + +float height(const Rectangle& rect) { + return rect.botRight.y - rect.topLeft.y; +} +``` + +Finally, it would be convenient to overload the equality comparison operators for all the data types mentioned above. + +
+ +In the C++ language, it is also possible to overload the input/output operators +for reading from or writing to the terminal. +Here is an example of corresponding overloads for the `Point2D` data type: + +```c++ +// output operator overload +std::ostream& operator<<(std::ostream& os, const Point2D& p) { + return os << "(" << p.x << ", " << p.y << ")"; +} + +// input operator overload +std::istream& operator>>(std::istream& is, Point2D& p) { + return is >> p.x >> p.y; +} +``` + +Notice the arguments of type `std::ostream` and `std::istream` — those are +output and input streams, respectively. +While we have not covered them in this course yet, they will be topics in upcoming lessons. +For now, however, it is sufficient to know that `std::cout` and `std::cin` +(which we have seen in previous lessons!) are particular objects of these classes. + +Therefore, with the help of the overloads given above, it becomes possible to write the following code: +```c++ +Point2D point; +std::cout << "Please enter coordinates of point (x, y)" << std::endl; +std::cin >> point; +std::cout << "Your point is " << point << std::endl; +``` + +You can find the overloads of the input/output operators +for the other data types used in our game in the `operators.hpp` file. + +
diff --git a/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/test/test.cpp b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/test/test.cpp new file mode 100644 index 0000000..efd387a --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/OperatorsOverloading/test/test.cpp @@ -0,0 +1,369 @@ +#include + +#include "operators.hpp" +#include "testing.hpp" + +testing::Environment* const env = + testing::AddGlobalTestEnvironment(new TestEnvironment); + +const float MIN = -10e3; +const float MAX = 10e3; + +const Rectangle generateArea = { { MIN, MIN }, { MAX, MAX } }; + +namespace expected { + Point2D add(Point2D a, Point2D b) { + Point2D c = { 0, 0 }; + c.x = a.x + b.x; + c.y = a.y + b.y; + return c; + } + + Point2D mul(float s, Point2D a) { + Point2D b = { 0, 0 }; + b.x = s * a.x; + b.y = s * a.y; + return b; + } +} + +std::string point_plus_error_msg(Point2D a, Point2D b, Point2D expected, Point2D actual) { + std::ostringstream stream; + stream << "Testing expression:\n" + << " c = a + b" << "\n"; + stream << "Test data:\n" + << " a = " << a << "\n" + << " b = " << b << "\n"; + stream << "Expected result:\n" + << " c = " << expected << "\n"; + stream << "Actual result:\n" + << " c = " << actual << "\n"; + return stream.str(); +} + +TEST(PointPlusTest, PointPlusTest) { + property_test( + [] () { + Point2D a = generatePoint(generateArea); + Point2D b = generatePoint(generateArea); + return std::make_tuple(a, b); + }, + [] (std::tuple data) { + Point2D a, b; + std::tie(a, b) = data; + Point2D expected = expected::add(a, b); + Point2D actual = a + b; + ASSERT_FLOAT_EQ(expected.x, actual.x) << point_plus_error_msg(a, b, expected, actual); + ASSERT_FLOAT_EQ(expected.y, actual.y) << point_plus_error_msg(a, b, expected, actual); + } + ); +} + +std::string point_unary_minus_error_msg(Point2D a, Point2D expected, Point2D actual) { + std::ostringstream stream; + stream << "Testing expression:\n" + << " b = -a" << "\n"; + stream << "Test data:\n" + << " a = " << a << "\n"; + stream << "Expected result:\n" + << " b = " << expected << "\n"; + stream << "Actual result:\n" + << " b = " << actual << "\n"; + return stream.str(); +} + +TEST(PointUnaryMinusTest, PointUnaryMinusTest) { + property_test( + [] () { + Point2D a = generatePoint(generateArea); + return a; + }, + [] (Point2D a) { + Point2D expected = { -a.x, -a.y }; + Point2D actual = -a; + ASSERT_FLOAT_EQ(expected.x, actual.x) << point_unary_minus_error_msg(a, expected, actual); + ASSERT_FLOAT_EQ(expected.y, actual.y) << point_unary_minus_error_msg(a, expected, actual); + } + ); +} + +std::string point_minus_error_msg(Point2D a, Point2D b, Point2D expected, Point2D actual) { + std::ostringstream stream; + stream << "Testing expression:\n" + << " c = a - b" << "\n"; + stream << "Test data:\n" + << " a = " << a << "\n" + << " b = " << b << "\n"; + stream << "Expected result:\n" + << " c = " << expected << "\n"; + stream << "Actual result:\n" + << " c = " << actual << "\n"; + return stream.str(); +} + +TEST(PointMinusTest, PointMinusTest) { + property_test( + [] () { + Point2D a = generatePoint(generateArea); + Point2D b = generatePoint(generateArea); + return std::make_tuple(a, b); + }, + [] (std::tuple data) { + Point2D a, b; + std::tie(a, b) = data; + Point2D expected = expected::add(a, { -b.x, -b.y }); + Point2D actual = a - b; + ASSERT_FLOAT_EQ(expected.x, actual.x) << point_minus_error_msg(a, b, expected, actual); + ASSERT_FLOAT_EQ(expected.y, actual.y) << point_minus_error_msg(a, b, expected, actual); + } + ); +} + +std::string point_scalar_mul_error_msg(float s, Point2D a, Point2D expected, Point2D actual) { + std::ostringstream stream; + stream << "Testing expression:\n" + << " c = s * a" << "\n"; + stream << "Test data:\n" + << " s = " << s << "\n" + << " a = " << a << "\n"; + stream << "Expected result:\n" + << " c = " << expected << "\n"; + stream << "Actual result:\n" + << " c = " << actual << "\n"; + return stream.str(); +} + +TEST(ScalarProdTest, ScalarProdTest) { + property_test( + [] () { + float s = generateFloat(MIN, MAX); + Point2D a = generatePoint(generateArea); + return std::make_tuple(s, a); + }, + [] (std::tuple data) { + float s; + Point2D a; + std::tie(s, a) = data; + Point2D expected = expected::mul(s, a); + Point2D actual = s * a; + ASSERT_FLOAT_EQ(expected.x, actual.x) << point_scalar_mul_error_msg(s, a, expected, actual); + ASSERT_FLOAT_EQ(expected.y, actual.y) << point_scalar_mul_error_msg(s, a, expected, actual); + } + ); +} + +std::string circle_plus_error_msg(Circle c, Point2D v, Circle expected, Circle actual) { + std::ostringstream stream; + stream << "Testing expression:\n" + << " c' = c + v" << "\n"; + stream << "Test data:\n" + << " c = " << c << "\n" + << " v = " << v << "\n"; + stream << "Expected result:\n" + << " c' = " << expected << "\n"; + stream << "Actual result:\n" + << " c' = " << actual << "\n"; + return stream.str(); +} + +TEST(CirclePlusTest, CirclePlusTest) { + property_test( + [] () { + Circle c = generateCircle(generateFloat(MIN, MAX), generateArea); + Point2D v = generatePoint(generateArea); + return std::make_tuple(c, v); + }, + [] (std::tuple data) { + Circle c; + Point2D v; + std::tie(c, v) = data; + Circle expected = Circle { expected::add(c.center, v) , c.radius }; + Circle actual = c + v; + ASSERT_FLOAT_EQ(expected.center.x, actual.center.x) << circle_plus_error_msg(c, v, expected, actual); + ASSERT_FLOAT_EQ(expected.center.y, actual.center.y) << circle_plus_error_msg(c, v, expected, actual); + ASSERT_FLOAT_EQ(expected.radius, actual.radius) << circle_plus_error_msg(c, v, expected, actual); + } + ); +} + +std::string circle_minus_error_msg(Circle c, Point2D v, Circle expected, Circle actual) { + std::ostringstream stream; + stream << "Testing expression:\n" + << " c' = c - v" << "\n"; + stream << "Test data:\n" + << " c = " << c << "\n" + << " v = " << v << "\n"; + stream << "Expected result:\n" + << " c' = " << expected << "\n"; + stream << "Actual result:\n" + << " c' = " << actual << "\n"; + return stream.str(); +} + +TEST(CircleMinusTest, CircleMinusTest) { + property_test( + [] () { + Circle c = generateCircle(generateFloat(MIN, MAX), generateArea); + Point2D v = generatePoint(generateArea); + return std::make_tuple(c, v); + }, + [] (std::tuple data) { + Circle c; + Point2D v; + std::tie(c, v) = data; + Circle expected = Circle { expected::add(c.center, Point2D { -v.x, -v.y }) , c.radius }; + Circle actual = c - v; + ASSERT_FLOAT_EQ(expected.center.x, actual.center.x) << circle_minus_error_msg(c, v, expected, actual); + ASSERT_FLOAT_EQ(expected.center.y, actual.center.y) << circle_minus_error_msg(c, v, expected, actual); + ASSERT_FLOAT_EQ(expected.radius, actual.radius) << circle_minus_error_msg(c, v, expected, actual); + } + ); +} + +std::string rectangle_plus_error_msg(Rectangle r, Point2D v, Rectangle expected, Rectangle actual) { + std::ostringstream stream; + stream << "Testing expression:\n" + << " r' = r + v" << "\n"; + stream << "Test data:\n" + << " r = " << r << "\n" + << " v = " << v << "\n"; + stream << "Expected result:\n" + << " r' = " << expected << "\n"; + stream << "Actual result:\n" + << " r' = " << actual << "\n"; + return stream.str(); +} + +TEST(RectanglePlusTest, RectanglePlusTest) { + property_test( + [] () { + Rectangle r = generateRectangle(generateArea); + Point2D v = generatePoint(generateArea); + return std::make_tuple(r, v); + }, + [] (std::tuple data) { + Rectangle r; + Point2D v; + std::tie(r, v) = data; + Rectangle expected = Rectangle { + expected::add(r.topLeft, v), + expected::add(r.botRight, v) + }; + Rectangle actual = r + v; + ASSERT_FLOAT_EQ(expected.topLeft.x, actual.topLeft.x) << rectangle_plus_error_msg(r, v, expected, actual); + ASSERT_FLOAT_EQ(expected.topLeft.y, actual.topLeft.y) << rectangle_plus_error_msg(r, v, expected, actual); + ASSERT_FLOAT_EQ(expected.botRight.x, actual.botRight.x) << rectangle_plus_error_msg(r, v, expected, actual); + ASSERT_FLOAT_EQ(expected.botRight.y, actual.botRight.y) << rectangle_plus_error_msg(r, v, expected, actual); + } + ); +} + +std::string rectangle_minus_error_msg(Rectangle r, Point2D v, Rectangle expected, Rectangle actual) { + std::ostringstream stream; + stream << "Testing expression:\n" + << " r' = r - v" << "\n"; + stream << "Test data:\n" + << " r = " << r << "\n" + << " v = " << v << "\n"; + stream << "Expected result:\n" + << " r' = " << expected << "\n"; + stream << "Actual result:\n" + << " r' = " << actual << "\n"; + return stream.str(); +} + +TEST(RectangleMinusTest, RectangleMinusTest) { + property_test( + [] () { + Rectangle r = generateRectangle(generateArea); + Point2D v = generatePoint(generateArea); + return std::make_tuple(r, v); + }, + [] (std::tuple data) { + Rectangle r; + Point2D v; + std::tie(r, v) = data; + Rectangle expected = Rectangle { + expected::add(r.topLeft, { -v.x, -v.y }), + expected::add(r.botRight, { -v.x, -v.y }) + }; + Rectangle actual = r - v; + ASSERT_FLOAT_EQ(expected.topLeft.x, actual.topLeft.x) << rectangle_minus_error_msg(r, v, expected, actual); + ASSERT_FLOAT_EQ(expected.topLeft.y, actual.topLeft.y) << rectangle_minus_error_msg(r, v, expected, actual); + ASSERT_FLOAT_EQ(expected.botRight.x, actual.botRight.x) << rectangle_minus_error_msg(r, v, expected, actual); + ASSERT_FLOAT_EQ(expected.botRight.y, actual.botRight.y) << rectangle_minus_error_msg(r, v, expected, actual); + } + ); +} + +std::string circle_scalar_mul_error_msg(float s, Circle c, Circle expected, Circle actual) { + std::ostringstream stream; + stream << "Testing expression:\n" + << " c' = s * c" << "\n"; + stream << "Test data:\n" + << " s = " << s << "\n" + << " c = " << c << "\n"; + stream << "Expected result:\n" + << " c' = " << expected << "\n"; + stream << "Actual result:\n" + << " c' = " << actual << "\n"; + return stream.str(); +} + +TEST(CircleMulTest, CircleMulTest) { + property_test( + [] () { + float s = generateFloat(MIN, MAX); + Circle c = generateCircle(generateFloat(MIN, MAX), generateArea); + return std::make_tuple(s, c); + }, + [] (std::tuple data) { + float s; + Circle c; + std::tie(s, c) = data; + Circle expected = Circle { c.center, s * c.radius }; + Circle actual = s * c; + ASSERT_FLOAT_EQ(expected.center.x, actual.center.x) << circle_scalar_mul_error_msg(s, c, expected, actual); + ASSERT_FLOAT_EQ(expected.center.y, actual.center.y) << circle_scalar_mul_error_msg(s, c, expected, actual); + ASSERT_FLOAT_EQ(expected.radius, actual.radius) << circle_scalar_mul_error_msg(s, c, expected, actual); + } + ); +} + +std::string rectangle_scalar_mul_error_msg(float s, Rectangle r, Rectangle expected, Rectangle actual) { + std::ostringstream stream; + stream << "Testing expression:\n" + << " r' = s * r" << "\n"; + stream << "Test data:\n" + << " s = " << s << "\n" + << " r = " << r << "\n"; + stream << "Expected result:\n" + << " r' = " << expected << "\n"; + stream << "Actual result:\n" + << " r' = " << actual << "\n"; + return stream.str(); +} + +TEST(RectangleScalarMulTest, RectangleScalarMulTest) { + property_test( + [] () { + float s = generateFloat(MIN, MAX); + Rectangle r = generateRectangle(generateArea); + return std::make_tuple(s, r); + }, + [] (std::tuple data) { + float s; + Rectangle r; + std::tie(s, r) = data; + Rectangle expected = Rectangle { + r.topLeft, + expected::add(r.topLeft, expected::mul(s, { width(r), height(r) })) + }; + Rectangle actual = s * r; + ASSERT_FLOAT_EQ(expected.topLeft.x, actual.topLeft.x) << rectangle_scalar_mul_error_msg(s, r, expected, actual); + ASSERT_FLOAT_EQ(expected.topLeft.y, actual.topLeft.y) << rectangle_scalar_mul_error_msg(s, r, expected, actual); + ASSERT_FLOAT_EQ(expected.botRight.x, actual.botRight.x) << rectangle_scalar_mul_error_msg(s, r, expected, actual); + ASSERT_FLOAT_EQ(expected.botRight.y, actual.botRight.y) << rectangle_scalar_mul_error_msg(s, r, expected, actual); + } + ); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/CMakeLists.txt b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/CMakeLists.txt new file mode 100644 index 0000000..8444dac --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.25) + +project(ObjectOrientedProgramming-ClassesAndObjects-Polymorphism) + +set(SRC + src/main.cpp + src/engine.cpp src/scenes.cpp src/textures.cpp + src/scene.cpp src/statscene.cpp src/dynscene.cpp + src/gobject.cpp src/gobjectlist.cpp src/cgobject.cpp + src/player.cpp src/consumable.cpp src/enemy.cpp + src/collision.cpp src/direction.cpp + src/rectangle.cpp src/point.cpp + src/operators.cpp src/utils.cpp +) + +set(TEST + test/test.cpp) + +add_executable(${PROJECT_NAME}-run ${SRC}) + +configure_test_target(${PROJECT_NAME}-test "${SRC}" ${TEST}) + +prepare_sfml_framework_lesson_task( + "${CMAKE_CURRENT_SOURCE_DIR}/.." + ${PROJECT_NAME}-run + ${PROJECT_NAME}-test +) diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/cgobject.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/cgobject.cpp new file mode 100644 index 0000000..1d49ba3 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/cgobject.cpp @@ -0,0 +1,47 @@ +#include "cgobject.hpp" + +#include "operators.hpp" + +CircleGameObject::CircleGameObject(Circle circle) + : circle(circle) + , status(GameObjectStatus::NORMAL) +{} + +Point2D CircleGameObject::getPosition() const { + return circle.center; +} + +void CircleGameObject::setPosition(Point2D position) { + circle.center = position; +} + +GameObjectStatus CircleGameObject::getStatus() const { + return status; +} + +void CircleGameObject::setStatus(GameObjectStatus newStatus) { + status = newStatus; +} + +Circle CircleGameObject::getCircle() const { + return circle; +} + +Rectangle CircleGameObject::getBoundingBox() const { + Point2D offset = { circle.radius, circle.radius }; + Point2D p1 = circle.center - offset; + Point2D p2 = circle.center + offset; + return createRectangle(p1, p2); +} + +void CircleGameObject::draw(sf::RenderWindow &window, TextureManager& textureManager) const { + const sf::Texture* texture = getTexture(textureManager); + if (texture == nullptr) + return; + sf::CircleShape shape; + shape.setPosition(circle.center.x, circle.center.y); + shape.setOrigin(circle.radius, circle.radius); + shape.setRadius(circle.radius); + shape.setTexture(texture); + window.draw(shape); +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/collision.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/collision.cpp new file mode 100644 index 0000000..9b56990 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/collision.cpp @@ -0,0 +1,21 @@ +#include + +#include "collision.hpp" +#include "cgobject.hpp" +#include "utils.hpp" + +CollisionInfo collisionInfo(const Circle& circle1, const Circle& circle2) { + CollisionInfo info; + info.distance = distance(circle1.center, circle2.center); + info.collide = (info.distance < circle1.radius + circle2.radius); + return info; +} + +CollisionInfo collisionInfo(const GameObject& object1, const GameObject& object2) { + const CircleGameObject* circleObject1 = dynamic_cast(&object1); + const CircleGameObject* circleObject2 = dynamic_cast(&object2); + if (circleObject1 && circleObject2) { + return collisionInfo(circleObject1->getCircle(), circleObject2->getCircle()); + } + assert(false); +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/consumable.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/consumable.cpp new file mode 100644 index 0000000..2d371e4 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/consumable.cpp @@ -0,0 +1,34 @@ +#include "consumable.hpp" + +#include "constants.hpp" + +ConsumableObject::ConsumableObject() + : CircleGameObject({ { CONSUMABLE_START_X, CONSUMABLE_START_Y }, CONSUMABLE_RADIUS }) +{} + +GameObjectKind ConsumableObject::getKind() const { + return GameObjectKind::CONSUMABLE; +} + +Point2D ConsumableObject::getVelocity() const { + return { 0.0f, 0.0f }; +} + +void ConsumableObject::update(sf::Time delta) { + // TODO: write your solution here +} + +void ConsumableObject::onCollision(const GameObject &object, const CollisionInfo &info) { + // TODO: write your solution here +} + +const sf::Texture* ConsumableObject::getTexture(TextureManager& textureManager) const { + switch (getStatus()) { + case GameObjectStatus::NORMAL: + return textureManager.getTexture(GameTextureID::STAR); + case GameObjectStatus::WARNED: + return textureManager.getTexture(GameTextureID::STAR_CONCERNED); + case GameObjectStatus::DESTROYED: + return nullptr; + } +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/direction.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/direction.cpp new file mode 100644 index 0000000..0a2b606 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/direction.cpp @@ -0,0 +1,16 @@ +#include "direction.hpp" + +Point2D getDirection(Direction direction) { + switch (direction) { + case North: + return { 0.0f, -1.0f }; + case East: + return { 1.0f, 0.0f }; + case South: + return { 0.0f, 1.0f }; + case West: + return { -1.0f, 0.0f }; + default: + return { 0.0f, 0.0f }; + } +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/dynscene.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/dynscene.cpp new file mode 100644 index 0000000..1dbcf2e --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/dynscene.cpp @@ -0,0 +1,144 @@ +#include "dynscene.hpp" + +#include "constants.hpp" +#include "player.hpp" +#include "consumable.hpp" +#include "enemy.hpp" +#include "utils.hpp" + +const int MAX_DYNAMIC_OBJECTS_ON_SCENE = 10; +const int MAX_ENEMY_OBJECTS_ON_SCENE = 4; + +const int NEW_DYNAMIC_OBJECT_PROB = 1; +const int NEW_ENEMY_OBJECT_PROB = 10; + +GameplayDynamicScene::GameplayDynamicScene() : Scene(SCENE_WIDTH, SCENE_HEIGHT) {} + +void GameplayDynamicScene::activate() { + addNewGameObject(GameObjectKind::PLAYER); +} + +void GameplayDynamicScene::deactivate() { + // remove all the objects from the list + objects.remove([&] (const GameObject& object) { + return true; + }); +} + +SceneID GameplayDynamicScene::getID() const { + return SceneID::DYNAMIC_GAME_FIELD; +} + +SceneID GameplayDynamicScene::getNextSceneID() const { + return SceneID::DYNAMIC_GAME_FIELD; +} + +void GameplayDynamicScene::processEvent(const sf::Event& event) { + return; +} + +void GameplayDynamicScene::update(sf::Time delta) { + // update the objects' state + objects.foreach([delta] (GameObject& object) { + object.update(delta); + }); + // move objects according to their velocity and elapsed time + objects.foreach([this, delta] (GameObject& object) { + move(object, delta); + }); + // compute collision information for each pair of objects + objects.foreach([this] (GameObject& object) { + objects.foreach([this, &object] (GameObject& other) { + if (&object != &other) { + detectCollision(object, other); + } + }); + }); + // update the list of objects + updateObjectsList(); +} + +void GameplayDynamicScene::updateObjectsList() { + // remove destroyed objects from the list + objects.remove([] (const GameObject& object) { + return (object.getStatus() == GameObjectStatus::DESTROYED) + && (object.getKind() != GameObjectKind::PLAYER); + }); + // count the number of the different kinds of objects present on the scene + int consumableCount = 0; + int enemyCount = 0; + objects.foreach([&] (const GameObject& object) { + switch (object.getKind()) { + case GameObjectKind::CONSUMABLE: + ++consumableCount; + break; + case GameObjectKind::ENEMY: + ++enemyCount; + break; + default: + break; + } + }); + // add new objects of randomly chosen kind if there is enough room for them on the scene + int dynamicObjectsCount = consumableCount + enemyCount; + if (dynamicObjectsCount < MAX_DYNAMIC_OBJECTS_ON_SCENE) { + int r = rand() % 100; + int k = rand() % 100; + if (r < NEW_DYNAMIC_OBJECT_PROB) { + if (k < NEW_ENEMY_OBJECT_PROB && enemyCount < MAX_ENEMY_OBJECTS_ON_SCENE) { + addNewGameObject(GameObjectKind::ENEMY); + } else if (k > NEW_ENEMY_OBJECT_PROB) { + addNewGameObject(GameObjectKind::CONSUMABLE); + } + } + } +} + +std::shared_ptr GameplayDynamicScene::addNewGameObject(GameObjectKind kind) { + std::shared_ptr object; + while (!object) { + // create an object with default position + switch (kind) { + case GameObjectKind::PLAYER: { + object = std::make_shared(); + break; + } + case GameObjectKind::CONSUMABLE: { + object = std::make_shared(); + break; + } + case GameObjectKind::ENEMY: { + object = std::make_shared(); + break; + } + } + // set random position for consumable and enemy objects + if (kind == GameObjectKind::CONSUMABLE || + kind == GameObjectKind::ENEMY) { + setObjectPosition(*object, generatePoint(getBoundingBox())); + } else { + fitInto(*object); + } + // check that object does not collide with existing objects + bool collide = false; + objects.foreach([&collide, &object](GameObject& other) { + collide |= collisionInfo(*object, other).collide; + }); + // reset a colliding object + if (collide) { + object = nullptr; + } + } + objects.insert(object); + return object; +} + +void GameplayDynamicScene::draw(sf::RenderWindow &window, TextureManager& textureManager) { + // draw background + drawBackground(window, textureManager.getTexture(GameTextureID::SPACE)); + // draw all objects on the scene + objects.foreach([&] (const GameObject& object) { + object.draw(window, textureManager); + }); +} + diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/enemy.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/enemy.cpp new file mode 100644 index 0000000..3bb34d8 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/enemy.cpp @@ -0,0 +1,44 @@ +#include "enemy.hpp" + +#include "constants.hpp" +#include "operators.hpp" +#include "utils.hpp" + +EnemyObject::EnemyObject() + : CircleGameObject({ { ENEMY_START_X, ENEMY_START_Y }, ENEMY_RADIUS }) +{} + +GameObjectKind EnemyObject::getKind() const { + return GameObjectKind::ENEMY; +} + +Point2D EnemyObject::getVelocity() const { + return velocity; +} + +void EnemyObject::update(sf::Time delta) { + if (CircleGameObject::getStatus() == GameObjectStatus::DESTROYED) + return; + updateTimer += delta; + if (updateTimer < sf::seconds(1.0f)) + return; + updateTimer = sf::milliseconds(0.0f); + updateVelocity(); +} + +void EnemyObject::setVelocity(Point2D velocity) { + this->velocity = velocity; +} + +void EnemyObject::updateVelocity() { + // TODO: write your solution here +} + +void EnemyObject::onCollision(const GameObject &object, const CollisionInfo &info) { + return; +} + +const sf::Texture* EnemyObject::getTexture(TextureManager& textureManager) const { + // TODO: write your solution here + return nullptr; +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/engine.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/engine.cpp new file mode 100644 index 0000000..c506cdf --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/engine.cpp @@ -0,0 +1,89 @@ +#include "engine.hpp" + +#include "constants.hpp" + +GameEngine *GameEngine::create() { + // TODO: write your solution here + return nullptr; +} + +GameEngine::GameEngine() + : scene(nullptr) + , active(true) +{ + // initialize random number generator + srand(time(nullptr)); + // initialize resource managers + active &= sceneManager.initialize(); + active &= textureManager.initialize(); + if (!active) { + return; + } + // set the current scene + scene = sceneManager.getCurrentScene(); + // initialize the application window + window.create(sf::VideoMode(WINDOW_WIDTH, WINDOW_HEIGHT), "Space Game"); + window.setFramerateLimit(60); +} + +void GameEngine::run() { + sf::Clock clock; + while (isActive()) { + sf::Time delta = clock.restart(); + processInput(); + update(delta); + render(); + sceneTransition(); + } +} + +bool GameEngine::isActive() const { + return active && window.isOpen(); +} + +void GameEngine::close() { + active = false; + window.close(); +} + +void GameEngine::processInput() { + sf::Event event; + while (window.pollEvent(event)) { + processEvent(event); + } +} + +void GameEngine::processEvent(const sf::Event &event) { + switch (event.type) { + case sf::Event::Closed: + close(); + break; + default: + scene->processEvent(event); + break; + } +} + +void GameEngine::update(sf::Time delta) { + scene->update(delta); +} + +void GameEngine::render() { + window.clear(sf::Color::White); + scene->draw(window, textureManager); + window.display(); +} + +void GameEngine::sceneTransition() { + if (scene->getNextSceneID() != scene->getID()) { + sceneManager.transitionScene(scene->getNextSceneID()); + scene = sceneManager.getCurrentScene(); + resizeWindow(); + } +} + +void GameEngine::resizeWindow() { + Rectangle sceneBox = scene->getBoundingBox(); + sf::Vector2u sceneSize = sf::Vector2u(width(sceneBox), height(sceneBox)); + window.setSize(sceneSize); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/gobject.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/gobject.cpp new file mode 100644 index 0000000..a673ef5 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/gobject.cpp @@ -0,0 +1,7 @@ +#include "gobject.hpp" + +#include "operators.hpp" + +void GameObject::move(Point2D vector) { + setPosition(getPosition() + vector); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/gobjectlist.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/gobjectlist.cpp new file mode 100644 index 0000000..5b8b35e --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/gobjectlist.cpp @@ -0,0 +1,54 @@ +#include "gobjectlist.hpp" + +void GameObjectList::link(GameObjectList::Node *cursor, std::unique_ptr &&node) { + // TODO: write your solution here +} + +void GameObjectList::unlink(GameObjectList::Node *node) { + // TODO: write your solution here +} + +GameObjectList::GameObjectList() { + // TODO: write your solution here +} + +void GameObjectList::insert(const std::shared_ptr &object) { + // TODO: write your solution here +} + +void GameObjectList::remove(const std::function &pred) { + GameObjectList::Node* curr = head->next.get(); + while (curr != tail) { + Node* next = curr->next.get(); + if (pred(*curr->object)) { + unlink(curr); + } + curr = next; + } +} + +void GameObjectList::foreach(const std::function& apply) { + Node* curr = head->next.get(); + while (curr != tail) { + apply(*curr->object); + curr = curr->next.get(); + } +} + +GameObjectList::GameObjectList(const GameObjectList &other) : GameObjectList() { + // TODO: write your solution here +} + +GameObjectList::GameObjectList(GameObjectList &&other) noexcept : GameObjectList() { + // TODO: write your solution here +} + +GameObjectList &GameObjectList::operator=(GameObjectList other) { + // TODO: write your solution here + return *this; +} + +void swap(GameObjectList& first, GameObjectList& second) { + using std::swap; + // TODO: write your solution here +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/main.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/main.cpp new file mode 100644 index 0000000..b192818 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/main.cpp @@ -0,0 +1,13 @@ +#include "engine.hpp" + +#include + +int main() { + GameEngine* engine = GameEngine::create(); + if (!engine) { + std::cout << "Game engine is not created!\n"; + return 1; + } + GameEngine::create()->run(); + return 0; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/operators.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/operators.cpp new file mode 100644 index 0000000..4dc9097 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/operators.cpp @@ -0,0 +1,42 @@ +#include "game.hpp" + +Point2D operator+(Point2D a, Point2D b) { + return add(a, b); +} + +Point2D operator-(Point2D a) { + return { -a.x, -a.y }; +} + +Point2D operator-(Point2D a, Point2D b) { + return add(a, -b); +} + +Point2D operator*(float s, Point2D a) { + return mul(s, a); +} + +Circle operator+(Circle c, Point2D v) { + return { c.center + v, c.radius }; +} + +Circle operator-(Circle c, Point2D v) { + return { c.center - v, c.radius }; +} + +Rectangle operator+(Rectangle r, Point2D v) { + return { r.topLeft + v, r.botRight + v }; +} + +Rectangle operator-(Rectangle r, Point2D v) { + return { r.topLeft - v, r.botRight - v }; +} + +Circle operator*(float s, Circle c) { + return { c.center, s * c.radius }; +} + +Rectangle operator*(float s, Rectangle r) { + Point2D v = { width(r), height(r) }; + return { r.topLeft, r.topLeft + s * v }; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/player.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/player.cpp new file mode 100644 index 0000000..452577b --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/player.cpp @@ -0,0 +1,54 @@ +#include "player.hpp" + +#include "constants.hpp" +#include "direction.hpp" +#include "operators.hpp" + +PlayerObject::PlayerObject() + : CircleGameObject({ { PLAYER_START_X, PLAYER_START_Y }, RADIUS }) +{} + +GameObjectKind PlayerObject::getKind() const { + return GameObjectKind::PLAYER; +} + +Point2D PlayerObject::getVelocity() const { + Point2D velocity = { 0.0f, 0.0f }; + if (CircleGameObject::getStatus() == GameObjectStatus::DESTROYED) + return velocity; + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) { + velocity = velocity + getDirection(North); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) { + velocity = velocity + getDirection(East); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) { + velocity = velocity + getDirection(South); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) { + velocity = velocity + getDirection(West); + } + velocity = SPEED * velocity; + return velocity; +} + +void PlayerObject::update(sf::Time delta) { + return; +} + +const sf::Texture* PlayerObject::getTexture(TextureManager& textureManager) const { + switch (getStatus()) { + case GameObjectStatus::NORMAL: + return textureManager.getTexture(GameTextureID::PLANET); + case GameObjectStatus::WARNED: + return textureManager.getTexture(GameTextureID::PLANET); + case GameObjectStatus::DESTROYED: + return textureManager.getTexture(GameTextureID::PLANET_DEAD); + default: + return nullptr; + } +} + +void PlayerObject::onCollision(const GameObject &object, const CollisionInfo &info) { + // TODO: write your solution here +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/point.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/point.cpp new file mode 100644 index 0000000..5c74f69 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/point.cpp @@ -0,0 +1,19 @@ +#include "game.hpp" + +Point2D add(Point2D a, Point2D b) { + Point2D c = { 0, 0 }; + c.x = a.x + b.x; + c.y = a.y + b.y; + return c; +} + +Point2D mul(float s, Point2D a) { + Point2D b = { 0, 0 }; + b.x = s * a.x; + b.y = s * a.y; + return b; +} + +Point2D move(Point2D position, Point2D velocity, float delta) { + return add(position, mul(delta, velocity)); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/rectangle.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/rectangle.cpp new file mode 100644 index 0000000..2b5a1ec --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/rectangle.cpp @@ -0,0 +1,35 @@ +#include "rectangle.hpp" + +#include "operators.hpp" + +Point2D center(const Rectangle& rect) { + // TODO: explain this C++ initialization syntax + return rect.topLeft + 0.5f * Point2D { width(rect), height(rect) }; +} + +Rectangle createRectangle(Point2D p1, Point2D p2) { + Rectangle rect; + rect.topLeft = { std::min(p1.x, p2.x), std::min(p1.y, p2.y) }; + rect.botRight = { std::max(p1.x, p2.x), std::max(p1.y, p2.y) }; + return rect; +} + +Rectangle fitInto(const Rectangle& rect, const Rectangle& intoRect) { + if (width(rect) > width(intoRect) || height(rect) > height(intoRect)) { + return rect; + } + Point2D vector = { 0.0f, 0.0f }; + if (rect.topLeft.x < intoRect.topLeft.x) { + vector.x += intoRect.topLeft.x - rect.topLeft.x; + } + if (rect.topLeft.y < intoRect.topLeft.y) { + vector.y += intoRect.topLeft.y - rect.topLeft.y; + } + if (rect.botRight.x > intoRect.botRight.x) { + vector.x += intoRect.botRight.x - rect.botRight.x; + } + if (rect.botRight.y > intoRect.botRight.y) { + vector.y += intoRect.botRight.y - rect.botRight.y; + } + return rect + vector; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/scene.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/scene.cpp new file mode 100644 index 0000000..81a4470 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/scene.cpp @@ -0,0 +1,45 @@ +#include "scene.hpp" + +#include "operators.hpp" + +Scene::Scene(float width, float height) + : width(width) + , height(height) +{} + +Rectangle Scene::getBoundingBox() const { + Rectangle box; + box.topLeft = { 0.0f, 0.0f }; + box.botRight = { width, height }; + return box; +} + +void Scene::setObjectPosition(GameObject& object, Point2D position) { + // TODO: write your solution here +} + +void Scene::move(GameObject &object, Point2D vector) { + // TODO: write your solution here +} + +void Scene::move(GameObject& object, sf::Time delta) { + move(object, 0.001f * delta.asMilliseconds() * object.getVelocity()); +} + +void Scene::fitInto(GameObject &object) { + Rectangle rect = ::fitInto(object.getBoundingBox(), getBoundingBox()); + object.setPosition(center(rect)); +} + +void Scene::detectCollision(GameObject& object1, GameObject& object2) { + CollisionInfo info = collisionInfo(object1, object2); + object1.onCollision(object2, info); + object2.onCollision(object1, info); +} + +void Scene::drawBackground(sf::RenderWindow &window, const sf::Texture* texture) const { + sf::Sprite background; + background.setTexture(*texture); + background.setTextureRect(sf::IntRect(0, 0, width, height)); + window.draw(background); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/scenes.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/scenes.cpp new file mode 100644 index 0000000..7125bb8 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/scenes.cpp @@ -0,0 +1,14 @@ +#include "scenes.hpp" + +bool SceneManager::initialize() { + staticScene.activate(); + return true; +} + +Scene* SceneManager::getCurrentScene() { + return &staticScene; +} + +void SceneManager::transitionScene(SceneID id) { + return; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/statscene.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/statscene.cpp new file mode 100644 index 0000000..a6abdfa --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/statscene.cpp @@ -0,0 +1,46 @@ +#include "statscene.hpp" + +#include "constants.hpp" + +GameplayStaticScene::GameplayStaticScene() : Scene(SCENE_WIDTH, SCENE_HEIGHT) {} + +void GameplayStaticScene::activate() { + fitInto(player); + fitInto(consumable); + fitInto(enemy); +} + +void GameplayStaticScene::deactivate() { + return; +} + +SceneID GameplayStaticScene::getID() const { + return SceneID::STATIC_GAME_FIELD; +} + +SceneID GameplayStaticScene::getNextSceneID() const { + return SceneID::STATIC_GAME_FIELD; +} + +void GameplayStaticScene::processEvent(const sf::Event& event) { + return; +} + +void GameplayStaticScene::update(sf::Time delta) { + player.update(delta); + consumable.update(delta); + enemy.update(delta); + move(player, delta); + move(consumable, delta); + move(enemy, delta); + detectCollision(player, consumable); + detectCollision(enemy, player); + detectCollision(enemy, consumable); +} + +void GameplayStaticScene::draw(sf::RenderWindow &window, TextureManager& textureManager) { + drawBackground(window, textureManager.getTexture(GameTextureID::SPACE)); + player.draw(window, textureManager); + consumable.draw(window, textureManager); + enemy.draw(window, textureManager); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/textures.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/textures.cpp new file mode 100644 index 0000000..2b5abcc --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/textures.cpp @@ -0,0 +1,42 @@ +#include "textures.hpp" + +#include + +const char* getTextureFilename(GameTextureID id) { + switch (id) { + case GameTextureID::SPACE: + return "resources/space.png"; + case GameTextureID::PLANET: + return "resources/planet.png"; + case GameTextureID::PLANET_DEAD: + return "resources/planetDead.png"; + case GameTextureID::STAR: + return "resources/star.png"; + case GameTextureID::STAR_CONCERNED: + return "resources/starConcerned.png"; + case GameTextureID::BLACKHOLE: + return "resources/blackhole.png"; + default: + return ""; + } +} + +bool TextureManager::initialize() { + for (size_t i = 0; i < SIZE; ++i) { + GameTextureID id = static_cast(i); + const char* filename = getTextureFilename(id); + sf::Texture* texture = &textures[i]; + if (!texture->loadFromFile(filename)) { + std::cerr << "Could not open file " << filename << "\n"; + return false; + } + if (id == GameTextureID::SPACE) { + texture->setRepeated(true); + } + } + return true; +} + +const sf::Texture* TextureManager::getTexture(GameTextureID id) const { + return &textures[static_cast(id)]; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/utils.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/utils.cpp new file mode 100644 index 0000000..783fc7a --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/src/utils.cpp @@ -0,0 +1,51 @@ +#include "utils.hpp" + +#include +#include +#include + +float distance(Point2D a, Point2D b) { + float dx = a.x - b.x; + float dy = a.y - b.y; + return sqrt(dx * dx + dy * dy); +} + +float generateFloat(float min, float max) { + return min + (rand() / (RAND_MAX / (max - min))); +} + +int generateInt(int min, int max) { + return min + rand() % (max - min + 1); +} + +bool generateBool(float prob) { + return generateFloat(0.0f, 1.0f) < prob; +} + +Point2D generatePoint(const Rectangle& boundingBox) { + Point2D point = { 0.0f, 0.0f, }; + point.x = generateFloat(boundingBox.topLeft.x, boundingBox.botRight.x); + point.y = generateFloat(boundingBox.topLeft.y, boundingBox.botRight.y); + return point; +} + +Circle generateCircle(float radius, const Rectangle& boundingBox) { + Circle circle = { { 0.0f, 0.0f }, 0.0f }; + if (radius > std::min(width(boundingBox), height(boundingBox))) { + return circle; + } + circle.center.x = generateFloat( + boundingBox.topLeft.x + radius, + boundingBox.botRight.x - radius + ); + circle.center.y = generateFloat( + boundingBox.topLeft.x + radius, + boundingBox.botRight.x - radius + ); + circle.radius = radius; + return circle; +} + +Rectangle generateRectangle(const Rectangle& boundingBox) { + return createRectangle(generatePoint(boundingBox), generatePoint(boundingBox)); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/task-info.yaml b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/task-info.yaml new file mode 100644 index 0000000..be282b3 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/task-info.yaml @@ -0,0 +1,46 @@ +type: edu +custom_name: Polymorphism +files: +- name: src/player.cpp + visible: true +- name: src/consumable.cpp + visible: true +- name: src/engine.cpp + visible: true +- name: src/scenes.cpp + visible: true +- name: src/textures.cpp + visible: true +- name: src/scene.cpp + visible: true +- name: src/statscene.cpp + visible: true +- name: src/dynscene.cpp + visible: true +- name: src/gobject.cpp + visible: true +- name: src/gobjectlist.cpp + visible: true +- name: src/cgobject.cpp + visible: true +- name: src/enemy.cpp + visible: true +- name: src/collision.cpp + visible: true +- name: src/direction.cpp + visible: true +- name: src/rectangle.cpp + visible: true +- name: src/point.cpp + visible: true +- name: src/operators.cpp + visible: true +- name: src/utils.cpp + visible: true +- name: CMakeLists.txt + visible: false +- name: src/main.cpp + visible: true + editable: false +- name: test/test.cpp + visible: false diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/task-remote-info.yaml b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/task-remote-info.yaml new file mode 100644 index 0000000..bb191d2 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/task-remote-info.yaml @@ -0,0 +1 @@ +id: 656697934 diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/task.md b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/task.md new file mode 100644 index 0000000..9496d44 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/task.md @@ -0,0 +1,107 @@ +Despite the class `CircleGameObject` adding some data fields to the `GameObject` class, +it still leaves some of `GameObject`'s virtual methods unimplemented. +Therefore, this class is still an _abstract class_ — it cannot be instantiated. + +We need to introduce concrete subclasses to implement the behavior of the base class. +Instances of these subclasses can then be used +wherever an instance of the base class is expected. +By substituting the concrete subclass of an object, +we can change the behavior of the program without changing the code +of the functions that use this object! +This ability to treat objects of different classes, which implement different behaviors, +as objects of a common base class is known as _subtype polymorphism_. + +Let us introduce two concrete subclasses of the `CircleGameObject` class — +the `PlayerObject` class and the `ConsumableObject` class, +representing the object controlled by the player and the consumable objects, respectively. +At last, both of these classes implement all the functionality required by the `GameObject` class. + +Please find the declaration of these classes in the `player.hpp` and `consumable.hpp` files. +There are no new syntactic constructs here, so you should be able to understand the code in these files. + +The implementations of these classes can be found in the `player.cpp` and `consumable.cpp` files. +Note that the full implementation of some methods is already provided. +For example, the `getVelocity` method of the `PlayerObject` class computes +the current velocity vector by calling the `SFML` functions +that determine which keys are pressed by the player at the moment. + +Your task is to implement the `getTexture` methods of both classes. +These methods should return the current texture of an object to be displayed, +depending on the current status of the object. +Although we have not yet implemented the methods that actually update +the status of the objects, implementing the `getTexture` methods first +will give you a good opportunity to practice and learn the method call syntax. + +The `getTexture` method takes one argument by reference — an object of the `TextureManager` class. +It is another class predefined by us — it is responsible for loading game-required textures. +A texture pointer can be requested by calling the `getTexture` method of the `TextureManager` class. +It takes the ID of the textures as an argument — these IDs are represented by the `GameTextureID` enum. + +
+ +Note that previously we used the keyword `enum`, +but the `GameTextureID` type is defined with the two keywords: `enum class`. +So, what is the difference between the `enum` and `enum class` declarations? + +In fact, the `enum class` (also known as a _scoped enumeration_) +is a restricted form of the regular `enum`, +introduced in the C++ language to overcome +some of its issues. + +Firstly, in the case of `enum class`, the names of enumeration values are kept +within the scope of the enumeration name. +This way, the enumeration values do not pollute the global scope, +so there is no risk of accidental name clashes. + +Let us see some examples. +For a regular `enum`, the following syntax is used: +```c++ +enum Color { RED, GREEN, BLUE }; +// RED, GREEN, and BLUE are globally accessible names +Color green = GREEN; +``` +Whereas in the case of `enum class`, the enumeration value can only be accessed +through the enumeration name: +```c++ +enum class Color { RED, GREEN, BLUE }; +// RED, GREEN, and BLUE do not pollute the global scope +Color green = Color::GREEN; +``` + +Secondly, `enum class` does not permit implicit conversion to `int`. +For example, the following code compiles just fine: +```c++ +enum Color { RED, GREEN, BLUE }; +// no compilation error, the variable green equals to 1. +int green = GREEN; +``` +However, if `enum class` is used, the code will not compile: +```c++ +enum class Color { RED, GREEN, BLUE }; +// compilation error: no implicit conversion from Color to int. +int green = Color::GREEN; +``` + +
+ +Please implement the `getTexture` methods of `PlayerObject` and `ConsumableObject` +using the following logic: +* under `NORMAL` or `WARNED` status, the player object should have the `PLANET` texture; +* under `DESTROYED` status, the player object should have the `PLANET_DEAD` texture; +* under `NORMAL` status, the consumable object should have the `STAR` texture; +* under `WARNED` status, the consumable object should have the `STAR_CONCERNED` texture; +* under `DESTROYED` status, the consumable object should not be displayed. + +
+ +If you have troubles implementing the last case, +consult the documentation of the `GameObject`'s `getTexture` method. + +
+ +To implement this method, you will need to call the `getTexture` method of the `TextureManager` class. +To do so, use the dot syntax `.` — the same syntax as the one used to access fields of a structure: + +```c++ +const sf::Texture* texture = textureManaged.getTexture(id); +``` \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/test/test.cpp b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/test/test.cpp new file mode 100644 index 0000000..6572680 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/Polymorphism/test/test.cpp @@ -0,0 +1,63 @@ +#include + +#include + +#include "gobject.hpp" +#include "textures.hpp" + +#include "testscene.hpp" +#include "testing.hpp" + +testing::Environment* const env = + testing::AddGlobalTestEnvironment(new TestEnvironment); + +const float MIN = -10e3; +const float MAX = 10e3; + +const Rectangle generateArea = { { MIN, MIN }, { MAX, MAX } }; + +TEST(PlayerObjectTest, GetTextureTest) { + TextureManager textureManager = TextureManager(); + bool init = textureManager.initialize(); + ASSERT_TRUE(init) << "Internal error: failed to initialize texture manager"; + + TestPlayerObject object = TestPlayerObject(); + + object.performSetStatus(GameObjectStatus::NORMAL); + const sf::Texture* normalTexture = object.getTexture(textureManager); + ASSERT_EQ(textureManager.getTexture(GameTextureID::PLANET), normalTexture) + << "Texture of player object in GameObjectStatus::NORMAL status is not equal to GameTextureID::PLANET"; + + object.performSetStatus(GameObjectStatus::WARNED); + const sf::Texture* warnedTexture = object.getTexture(textureManager); + ASSERT_EQ(textureManager.getTexture(GameTextureID::PLANET), warnedTexture) + << "Texture of player object in GameObjectStatus::WARNED status is not equal to GameTextureID::PLANET"; + + object.performSetStatus(GameObjectStatus::DESTROYED); + const sf::Texture* destroyedTexture = object.getTexture(textureManager); + ASSERT_EQ(textureManager.getTexture(GameTextureID::PLANET_DEAD), destroyedTexture) + << "Texture of player object in GameObjectStatus::DESTROYED status is not equal to GameTextureID::PLANET_DEAD"; +} + +TEST(ConsumableObjectTest, GetTextureTest) { + TextureManager textureManager = TextureManager(); + bool init = textureManager.initialize(); + ASSERT_TRUE(init) << "Internal error: failed to initialize texture manager"; + + TestConsumableObject object = TestConsumableObject(); + + object.performSetStatus(GameObjectStatus::NORMAL); + const sf::Texture* normalTexture = object.getTexture(textureManager); + ASSERT_EQ(textureManager.getTexture(GameTextureID::STAR), normalTexture) + << "Texture of consumable object in GameObjectStatus::NORMAL status is not equal to GameTextureID::STAR"; + + object.performSetStatus(GameObjectStatus::WARNED); + const sf::Texture* warnedTexture = object.getTexture(textureManager); + ASSERT_EQ(textureManager.getTexture(GameTextureID::STAR_CONCERNED), warnedTexture) + << "Texture of consumable object in GameObjectStatus::WARNED status is not equal to GameTextureID::STAR_CONCERNED"; + + object.performSetStatus(GameObjectStatus::DESTROYED); + const sf::Texture* destroyedTexture = object.getTexture(textureManager); + ASSERT_EQ(nullptr, destroyedTexture) + << "Texture of consumable object in GameObjectStatus::DESTROYED status is not equal to nullptr"; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/CMakeLists.txt b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/CMakeLists.txt new file mode 100644 index 0000000..0be52bb --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.25) + +project(ObjectOrientedProgramming-ClassesAndObjects-StaticMembers) + +set(SRC + src/main.cpp + src/engine.cpp src/scenes.cpp src/textures.cpp + src/scene.cpp src/statscene.cpp src/dynscene.cpp + src/gobject.cpp src/gobjectlist.cpp src/cgobject.cpp + src/player.cpp src/consumable.cpp src/enemy.cpp + src/collision.cpp src/direction.cpp + src/rectangle.cpp src/point.cpp + src/operators.cpp src/utils.cpp +) + +set(TEST + test/test.cpp) + +add_executable(${PROJECT_NAME}-run ${SRC}) + +configure_test_target(${PROJECT_NAME}-test "${SRC}" ${TEST}) + +prepare_sfml_framework_lesson_task( + "${CMAKE_CURRENT_SOURCE_DIR}/.." + ${PROJECT_NAME}-run + ${PROJECT_NAME}-test +) diff --git a/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/cgobject.cpp b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/cgobject.cpp new file mode 100644 index 0000000..1d49ba3 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/cgobject.cpp @@ -0,0 +1,47 @@ +#include "cgobject.hpp" + +#include "operators.hpp" + +CircleGameObject::CircleGameObject(Circle circle) + : circle(circle) + , status(GameObjectStatus::NORMAL) +{} + +Point2D CircleGameObject::getPosition() const { + return circle.center; +} + +void CircleGameObject::setPosition(Point2D position) { + circle.center = position; +} + +GameObjectStatus CircleGameObject::getStatus() const { + return status; +} + +void CircleGameObject::setStatus(GameObjectStatus newStatus) { + status = newStatus; +} + +Circle CircleGameObject::getCircle() const { + return circle; +} + +Rectangle CircleGameObject::getBoundingBox() const { + Point2D offset = { circle.radius, circle.radius }; + Point2D p1 = circle.center - offset; + Point2D p2 = circle.center + offset; + return createRectangle(p1, p2); +} + +void CircleGameObject::draw(sf::RenderWindow &window, TextureManager& textureManager) const { + const sf::Texture* texture = getTexture(textureManager); + if (texture == nullptr) + return; + sf::CircleShape shape; + shape.setPosition(circle.center.x, circle.center.y); + shape.setOrigin(circle.radius, circle.radius); + shape.setRadius(circle.radius); + shape.setTexture(texture); + window.draw(shape); +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/collision.cpp b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/collision.cpp new file mode 100644 index 0000000..9b56990 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/collision.cpp @@ -0,0 +1,21 @@ +#include + +#include "collision.hpp" +#include "cgobject.hpp" +#include "utils.hpp" + +CollisionInfo collisionInfo(const Circle& circle1, const Circle& circle2) { + CollisionInfo info; + info.distance = distance(circle1.center, circle2.center); + info.collide = (info.distance < circle1.radius + circle2.radius); + return info; +} + +CollisionInfo collisionInfo(const GameObject& object1, const GameObject& object2) { + const CircleGameObject* circleObject1 = dynamic_cast(&object1); + const CircleGameObject* circleObject2 = dynamic_cast(&object2); + if (circleObject1 && circleObject2) { + return collisionInfo(circleObject1->getCircle(), circleObject2->getCircle()); + } + assert(false); +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/consumable.cpp b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/consumable.cpp new file mode 100644 index 0000000..2d371e4 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/consumable.cpp @@ -0,0 +1,34 @@ +#include "consumable.hpp" + +#include "constants.hpp" + +ConsumableObject::ConsumableObject() + : CircleGameObject({ { CONSUMABLE_START_X, CONSUMABLE_START_Y }, CONSUMABLE_RADIUS }) +{} + +GameObjectKind ConsumableObject::getKind() const { + return GameObjectKind::CONSUMABLE; +} + +Point2D ConsumableObject::getVelocity() const { + return { 0.0f, 0.0f }; +} + +void ConsumableObject::update(sf::Time delta) { + // TODO: write your solution here +} + +void ConsumableObject::onCollision(const GameObject &object, const CollisionInfo &info) { + // TODO: write your solution here +} + +const sf::Texture* ConsumableObject::getTexture(TextureManager& textureManager) const { + switch (getStatus()) { + case GameObjectStatus::NORMAL: + return textureManager.getTexture(GameTextureID::STAR); + case GameObjectStatus::WARNED: + return textureManager.getTexture(GameTextureID::STAR_CONCERNED); + case GameObjectStatus::DESTROYED: + return nullptr; + } +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/direction.cpp b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/direction.cpp new file mode 100644 index 0000000..0a2b606 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/direction.cpp @@ -0,0 +1,16 @@ +#include "direction.hpp" + +Point2D getDirection(Direction direction) { + switch (direction) { + case North: + return { 0.0f, -1.0f }; + case East: + return { 1.0f, 0.0f }; + case South: + return { 0.0f, 1.0f }; + case West: + return { -1.0f, 0.0f }; + default: + return { 0.0f, 0.0f }; + } +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/dynscene.cpp b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/dynscene.cpp new file mode 100644 index 0000000..1dbcf2e --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/dynscene.cpp @@ -0,0 +1,144 @@ +#include "dynscene.hpp" + +#include "constants.hpp" +#include "player.hpp" +#include "consumable.hpp" +#include "enemy.hpp" +#include "utils.hpp" + +const int MAX_DYNAMIC_OBJECTS_ON_SCENE = 10; +const int MAX_ENEMY_OBJECTS_ON_SCENE = 4; + +const int NEW_DYNAMIC_OBJECT_PROB = 1; +const int NEW_ENEMY_OBJECT_PROB = 10; + +GameplayDynamicScene::GameplayDynamicScene() : Scene(SCENE_WIDTH, SCENE_HEIGHT) {} + +void GameplayDynamicScene::activate() { + addNewGameObject(GameObjectKind::PLAYER); +} + +void GameplayDynamicScene::deactivate() { + // remove all the objects from the list + objects.remove([&] (const GameObject& object) { + return true; + }); +} + +SceneID GameplayDynamicScene::getID() const { + return SceneID::DYNAMIC_GAME_FIELD; +} + +SceneID GameplayDynamicScene::getNextSceneID() const { + return SceneID::DYNAMIC_GAME_FIELD; +} + +void GameplayDynamicScene::processEvent(const sf::Event& event) { + return; +} + +void GameplayDynamicScene::update(sf::Time delta) { + // update the objects' state + objects.foreach([delta] (GameObject& object) { + object.update(delta); + }); + // move objects according to their velocity and elapsed time + objects.foreach([this, delta] (GameObject& object) { + move(object, delta); + }); + // compute collision information for each pair of objects + objects.foreach([this] (GameObject& object) { + objects.foreach([this, &object] (GameObject& other) { + if (&object != &other) { + detectCollision(object, other); + } + }); + }); + // update the list of objects + updateObjectsList(); +} + +void GameplayDynamicScene::updateObjectsList() { + // remove destroyed objects from the list + objects.remove([] (const GameObject& object) { + return (object.getStatus() == GameObjectStatus::DESTROYED) + && (object.getKind() != GameObjectKind::PLAYER); + }); + // count the number of the different kinds of objects present on the scene + int consumableCount = 0; + int enemyCount = 0; + objects.foreach([&] (const GameObject& object) { + switch (object.getKind()) { + case GameObjectKind::CONSUMABLE: + ++consumableCount; + break; + case GameObjectKind::ENEMY: + ++enemyCount; + break; + default: + break; + } + }); + // add new objects of randomly chosen kind if there is enough room for them on the scene + int dynamicObjectsCount = consumableCount + enemyCount; + if (dynamicObjectsCount < MAX_DYNAMIC_OBJECTS_ON_SCENE) { + int r = rand() % 100; + int k = rand() % 100; + if (r < NEW_DYNAMIC_OBJECT_PROB) { + if (k < NEW_ENEMY_OBJECT_PROB && enemyCount < MAX_ENEMY_OBJECTS_ON_SCENE) { + addNewGameObject(GameObjectKind::ENEMY); + } else if (k > NEW_ENEMY_OBJECT_PROB) { + addNewGameObject(GameObjectKind::CONSUMABLE); + } + } + } +} + +std::shared_ptr GameplayDynamicScene::addNewGameObject(GameObjectKind kind) { + std::shared_ptr object; + while (!object) { + // create an object with default position + switch (kind) { + case GameObjectKind::PLAYER: { + object = std::make_shared(); + break; + } + case GameObjectKind::CONSUMABLE: { + object = std::make_shared(); + break; + } + case GameObjectKind::ENEMY: { + object = std::make_shared(); + break; + } + } + // set random position for consumable and enemy objects + if (kind == GameObjectKind::CONSUMABLE || + kind == GameObjectKind::ENEMY) { + setObjectPosition(*object, generatePoint(getBoundingBox())); + } else { + fitInto(*object); + } + // check that object does not collide with existing objects + bool collide = false; + objects.foreach([&collide, &object](GameObject& other) { + collide |= collisionInfo(*object, other).collide; + }); + // reset a colliding object + if (collide) { + object = nullptr; + } + } + objects.insert(object); + return object; +} + +void GameplayDynamicScene::draw(sf::RenderWindow &window, TextureManager& textureManager) { + // draw background + drawBackground(window, textureManager.getTexture(GameTextureID::SPACE)); + // draw all objects on the scene + objects.foreach([&] (const GameObject& object) { + object.draw(window, textureManager); + }); +} + diff --git a/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/enemy.cpp b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/enemy.cpp new file mode 100644 index 0000000..3bb34d8 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/enemy.cpp @@ -0,0 +1,44 @@ +#include "enemy.hpp" + +#include "constants.hpp" +#include "operators.hpp" +#include "utils.hpp" + +EnemyObject::EnemyObject() + : CircleGameObject({ { ENEMY_START_X, ENEMY_START_Y }, ENEMY_RADIUS }) +{} + +GameObjectKind EnemyObject::getKind() const { + return GameObjectKind::ENEMY; +} + +Point2D EnemyObject::getVelocity() const { + return velocity; +} + +void EnemyObject::update(sf::Time delta) { + if (CircleGameObject::getStatus() == GameObjectStatus::DESTROYED) + return; + updateTimer += delta; + if (updateTimer < sf::seconds(1.0f)) + return; + updateTimer = sf::milliseconds(0.0f); + updateVelocity(); +} + +void EnemyObject::setVelocity(Point2D velocity) { + this->velocity = velocity; +} + +void EnemyObject::updateVelocity() { + // TODO: write your solution here +} + +void EnemyObject::onCollision(const GameObject &object, const CollisionInfo &info) { + return; +} + +const sf::Texture* EnemyObject::getTexture(TextureManager& textureManager) const { + // TODO: write your solution here + return nullptr; +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/engine.cpp b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/engine.cpp new file mode 100644 index 0000000..32ca01d --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/engine.cpp @@ -0,0 +1,89 @@ +#include "engine.hpp" + +#include "constants.hpp" + +GameEngine *GameEngine::create() { + static GameEngine engine; + return &engine; +} + +GameEngine::GameEngine() + : scene(nullptr) + , active(true) +{ + // initialize random number generator + srand(time(nullptr)); + // initialize resource managers + active &= sceneManager.initialize(); + active &= textureManager.initialize(); + if (!active) { + return; + } + // set the current scene + scene = sceneManager.getCurrentScene(); + // initialize the application window + window.create(sf::VideoMode(WINDOW_WIDTH, WINDOW_HEIGHT), "Space Game"); + window.setFramerateLimit(60); +} + +void GameEngine::run() { + sf::Clock clock; + while (isActive()) { + sf::Time delta = clock.restart(); + processInput(); + update(delta); + render(); + sceneTransition(); + } +} + +bool GameEngine::isActive() const { + return active && window.isOpen(); +} + +void GameEngine::close() { + active = false; + window.close(); +} + +void GameEngine::processInput() { + sf::Event event; + while (window.pollEvent(event)) { + processEvent(event); + } +} + +void GameEngine::processEvent(const sf::Event &event) { + switch (event.type) { + case sf::Event::Closed: + close(); + break; + default: + scene->processEvent(event); + break; + } +} + +void GameEngine::update(sf::Time delta) { + scene->update(delta); +} + +void GameEngine::render() { + window.clear(sf::Color::White); + scene->draw(window, textureManager); + window.display(); +} + +void GameEngine::sceneTransition() { + if (scene->getNextSceneID() != scene->getID()) { + sceneManager.transitionScene(scene->getNextSceneID()); + scene = sceneManager.getCurrentScene(); + resizeWindow(); + } +} + +void GameEngine::resizeWindow() { + Rectangle sceneBox = scene->getBoundingBox(); + sf::Vector2u sceneSize = sf::Vector2u(width(sceneBox), height(sceneBox)); + window.setSize(sceneSize); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/gobject.cpp b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/gobject.cpp new file mode 100644 index 0000000..a673ef5 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/gobject.cpp @@ -0,0 +1,7 @@ +#include "gobject.hpp" + +#include "operators.hpp" + +void GameObject::move(Point2D vector) { + setPosition(getPosition() + vector); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/gobjectlist.cpp b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/gobjectlist.cpp new file mode 100644 index 0000000..5b8b35e --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/gobjectlist.cpp @@ -0,0 +1,54 @@ +#include "gobjectlist.hpp" + +void GameObjectList::link(GameObjectList::Node *cursor, std::unique_ptr &&node) { + // TODO: write your solution here +} + +void GameObjectList::unlink(GameObjectList::Node *node) { + // TODO: write your solution here +} + +GameObjectList::GameObjectList() { + // TODO: write your solution here +} + +void GameObjectList::insert(const std::shared_ptr &object) { + // TODO: write your solution here +} + +void GameObjectList::remove(const std::function &pred) { + GameObjectList::Node* curr = head->next.get(); + while (curr != tail) { + Node* next = curr->next.get(); + if (pred(*curr->object)) { + unlink(curr); + } + curr = next; + } +} + +void GameObjectList::foreach(const std::function& apply) { + Node* curr = head->next.get(); + while (curr != tail) { + apply(*curr->object); + curr = curr->next.get(); + } +} + +GameObjectList::GameObjectList(const GameObjectList &other) : GameObjectList() { + // TODO: write your solution here +} + +GameObjectList::GameObjectList(GameObjectList &&other) noexcept : GameObjectList() { + // TODO: write your solution here +} + +GameObjectList &GameObjectList::operator=(GameObjectList other) { + // TODO: write your solution here + return *this; +} + +void swap(GameObjectList& first, GameObjectList& second) { + using std::swap; + // TODO: write your solution here +} diff --git a/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/main.cpp b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/main.cpp new file mode 100644 index 0000000..b192818 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/main.cpp @@ -0,0 +1,13 @@ +#include "engine.hpp" + +#include + +int main() { + GameEngine* engine = GameEngine::create(); + if (!engine) { + std::cout << "Game engine is not created!\n"; + return 1; + } + GameEngine::create()->run(); + return 0; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/operators.cpp b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/operators.cpp new file mode 100644 index 0000000..4dc9097 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/operators.cpp @@ -0,0 +1,42 @@ +#include "game.hpp" + +Point2D operator+(Point2D a, Point2D b) { + return add(a, b); +} + +Point2D operator-(Point2D a) { + return { -a.x, -a.y }; +} + +Point2D operator-(Point2D a, Point2D b) { + return add(a, -b); +} + +Point2D operator*(float s, Point2D a) { + return mul(s, a); +} + +Circle operator+(Circle c, Point2D v) { + return { c.center + v, c.radius }; +} + +Circle operator-(Circle c, Point2D v) { + return { c.center - v, c.radius }; +} + +Rectangle operator+(Rectangle r, Point2D v) { + return { r.topLeft + v, r.botRight + v }; +} + +Rectangle operator-(Rectangle r, Point2D v) { + return { r.topLeft - v, r.botRight - v }; +} + +Circle operator*(float s, Circle c) { + return { c.center, s * c.radius }; +} + +Rectangle operator*(float s, Rectangle r) { + Point2D v = { width(r), height(r) }; + return { r.topLeft, r.topLeft + s * v }; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/player.cpp b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/player.cpp new file mode 100644 index 0000000..452577b --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/player.cpp @@ -0,0 +1,54 @@ +#include "player.hpp" + +#include "constants.hpp" +#include "direction.hpp" +#include "operators.hpp" + +PlayerObject::PlayerObject() + : CircleGameObject({ { PLAYER_START_X, PLAYER_START_Y }, RADIUS }) +{} + +GameObjectKind PlayerObject::getKind() const { + return GameObjectKind::PLAYER; +} + +Point2D PlayerObject::getVelocity() const { + Point2D velocity = { 0.0f, 0.0f }; + if (CircleGameObject::getStatus() == GameObjectStatus::DESTROYED) + return velocity; + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) { + velocity = velocity + getDirection(North); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) { + velocity = velocity + getDirection(East); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) { + velocity = velocity + getDirection(South); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) { + velocity = velocity + getDirection(West); + } + velocity = SPEED * velocity; + return velocity; +} + +void PlayerObject::update(sf::Time delta) { + return; +} + +const sf::Texture* PlayerObject::getTexture(TextureManager& textureManager) const { + switch (getStatus()) { + case GameObjectStatus::NORMAL: + return textureManager.getTexture(GameTextureID::PLANET); + case GameObjectStatus::WARNED: + return textureManager.getTexture(GameTextureID::PLANET); + case GameObjectStatus::DESTROYED: + return textureManager.getTexture(GameTextureID::PLANET_DEAD); + default: + return nullptr; + } +} + +void PlayerObject::onCollision(const GameObject &object, const CollisionInfo &info) { + // TODO: write your solution here +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/point.cpp b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/point.cpp new file mode 100644 index 0000000..5c74f69 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/point.cpp @@ -0,0 +1,19 @@ +#include "game.hpp" + +Point2D add(Point2D a, Point2D b) { + Point2D c = { 0, 0 }; + c.x = a.x + b.x; + c.y = a.y + b.y; + return c; +} + +Point2D mul(float s, Point2D a) { + Point2D b = { 0, 0 }; + b.x = s * a.x; + b.y = s * a.y; + return b; +} + +Point2D move(Point2D position, Point2D velocity, float delta) { + return add(position, mul(delta, velocity)); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/rectangle.cpp b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/rectangle.cpp new file mode 100644 index 0000000..2b5a1ec --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/rectangle.cpp @@ -0,0 +1,35 @@ +#include "rectangle.hpp" + +#include "operators.hpp" + +Point2D center(const Rectangle& rect) { + // TODO: explain this C++ initialization syntax + return rect.topLeft + 0.5f * Point2D { width(rect), height(rect) }; +} + +Rectangle createRectangle(Point2D p1, Point2D p2) { + Rectangle rect; + rect.topLeft = { std::min(p1.x, p2.x), std::min(p1.y, p2.y) }; + rect.botRight = { std::max(p1.x, p2.x), std::max(p1.y, p2.y) }; + return rect; +} + +Rectangle fitInto(const Rectangle& rect, const Rectangle& intoRect) { + if (width(rect) > width(intoRect) || height(rect) > height(intoRect)) { + return rect; + } + Point2D vector = { 0.0f, 0.0f }; + if (rect.topLeft.x < intoRect.topLeft.x) { + vector.x += intoRect.topLeft.x - rect.topLeft.x; + } + if (rect.topLeft.y < intoRect.topLeft.y) { + vector.y += intoRect.topLeft.y - rect.topLeft.y; + } + if (rect.botRight.x > intoRect.botRight.x) { + vector.x += intoRect.botRight.x - rect.botRight.x; + } + if (rect.botRight.y > intoRect.botRight.y) { + vector.y += intoRect.botRight.y - rect.botRight.y; + } + return rect + vector; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/scene.cpp b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/scene.cpp new file mode 100644 index 0000000..1457361 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/scene.cpp @@ -0,0 +1,47 @@ +#include "scene.hpp" + +#include "operators.hpp" + +Scene::Scene(float width, float height) + : width(width) + , height(height) +{} + +Rectangle Scene::getBoundingBox() const { + Rectangle box; + box.topLeft = { 0.0f, 0.0f }; + box.botRight = { width, height }; + return box; +} + +void Scene::setObjectPosition(GameObject& object, Point2D position) { + object.setPosition(position); + fitInto(object); +} + +void Scene::move(GameObject &object, Point2D vector) { + object.move(vector); + fitInto(object); +} + +void Scene::move(GameObject& object, sf::Time delta) { + move(object, 0.001f * delta.asMilliseconds() * object.getVelocity()); +} + +void Scene::fitInto(GameObject &object) { + Rectangle rect = ::fitInto(object.getBoundingBox(), getBoundingBox()); + object.setPosition(center(rect)); +} + +void Scene::detectCollision(GameObject& object1, GameObject& object2) { + CollisionInfo info = collisionInfo(object1, object2); + object1.onCollision(object2, info); + object2.onCollision(object1, info); +} + +void Scene::drawBackground(sf::RenderWindow &window, const sf::Texture* texture) const { + sf::Sprite background; + background.setTexture(*texture); + background.setTextureRect(sf::IntRect(0, 0, width, height)); + window.draw(background); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/scenes.cpp b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/scenes.cpp new file mode 100644 index 0000000..7125bb8 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/scenes.cpp @@ -0,0 +1,14 @@ +#include "scenes.hpp" + +bool SceneManager::initialize() { + staticScene.activate(); + return true; +} + +Scene* SceneManager::getCurrentScene() { + return &staticScene; +} + +void SceneManager::transitionScene(SceneID id) { + return; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/statscene.cpp b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/statscene.cpp new file mode 100644 index 0000000..a6abdfa --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/statscene.cpp @@ -0,0 +1,46 @@ +#include "statscene.hpp" + +#include "constants.hpp" + +GameplayStaticScene::GameplayStaticScene() : Scene(SCENE_WIDTH, SCENE_HEIGHT) {} + +void GameplayStaticScene::activate() { + fitInto(player); + fitInto(consumable); + fitInto(enemy); +} + +void GameplayStaticScene::deactivate() { + return; +} + +SceneID GameplayStaticScene::getID() const { + return SceneID::STATIC_GAME_FIELD; +} + +SceneID GameplayStaticScene::getNextSceneID() const { + return SceneID::STATIC_GAME_FIELD; +} + +void GameplayStaticScene::processEvent(const sf::Event& event) { + return; +} + +void GameplayStaticScene::update(sf::Time delta) { + player.update(delta); + consumable.update(delta); + enemy.update(delta); + move(player, delta); + move(consumable, delta); + move(enemy, delta); + detectCollision(player, consumable); + detectCollision(enemy, player); + detectCollision(enemy, consumable); +} + +void GameplayStaticScene::draw(sf::RenderWindow &window, TextureManager& textureManager) { + drawBackground(window, textureManager.getTexture(GameTextureID::SPACE)); + player.draw(window, textureManager); + consumable.draw(window, textureManager); + enemy.draw(window, textureManager); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/textures.cpp b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/textures.cpp new file mode 100644 index 0000000..2b5abcc --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/textures.cpp @@ -0,0 +1,42 @@ +#include "textures.hpp" + +#include + +const char* getTextureFilename(GameTextureID id) { + switch (id) { + case GameTextureID::SPACE: + return "resources/space.png"; + case GameTextureID::PLANET: + return "resources/planet.png"; + case GameTextureID::PLANET_DEAD: + return "resources/planetDead.png"; + case GameTextureID::STAR: + return "resources/star.png"; + case GameTextureID::STAR_CONCERNED: + return "resources/starConcerned.png"; + case GameTextureID::BLACKHOLE: + return "resources/blackhole.png"; + default: + return ""; + } +} + +bool TextureManager::initialize() { + for (size_t i = 0; i < SIZE; ++i) { + GameTextureID id = static_cast(i); + const char* filename = getTextureFilename(id); + sf::Texture* texture = &textures[i]; + if (!texture->loadFromFile(filename)) { + std::cerr << "Could not open file " << filename << "\n"; + return false; + } + if (id == GameTextureID::SPACE) { + texture->setRepeated(true); + } + } + return true; +} + +const sf::Texture* TextureManager::getTexture(GameTextureID id) const { + return &textures[static_cast(id)]; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/utils.cpp b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/utils.cpp new file mode 100644 index 0000000..783fc7a --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/src/utils.cpp @@ -0,0 +1,51 @@ +#include "utils.hpp" + +#include +#include +#include + +float distance(Point2D a, Point2D b) { + float dx = a.x - b.x; + float dy = a.y - b.y; + return sqrt(dx * dx + dy * dy); +} + +float generateFloat(float min, float max) { + return min + (rand() / (RAND_MAX / (max - min))); +} + +int generateInt(int min, int max) { + return min + rand() % (max - min + 1); +} + +bool generateBool(float prob) { + return generateFloat(0.0f, 1.0f) < prob; +} + +Point2D generatePoint(const Rectangle& boundingBox) { + Point2D point = { 0.0f, 0.0f, }; + point.x = generateFloat(boundingBox.topLeft.x, boundingBox.botRight.x); + point.y = generateFloat(boundingBox.topLeft.y, boundingBox.botRight.y); + return point; +} + +Circle generateCircle(float radius, const Rectangle& boundingBox) { + Circle circle = { { 0.0f, 0.0f }, 0.0f }; + if (radius > std::min(width(boundingBox), height(boundingBox))) { + return circle; + } + circle.center.x = generateFloat( + boundingBox.topLeft.x + radius, + boundingBox.botRight.x - radius + ); + circle.center.y = generateFloat( + boundingBox.topLeft.x + radius, + boundingBox.botRight.x - radius + ); + circle.radius = radius; + return circle; +} + +Rectangle generateRectangle(const Rectangle& boundingBox) { + return createRectangle(generatePoint(boundingBox), generatePoint(boundingBox)); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/task-info.yaml b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/task-info.yaml new file mode 100644 index 0000000..7d93bfb --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/task-info.yaml @@ -0,0 +1,46 @@ +type: edu +custom_name: Static Members +files: +- name: src/main.cpp + visible: true + editable: false +- name: src/scenes.cpp + visible: true +- name: src/textures.cpp + visible: true +- name: src/scene.cpp + visible: true +- name: src/statscene.cpp + visible: true +- name: src/dynscene.cpp + visible: true +- name: src/gobject.cpp + visible: true +- name: src/gobjectlist.cpp + visible: true +- name: src/cgobject.cpp + visible: true +- name: src/player.cpp + visible: true +- name: src/consumable.cpp + visible: true +- name: src/enemy.cpp + visible: true +- name: src/collision.cpp + visible: true +- name: src/direction.cpp + visible: true +- name: src/rectangle.cpp + visible: true +- name: src/point.cpp + visible: true +- name: src/operators.cpp + visible: true +- name: src/utils.cpp + visible: true +- name: CMakeLists.txt + visible: false +- name: src/engine.cpp + visible: true +- name: test/test.cpp + visible: false diff --git a/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/task-remote-info.yaml b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/task-remote-info.yaml new file mode 100644 index 0000000..adac39b --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/task-remote-info.yaml @@ -0,0 +1 @@ +id: 1527203786 diff --git a/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/task.md b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/task.md new file mode 100644 index 0000000..6da886b --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/task.md @@ -0,0 +1,94 @@ +The `Scene` class is also an _abstract class_ — +it has pure virtual methods and thus cannot be instantiated. +This gives us the flexibility of having different implementations of the `Scene` class. + +One such implementation is provided by the `GameplayStaticScene` subclass +(see files `statscene.hpp` and `statscene.cpp`). +This scene implementation is called static because it contains only +a static predefined number of game objects: +one player object, one consumable object, and one enemy object, +which we will cover later in this module. +This is certainly a downgrade from our previous version of the game implementation, +where we learned how to create objects dynamically with the help of linked lists. +Do not worry, we will restore this feature soon. +But for now, let us work with the static scene. + +While the `Scene` class is very important and does a lot of work, +like maintaining game objects and drawing scenes, +it is not responsible for managing the game itself. +This responsibility lies with another class — `GameEngine` +(see files `engine.hpp` and `engine.cpp`). +This class, in particular, controls the application window and the currently active scene. + +
+ +Another important responsibility of the `GameEngine` class is to perform scene transitions. +For now, this functionality might not be important for us, +since we will initially have only one single scene (the gameplay scene). +However, it will become useful later in this course as we start to implement other types of scenes — +for example, a scene managing the main menu of the game. + +
+ +The most important method of the `GameEngine` class is the `run()` method, +which implements the main loop of the game. +In essence, the entry point of our game application is just +creating a `GameEngine` object and calling its `run()` method (see file `main.cpp`). + +Speaking of creating the `GameEngine` object, +the `GameEngine` class has a certain peculiarity compared to the other classes we have seen so far — +there can only exist a single unique `GameEngine` object instance per game instance. +We can express this in the code with the help of another C++ feature: the `static` modifier. + +First, note that the `GameEngine` class has one method that stands out from the others: +the `create()` method, which has a `static` modifier in front of it. +The `static` modifier, when applied to a class member (either a field or a method), +turns this member into a _static_ member. + +Static members are not associated with objects, rather they are associated with the class itself. +This means that in order to access a static member, you do not need an instance of the class at hand. +Instead, static members are accessed through the class name: + +```c++ +// obtains an engine instance by calling the static method `create` +GameEngine* engine = GameEngine::create(); +``` + +Static members provide a convenient way to associate some methods or data fields with the class itself. +For example, the `create` method shown above provides the ability to instantiate the game engine object. + +When applied to the declaration of local variables inside functions, +the `static` modifier has a different meaning. +It allows creating a _static variable_ that retains its value between function calls. +Such variables are actually stored inside the static memory region of the program, +instead of the stack memory region, where other local variables of the function reside +(hence the name _static_). + +```c++ +int foo() { + static int x = 0; + return ++x; +} + +// prints 1 +std::cout << foo() << std::endl; +// prints 2 +std::cout << foo() << std::endl; +``` + +With the help of the `static` modifier, it becomes possible to +ensure that only one engine is created per each game run. +To achieve this, it is sufficient to declare a static `GameEngine` variable +inside the `GameEngine::create` method and return a pointer to this variable. + +
+ +Note that in this case, the address escape error does not occur. +Because the `static` variable resides in the static memory region, it lives throughout the whole program execution time. +Thus, it is safe to return the address of this variable from the function. + +
+ +Please implement the `create` method as described above. +If you do this correctly, you will finally be able to run the refactored game application +and see the planet and star objects on the screen. diff --git a/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/test/test.cpp b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/test/test.cpp new file mode 100644 index 0000000..fc4de50 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/StaticMembers/test/test.cpp @@ -0,0 +1,23 @@ +#include + +#include + +#include "engine.hpp" + +#include "testing.hpp" + +testing::Environment* const env = + testing::AddGlobalTestEnvironment(new TestEnvironment); + +TEST(EngineTest, CreateTest) { + GameEngine* engine = GameEngine::create(); + + ASSERT_TRUE(engine != nullptr) + << "GameEngine::create() returns nullptr!"; + + ASSERT_TRUE(engine == GameEngine::create()) + << "GameEngine::create() returns pointer to non-static memory!"; + + ASSERT_TRUE(engine->isActive()) + << "GameEngine::create() returns pointer to a non-initialized engine object!"; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/lesson-info.yaml b/ObjectOrientedProgramming/ClassesAndObjects/lesson-info.yaml new file mode 100644 index 0000000..2fb38f1 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/lesson-info.yaml @@ -0,0 +1,14 @@ +type: framework +custom_name: Classes And Objects +content: +- Introduction +- OperatorsOverloading +- IntroducingObjects +- Inheritance +- Polymorphism +- Encapsulation +- StaticMembers +- CollisionsRevisited +- NewChallenge +- NewDynamics +is_template_based: false \ No newline at end of file diff --git a/ObjectOrientedProgramming/ClassesAndObjects/lesson-remote-info.yaml b/ObjectOrientedProgramming/ClassesAndObjects/lesson-remote-info.yaml new file mode 100644 index 0000000..f2f1c13 --- /dev/null +++ b/ObjectOrientedProgramming/ClassesAndObjects/lesson-remote-info.yaml @@ -0,0 +1 @@ +id: 1785975432 diff --git a/ObjectOrientedProgramming/MemoryOwnership/CopyConstructor/CMakeLists.txt b/ObjectOrientedProgramming/MemoryOwnership/CopyConstructor/CMakeLists.txt new file mode 100644 index 0000000..88acb9a --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/CopyConstructor/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.27) + +project(ObjectOrientedProgramming-MemoryOwnership-CopyConstructor) + +set(CMAKE_CXX_STANDARD 14) + +# Files from `./src` directory +set(SRC src/main.cpp) + + +# Running learner side code +# Use PROJECT_NAME dependent names of targets for the plugin support to work correctly. +add_executable(${PROJECT_NAME}-run ${SRC}) \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/CopyConstructor/include/int_array.hpp b/ObjectOrientedProgramming/MemoryOwnership/CopyConstructor/include/int_array.hpp new file mode 100644 index 0000000..9436052 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/CopyConstructor/include/int_array.hpp @@ -0,0 +1,78 @@ +#ifndef CPPBASICS_INT_ARRAY_HPP +#define CPPBASICS_INT_ARRAY_HPP + +#include +#include + +class int_array { +public: + int_array() + : data_(nullptr) + , size_(0) + {}; + + explicit int_array(std::size_t size) + : data_(new int[size]) + , size_(size) + {}; + + ~int_array() { + delete[] data_; + } + + int_array(const int_array& other) + : data_(new int[other.size_]) + , size_(other.size_) + { + for (size_t i = 0; i < size_; ++i) { + data_[i] = other.data_[i]; + } + }; + + int_array& operator=(const int_array& other) { + if (this == &other) { + return *this; + } + delete[] data_; + data_ = new int[other.size_]; + size_ = other.size_; + for (size_t i = 0; i < size_; ++i) { + data_[i] = other.data_[i]; + } + return *this; + } + + std::size_t size() const { + return size_; + } + + int& operator[](std::size_t i) { + return data_[i]; + } + + const int& operator[](std::size_t i) const { + return data_[i]; + } + + friend std::ostream& operator<<(std::ostream &os, const int_array& array) { + if (array.size() == 0) { + os << "[]"; + return os; + } + os << "[ "; + for (std::size_t i = 0; i < array.size(); ++i) { + os << array[i]; + if (i != array.size() - 1) { + os << ", "; + } + } + os << " ]"; + return os; + } + +private: + int* data_; + size_t size_; +}; + +#endif // CPPBASICS_INT_ARRAY_HPP diff --git a/ObjectOrientedProgramming/MemoryOwnership/CopyConstructor/src/main.cpp b/ObjectOrientedProgramming/MemoryOwnership/CopyConstructor/src/main.cpp new file mode 100644 index 0000000..163c688 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/CopyConstructor/src/main.cpp @@ -0,0 +1,41 @@ +#include "../include/int_array.hpp" + +int main() { + + std::cout << "create a non-empty array 'a':\n"; + int_array a = int_array(10); + for (size_t i = 0; i < a.size(); ++i) { + a[i] = (int) i + 1; + } + std::cout << "a = " << a << "\n"; + std::cout << "\n"; + + std::cout << "create a copy 'b' of array 'a':\n"; + int_array b(a); + std::cout << "b = " << b << "\n"; + std::cout << "\n"; + + std::cout << "modify 'b', check that the original 'a' is left unchanged:\n"; + for (size_t i = 0; i < b.size(); ++i) { + b[i] = -b[i]; + } + std::cout << "a = " << a << "\n"; + std::cout << "b = " << b << "\n"; + std::cout << "\n"; + + std::cout << "create another non-empty array 'c':\n"; + int_array c = int_array(4); + for (size_t i = 0; i < c.size(); ++i) { + c[i] = 0; + } + std::cout << "c = " << c << "\n"; + std::cout << "\n"; + + std::cout << "re-assign 'c' to the copy of 'a':\n"; + c = a; + std::cout << "a = " << a << "\n"; + std::cout << "c = " << c << "\n"; + std::cout << "\n"; + + return 0; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/CopyConstructor/task-info.yaml b/ObjectOrientedProgramming/MemoryOwnership/CopyConstructor/task-info.yaml new file mode 100644 index 0000000..8ce2a1b --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/CopyConstructor/task-info.yaml @@ -0,0 +1,10 @@ +type: theory +custom_name: Copy Constructor +files: +- name: CMakeLists.txt + visible: false +- name: include/int_array.hpp + visible: true + editable: false +- name: src/main.cpp + visible: true diff --git a/ObjectOrientedProgramming/MemoryOwnership/CopyConstructor/task-remote-info.yaml b/ObjectOrientedProgramming/MemoryOwnership/CopyConstructor/task-remote-info.yaml new file mode 100644 index 0000000..01cf9d0 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/CopyConstructor/task-remote-info.yaml @@ -0,0 +1 @@ +id: 1236700959 diff --git a/ObjectOrientedProgramming/MemoryOwnership/CopyConstructor/task.md b/ObjectOrientedProgramming/MemoryOwnership/CopyConstructor/task.md new file mode 100644 index 0000000..ac9c4a3 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/CopyConstructor/task.md @@ -0,0 +1,124 @@ + In C++, a class can define a special kind of constructor, + called the _copy constructor_. + +For example, consider the `int_array` class, which represents +a dynamically allocated array of integers +(see the complete definition of the class in the file `include/int_array.hpp`): + +This class has two fields: a pointer to the allocated array and its size. +It also defines the default constructor creating an empty array, +and one custom constructor creating a new array of the given size. +The destructor of the class deallocates the array. + +
+ Note that we do not check for the null pointer in the destructor, + as deleting a `nullptr` is a safe operation that has no effect. +
+ +To effectively work with an array, the class also +defines a `size()` method to query for the size of the array, +overloads the array subscript operators to provide access to the underlying array, +and overloads the printing operator to display the contents of the array. + +
+ +Note that there are two overloads of array subscript operator: +one for a mutable array and one for an immutable array. + +
+ +Now, suppose a user of this class has created an array: + +```c++ +int_array a = int_array(10); +for (size_t i = 0; i < a.size(); ++i) { + a[i] = (int) i + 1; +} +``` + +They might want to create a copy of this array. +Of course, one can do that manually by creating an array of a suitable size +and assigning elements in a loop. + +However, C++ provides a more convenient way to do this, +by defining a special _copy constructor_: + +```c++ +int_array(const int_array& other) + : data_(new int[other.size_]) + , size_(other.size_) +{ + for (size_t i = 0; i < size_; ++i) { + data_[i] = other.data_[i]; + } +}; +``` + +Now, the user of the class can create a copy as simply as follows: + +```c++ +int_array b(a); +``` + +Another possible use-case scenario: given two existing arrays, +a user might want to reassign one of them, copying the elements of the other. +The C++ language has a tool for that too! +It is called the _copy assignment operator_, which can be declared as follows: + +```c++ +int_array& operator=(const int_array& other) { + if (this == &other) { + return *this; + } + delete[] data_; + data_ = new int[other.size_]; + size_ = other.size_; + for (size_t i = 0; i < size_; ++i) { + data_[i] = other.data_[i]; + } + return *this; +} +``` + +
+ +The first `if` statement in the implementation of the operator +handles the case of self-assignment — in this case, the method simply returns. + +
+ +
+ +Note that the assignment operator returns a reference `int_array&` +to the object itself as a result. +It is required to support multiple assignments syntax, +_e.g._: + +```c++ +a = b = c; +``` + +
+ +The copy assignment operator can be used as follows: + +```c++ +int_array c = int_array(4); +for (size_t i = 0; i < a.size(); ++i) { + c[i] = 0; +} +// re-assign c +c = a; +``` + +
+ +Note that when the following syntax is used: + +```c++ +int_array b = a; +``` + +the copy constructor will be called, not the assignment operator! + +
diff --git a/ObjectOrientedProgramming/MemoryOwnership/Introduction/CMakeLists.txt b/ObjectOrientedProgramming/MemoryOwnership/Introduction/CMakeLists.txt new file mode 100644 index 0000000..ff53e71 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/Introduction/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.27) + +project(ObjectOrientedProgramming-MemoryOwnership-Introduction) + +set(CMAKE_CXX_STANDARD 14) + +# Files from `./src` directory +set(SRC src/main.cpp) + + +# Running learner side code +# Use PROJECT_NAME dependent names of targets for the plugin support to work correctly. +add_executable(${PROJECT_NAME}-run ${SRC}) \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/Introduction/src/main.cpp b/ObjectOrientedProgramming/MemoryOwnership/Introduction/src/main.cpp new file mode 100644 index 0000000..e9cdae1 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/Introduction/src/main.cpp @@ -0,0 +1,3 @@ +int main() { + return 0; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/Introduction/task-info.yaml b/ObjectOrientedProgramming/MemoryOwnership/Introduction/task-info.yaml new file mode 100644 index 0000000..17adf1f --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/Introduction/task-info.yaml @@ -0,0 +1,6 @@ +type: theory +files: +- name: CMakeLists.txt + visible: false +- name: src/main.cpp + visible: true diff --git a/ObjectOrientedProgramming/MemoryOwnership/Introduction/task-remote-info.yaml b/ObjectOrientedProgramming/MemoryOwnership/Introduction/task-remote-info.yaml new file mode 100644 index 0000000..f0bdb7a --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/Introduction/task-remote-info.yaml @@ -0,0 +1 @@ +id: 546734997 diff --git a/ObjectOrientedProgramming/MemoryOwnership/Introduction/task.md b/ObjectOrientedProgramming/MemoryOwnership/Introduction/task.md new file mode 100644 index 0000000..ab167d3 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/Introduction/task.md @@ -0,0 +1,16 @@ +The next few lessons are dedicated to the important concept of _ownership_, +which defines the rules for managing the lifecycle of resources, +such as allocated memory blocks. +The concept of ownership is a crucial part of the modern C++ language, +and mastering it is an important step towards writing safe C++ programs. + +Here, we are going to cover the following topics related to ownership: + +* Lifetime of objects. +* `new` and `delete` operators. +* Placement `new` operator. +* Ownership and move semantics. +* Smart pointers: `unique_ptr`, `shared_ptr`, and `weak_ptr`. +* Resource Acquisition Is Initialization (RAII) idiom. +* Rule-of-five and copy-and-swap idiom. + diff --git a/ObjectOrientedProgramming/MemoryOwnership/MoveSemantics/CMakeLists.txt b/ObjectOrientedProgramming/MemoryOwnership/MoveSemantics/CMakeLists.txt new file mode 100644 index 0000000..9177132 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/MoveSemantics/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.26) + +project(ObjectOrientedProgramming-MemoryOwnership-MoveSemantics) + +set(CMAKE_CXX_STANDARD 14) + +# Files from `./src` directory +set(SRC src/main.cpp) + +# Running learner side code +# Use PROJECT_NAME dependent names of targets for the plugin support to work correctly. +add_executable(${PROJECT_NAME}-run ${SRC}) \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/MoveSemantics/include/int_array.hpp b/ObjectOrientedProgramming/MemoryOwnership/MoveSemantics/include/int_array.hpp new file mode 100644 index 0000000..c74e2a4 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/MoveSemantics/include/int_array.hpp @@ -0,0 +1,98 @@ +#ifndef CPPBASICS_INT_ARRAY_HPP +#define CPPBASICS_INT_ARRAY_HPP + +#include +#include + +class int_array { +public: + int_array() + : data_(nullptr) + , size_(0) + {}; + + explicit int_array(std::size_t size) + : data_(new int[size]) + , size_(size) + {}; + + ~int_array() { + delete[] data_; + } + + int_array(const int_array& other) + : data_(new int[other.size_]) + , size_(other.size_) + { + for (size_t i = 0; i < size_; ++i) { + data_[i] = other.data_[i]; + } + }; + + int_array& operator=(const int_array& other) { + if (this == &other) { + return *this; + } + delete[] data_; + data_ = new int[other.size_]; + size_ = other.size_; + for (size_t i = 0; i < size_; ++i) { + data_[i] = other.data_[i]; + } + return *this; + } + + int_array(int_array&& other) + : data_(other.data_) + , size_(other.size_) + { + other.data_ = nullptr; + other.size_ = 0; + }; + + int_array& operator=(int_array&& other) { + if (this == &other) { + return *this; + } + delete[] data_; + data_ = other.data_; + size_ = other.size_; + other.data_ = nullptr; + other.size_ = 0; + return *this; + } + + std::size_t size() const { + return size_; + } + + int& operator[](std::size_t i) { + return data_[i]; + } + + const int& operator[](std::size_t i) const { + return data_[i]; + } + + friend std::ostream& operator<<(std::ostream &os, const int_array& array) { + if (array.size() == 0) { + os << "[]"; + return os; + } + os << "[ "; + for (std::size_t i = 0; i < array.size(); ++i) { + os << array[i]; + if (i != array.size() - 1) { + os << ", "; + } + } + os << " ]"; + return os; + } + +private: + int* data_; + size_t size_; +}; + +#endif // CPPBASICS_INT_ARRAY_HPP diff --git a/ObjectOrientedProgramming/MemoryOwnership/MoveSemantics/src/main.cpp b/ObjectOrientedProgramming/MemoryOwnership/MoveSemantics/src/main.cpp new file mode 100644 index 0000000..8d487c4 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/MoveSemantics/src/main.cpp @@ -0,0 +1,35 @@ +#include "../include/int_array.hpp" + +int_array create_array(int value, size_t size) { + if (size == 0) { + return int_array(); + } + int_array array = int_array(size); + for (size_t i = 0; i < size; ++i) { + array[i] = value; + } + return array; +} + +int main() { + + std::cout << "create an array 'a' filled with value '1':\n"; + int_array a = create_array(0, 4); + std::cout << "a = " << a << "\n"; + std::cout << "\n"; + + std::cout << "create non-empty arrays 'b' and 'c':\n"; + int_array b = create_array(1, 4); + int_array c = create_array(2, 4); + std::cout << "b = " << b << "\n"; + std::cout << "c = " << c << "\n"; + std::cout << "\n"; + + std::cout << "transfer ownership from 'c' to 'b':\n"; + b = std::move(c); + std::cout << "b = " << b << "\n"; + std::cout << "c = " << c << "\n"; + std::cout << "\n"; + + return 0; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/MoveSemantics/task-info.yaml b/ObjectOrientedProgramming/MemoryOwnership/MoveSemantics/task-info.yaml new file mode 100644 index 0000000..6f4ed8b --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/MoveSemantics/task-info.yaml @@ -0,0 +1,10 @@ +type: theory +custom_name: Move Semantics +files: +- name: CMakeLists.txt + visible: false +- name: src/main.cpp + visible: true +- name: include/int_array.hpp + visible: true + editable: false \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/MoveSemantics/task-remote-info.yaml b/ObjectOrientedProgramming/MemoryOwnership/MoveSemantics/task-remote-info.yaml new file mode 100644 index 0000000..9b70f51 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/MoveSemantics/task-remote-info.yaml @@ -0,0 +1 @@ +id: 811498572 diff --git a/ObjectOrientedProgramming/MemoryOwnership/MoveSemantics/task.md b/ObjectOrientedProgramming/MemoryOwnership/MoveSemantics/task.md new file mode 100644 index 0000000..49931c3 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/MoveSemantics/task.md @@ -0,0 +1,174 @@ +[Move semantics](https://en.cppreference.com/w/cpp/language/move_constructor) +is a feature of C++ that allows to efficiently +transfer ownership of an object without copying. +This can be useful for improving performance +and avoiding unnecessary memory allocations. +Before diving into the move semantics details, let us briefly understand the concept of +[value categories](https://en.cppreference.com/w/cpp/language/value_category). + +* An __lvalue__ expression represents an object that has a name or an identifier. + It refers to something that exists in memory and typically persists beyond a single expression. + It usually stands on the left-hand side of an assignment operator (`=`), hence the name __lvalue__. + +* An __rvalue__ expression represents a temporary or disposable value. + It is usually the intermediate result of some computation that might not have a named memory location. + It usually stands on the right-hand side of an assignment operator (`=`), hence the name __rvalue__. + +For example, below, variables `a`, `b`, and `c` are lvalues, +while the expression `a + b` is an rvalue. + +```c++ +int a = 2; +int b = 3; +int c = a + b; +``` + +Move semantics utilize rvalues, +representing temporary objects or objects about to be destroyed. +When another object wants to copy a soon-to-be-disposed rvalue object, +instead of actual copying, the contents of the rvalue object can be moved. + +For example, recall the `int_array` class, +which has a custom copy constructor +(see file `include/int_array.hpp`): + +```c++ +class int_array { +public: + /* ... */ + int_array(const int_array& other) + : data_(new int[other.size_]) + , size_(other.size_) + { + for (size_t i = 0; i < size_; ++i) { + data_[i] = other.data_[i]; + } + }; + /* ... */ +private: + int* data_; + size_t size_; +}; +``` + +Suppose there is a function that creates an array filled with a given value: + +```c++ +int_array create_array(int value, size_t size) { + if (size == 0) { + return int_array(); + } + int_array array = int_array(size); + for (size_t i = 0; i < size; ++i) { + array[i] = value; + } + return array; +} +``` + +And then this function is called as follows: + +```c++ +// the copy constructor is called here (!) +int_array a = create_array(0, 4); +``` + +In the code above, unnecessary copying is performed, +which copies an array from an object returned from the function +into the newly created object. + +However, since the returned object is actually a temporary rvalue +that is going to be disposed of anyway, +we can take advantage of that and instead of copying the array, +just _move_ the pointer. + +To do that, in addition to the copy constructor, +one can define a _move constructor_, +which takes an rvalue reference, denoted with `&&`, as an argument: + +```c++ +class int_array { +public: + /* ... */ + int_array(int_array&& other) + : data_(other.data_) + , size_(other.size_) + { + other.data_ = nullptr; + other.size_ = 0; + }; + /* ... */ +private: + int* data_; + size_t size_; +}; +``` + +Note that in addition to copying the pointer, +the move constructor of the `int_array` class also nullifies +the pointer in the original object passed by rvalue reference. +It is necessary because, otherwise, once the destructor +of the original object is called, it would deallocate the memory +pointed-to by the `data` field. +This way, the given move constructor implementation reflects +the occurring ownership transfer. + +In a similar way to the copy assignment operator, +one can also define a move assignment operator for a class: + +```c++ +class int_array { +public: + /* ... */ + int_array& operator=(int_array&& other) { + // ... + } + /* ... */ +private: + int *data; + std::size_t size; +}; +``` + +With the help of the move assignment operator and +the special standard function `std::move`, +one can manually transfer ownership from one object to another: + +```c++ +int_array b = create_array(1, 4); +int_array c = create_array(2, 4); +// ownership transfer +b = std::move(c); +``` + +
+ +Technically, the `std::move` function converts an lvalue into an rvalue reference. + +
+ +A custom implementation of the move assignment operator +should ensure that after being moved, +the object remains in a valid state, +usually some kind of null state. +This means that it should be possible to still safely destruct the object, +but other operations such as method calls or operator +uses might lead to undefined behavior. +In general, avoid using an object after it has been moved, +except for assigning it a new value or destroying it. + +The smart pointer `std::unique_ptr` also supports move semantics. +The move semantics of this class transfers ownership of the object +from the source `std::unique_ptr` to the destination pointer. +The source `std::unique_ptr` is then left in a null state. + +```c++ +std::unique_ptr p1 = std::make_unique("Beethoven"); +std::unique_ptr p2; +// transfer ownership from p1 to p2. +p2 = std::move(p1); +// p1 is now in a null state. +assert(p1 == nullptr); +// p2 now owns the Dog object. +assert(p2 != nullptr); +``` \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/NewAndDeleteOperators/CMakeLists.txt b/ObjectOrientedProgramming/MemoryOwnership/NewAndDeleteOperators/CMakeLists.txt new file mode 100644 index 0000000..4548bc7 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/NewAndDeleteOperators/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.26) + +project(ObjectOrientedProgramming-MemoryOwnership-new_and_delete_operators) + +set(CMAKE_CXX_STANDARD 14) + +# Files from `./src` directory +set(SRC src/task.cpp src/book.cpp) + +# Files from `./test` directory +set(TEST test/test.cpp) + +# Running tests +# Use PROJECT_NAME dependent names of targets for the plugin support to work correctly. +configure_test_target(${PROJECT_NAME}-test "${SRC}" ${TEST}) \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/NewAndDeleteOperators/include/book.hpp b/ObjectOrientedProgramming/MemoryOwnership/NewAndDeleteOperators/include/book.hpp new file mode 100644 index 0000000..198ff6f --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/NewAndDeleteOperators/include/book.hpp @@ -0,0 +1,27 @@ +#ifndef CPPBASICS_BOOK_HPP +#define CPPBASICS_BOOK_HPP + +#include +#include +#include + +class Book { +public: + explicit Book(const std::string& name) : name(name) { + std::cout << "Book is open!\n"; + counter++; + } + + ~Book() { + std::cout << "Book is closed!\n"; + } + + static size_t getBooksCounter() { + return counter; + } +private: + std::string name; + static size_t counter; +}; + +#endif // CPPBASICS_BOOK_HPP diff --git a/ObjectOrientedProgramming/MemoryOwnership/NewAndDeleteOperators/src/book.cpp b/ObjectOrientedProgramming/MemoryOwnership/NewAndDeleteOperators/src/book.cpp new file mode 100644 index 0000000..155a888 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/NewAndDeleteOperators/src/book.cpp @@ -0,0 +1,3 @@ +#include "../include/book.hpp" + +size_t Book::counter = 0; \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/NewAndDeleteOperators/src/task.cpp b/ObjectOrientedProgramming/MemoryOwnership/NewAndDeleteOperators/src/task.cpp new file mode 100644 index 0000000..25a6e70 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/NewAndDeleteOperators/src/task.cpp @@ -0,0 +1,19 @@ +#include + +#include "../include/book.hpp" + +void newAndDeleteBook() { + Book *favourite_book = new Book("Harry Potter"); + delete favourite_book; +} + +void mallocAndFreeBook() { + Book *favourite_book = (Book *) malloc(sizeof(Book)); + free(favourite_book); +} + +int main() { + newAndDeleteBook(); + mallocAndFreeBook(); + return 0; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/NewAndDeleteOperators/task-info.yaml b/ObjectOrientedProgramming/MemoryOwnership/NewAndDeleteOperators/task-info.yaml new file mode 100644 index 0000000..605fe55 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/NewAndDeleteOperators/task-info.yaml @@ -0,0 +1,22 @@ +type: edu +custom_name: New and Delete Operators +files: +- name: CMakeLists.txt + visible: false +- name: src/task.cpp + visible: true + placeholders: + - offset: 83 + length: 75 + placeholder_text: // Create and delete Book object using new/delete + - offset: 193 + length: 79 + placeholder_text: // Do the same using malloc/free +- name: test/test.cpp + visible: false +- name: include/book.hpp + visible: true + editable: false +- name: src/book.cpp + visible: true + editable: false diff --git a/ObjectOrientedProgramming/MemoryOwnership/NewAndDeleteOperators/task-remote-info.yaml b/ObjectOrientedProgramming/MemoryOwnership/NewAndDeleteOperators/task-remote-info.yaml new file mode 100644 index 0000000..dc0a00d --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/NewAndDeleteOperators/task-remote-info.yaml @@ -0,0 +1 @@ +id: 700142889 diff --git a/ObjectOrientedProgramming/MemoryOwnership/NewAndDeleteOperators/task.md b/ObjectOrientedProgramming/MemoryOwnership/NewAndDeleteOperators/task.md new file mode 100644 index 0000000..1db6309 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/NewAndDeleteOperators/task.md @@ -0,0 +1,45 @@ +Recall that in the `Memory Management` module of this course, +we studied the `malloc` and `free` functions, which are used +to allocate and deallocate memory. +As we mentioned, these functions implement C-style memory management, +and C++ has its own tools to manage memory. +It is finally time to meet these tools. + +The `new` operator allocates memory on the heap for an object or an array of objects: + +```cpp +// allocates memory for one `int` +int* ptr = new int; +``` + +The `delete` operator releases the memory back to the heap: + +```cpp +delete ptr; +``` + +To allocate an array of a certain type, the `new[]` operator is used: + +```cpp +int* array = new int[10]; +``` + +As always, this operator returns a pointer to the first element of the array. + +To deallocate an array, the `delete[]` operator should be used: + +```cpp +delete[] array; +``` + +What is the difference between `malloc/free` and `new/delete`? + +The most important difference is that the `new` and `delete` operators +call the constructor and destructor, respectively. +The `malloc` and `free` functions do not call constructors or destructors; +they are used merely to allocate raw memory blocks. + +To highlight the difference between the two, we ask you to complete the following task. +Given the `Book` class defined in the `book.hpp` file, +create an object of this class using `new`/`delete` syntax in the `newAndDeleteBook` function, +and then try to allocate memory for an object using `malloc`/`free` syntax in the `mallocAndFreeBook` function. \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/NewAndDeleteOperators/test/test.cpp b/ObjectOrientedProgramming/MemoryOwnership/NewAndDeleteOperators/test/test.cpp new file mode 100644 index 0000000..6ee37fd --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/NewAndDeleteOperators/test/test.cpp @@ -0,0 +1,13 @@ +#include + +#include "../include/book.hpp" + +void newAndDeleteBook(); +void mallocAndFreeBook(); + +TEST(CreateAndDeleteBookTest, SimpleBookTest) { + newAndDeleteBook(); + mallocAndFreeBook(); + ASSERT_EQ(1, Book::getBooksCounter()) + << "Expected constructor of Book() to be called inside `newAndDeleteBook` function."; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/ObjectLifetime/CMakeLists.txt b/ObjectOrientedProgramming/MemoryOwnership/ObjectLifetime/CMakeLists.txt new file mode 100644 index 0000000..0576868 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/ObjectLifetime/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.26) + +project(ObjectOrientedProgramming-MemoryOwnership-ObjectLifetime) + +set(CMAKE_CXX_STANDARD 14) + +# Files from `./src` directory +set(SRC src/main.cpp) + + +# Running learner side code +# Use PROJECT_NAME dependent names of targets for the plugin support to work correctly. +add_executable(${PROJECT_NAME}-run ${SRC}) \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/ObjectLifetime/src/main.cpp b/ObjectOrientedProgramming/MemoryOwnership/ObjectLifetime/src/main.cpp new file mode 100644 index 0000000..e9cdae1 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/ObjectLifetime/src/main.cpp @@ -0,0 +1,3 @@ +int main() { + return 0; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/ObjectLifetime/task-info.yaml b/ObjectOrientedProgramming/MemoryOwnership/ObjectLifetime/task-info.yaml new file mode 100644 index 0000000..3af5f2a --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/ObjectLifetime/task-info.yaml @@ -0,0 +1,7 @@ +type: theory +custom_name: Object Lifetime +files: + - name: CMakeLists.txt + visible: false + - name: src/main.cpp + visible: true diff --git a/ObjectOrientedProgramming/MemoryOwnership/ObjectLifetime/task-remote-info.yaml b/ObjectOrientedProgramming/MemoryOwnership/ObjectLifetime/task-remote-info.yaml new file mode 100644 index 0000000..c741cbf --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/ObjectLifetime/task-remote-info.yaml @@ -0,0 +1 @@ +id: 2100131281 diff --git a/ObjectOrientedProgramming/MemoryOwnership/ObjectLifetime/task.md b/ObjectOrientedProgramming/MemoryOwnership/ObjectLifetime/task.md new file mode 100644 index 0000000..97c4a15 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/ObjectLifetime/task.md @@ -0,0 +1,15 @@ +As we remember, the constructor and destructor are mandatory elements of any class, +and they are responsible for creating and destroying an object. +For any object, the period of time between its creation +through one of its constructors and its destruction through the destructor +is called the [_lifetime_](https://en.cppreference.com/w/cpp/language/lifetime) of this object. + +The concepts of an object's lifetime and +[_storage duration_](https://en.cppreference.com/w/cpp/language/storage_duration) +are related, but they mean different things. +Object lifetime refers to the object itself, while storage duration refers to the memory allocated for it. +The storage duration is the time between the allocation of a memory region and its deallocation, +while lifetime is the time between the construction of an object and its destruction. +Object lifetime is equal to or less than the lifetime of its storage. +Two objects residing in a memory region with the same storage duration can have +different non-overlapping lifetimes. \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/Ownership/CMakeLists.txt b/ObjectOrientedProgramming/MemoryOwnership/Ownership/CMakeLists.txt new file mode 100644 index 0000000..c6b5c46 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/Ownership/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.26) + +project(ObjectOrientedProgramming-MemoryOwnership-Ownership) + +set(CMAKE_CXX_STANDARD 14) + +# Files from `./src` directory +set(SRC src/main.cpp) + + +# Running learner side code +# Use PROJECT_NAME dependent names of targets for the plugin support to work correctly. +add_executable(${PROJECT_NAME}-run ${SRC}) \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/Ownership/src/main.cpp b/ObjectOrientedProgramming/MemoryOwnership/Ownership/src/main.cpp new file mode 100644 index 0000000..9b1a251 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/Ownership/src/main.cpp @@ -0,0 +1,4 @@ +int main() { + // Put your code here + return 0; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/Ownership/task-info.yaml b/ObjectOrientedProgramming/MemoryOwnership/Ownership/task-info.yaml new file mode 100644 index 0000000..e84747f --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/Ownership/task-info.yaml @@ -0,0 +1,7 @@ +type: theory +custom_name: Ownership +files: + - name: CMakeLists.txt + visible: false + - name: src/main.cpp + visible: true diff --git a/ObjectOrientedProgramming/MemoryOwnership/Ownership/task-remote-info.yaml b/ObjectOrientedProgramming/MemoryOwnership/Ownership/task-remote-info.yaml new file mode 100644 index 0000000..8b0dc9e --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/Ownership/task-remote-info.yaml @@ -0,0 +1 @@ +id: 1596188677 diff --git a/ObjectOrientedProgramming/MemoryOwnership/Ownership/task.md b/ObjectOrientedProgramming/MemoryOwnership/Ownership/task.md new file mode 100644 index 0000000..2754f8d --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/Ownership/task.md @@ -0,0 +1,99 @@ +In C++, ownership refers to the responsibility of managing +the lifecycle of certain resources, +such as memory, files, network connections, objects, _etc_. +The ownership model in C++ can be broadly categorized into two kinds: +ownership of sub-objects stored in object fields +and ownership of dynamically created objects using pointers. + +In C++, classes often contain member fields that represent sub-objects. +These sub-objects are typically owned by the parent object. +The parent object is responsible for calling their constructors and destructors, +and the memory they occupy is automatically managed by the parent object's lifecycle. + +
+ +Note that constructors and destructors are called in a specific order, +and, moreover, the order of destruction is the reverse order of initialization. +For example, consider the following class hierarchy: + +```c++ +class Motherboard { /* ... */ }; +class Processor { /* ... */ }; +class RandomAccessMemory { /* ... */ }; + +class Device { /* ... */ }; + +class Laptop : public Device { +private: + // Ownership of objects stored in the fields + // is managed automatically + Motherboard motherboard; + Processor processor; + RandomAccessMemory ram; +public: + /* ... */ +}; +``` + +The order of construction of a `Laptop` object would be as follows: +1. the base class `Device` constructor would be called first; +2. then the sub-objects' constructors would be called: + * the `Motherboard` constructor for the field `motherboard`; + * the `Processor` constructor for the field `processor`; + * the `RandomAccessMemory` constructor for the field `ram`; +3. finally, the `Laptop` constructor would be called. + +The order of destruction is the opposite: +1. first, the derived class destructor `Laptop` would be called; +2. then the sub-objects' destructors would be called: + * the `RandomAccessMemory` destructor; + * the `Processor` destructor; + * the `Motherboard` destructor; +3. finally, the base class `Device` destructor is called. + +
+ +Objects stored in pointer-typed fields are, by default, considered to be non-owned. +When a class contains a pointer to an object, +the responsibility for its lifetime and storage duration management lies outside the class. +It is crucial to understand this fact in order to prevent memory leaks and undefined behavior, +as ownership is not automatically transferred with the assignment of pointers. + +```c++ +class Student { +private: + // Non-owned by default + Laptop* laptop; +public: + explicit Student(Laptop* laptop) + : laptop(laptop) {} + + // the destructor does not destroy the object + // pointed to by the laptop field by default + ~Student() = default; +}; +``` + +In the example above, the Student class contains +a pointer to a dynamically allocated `Laptop` object. +The ownership responsibility for the `Laptop` object +may or may not be assigned to the `Student` class depending on the desired semantics. +If the desired semantics is that the `Student` takes +ownership of the `laptop` object passed to it in the constructor, +then the developer of this class must ensure that +the object is destroyed manually in the destructor +(for example, by using the `delete` operator). + +Objects in pointer-typed fields are considered non-owned by default +to allow for more explicit control over memory management, +providing more flexibility and efficiency. + +In the scenarios where ownership needs to be transferred, +[_smart pointers_](https://en.wikipedia.org/wiki/Smart_pointer) +such as `std::unique_ptr` and `std::shared_ptr` can be used, +providing automated memory management with reduced risks of memory-related issues. +These smart pointers are specialized classes defined in the standard library of C++. +They behave like plain pointers, in a sense that they support the +same set of operations, like the dereferencing, +but in addition, they provide specific ownership semantics. +We will discuss smart pointers in more detail in the next few lessons. \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/PlacementNew/CMakeLists.txt b/ObjectOrientedProgramming/MemoryOwnership/PlacementNew/CMakeLists.txt new file mode 100644 index 0000000..1ea4a14 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/PlacementNew/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.26) + +project(ObjectOrientedProgramming-MemoryOwnership-Placement_new) + +set(CMAKE_CXX_STANDARD 14) + +# Files from `./src` directory +set(SRC src/task.cpp src/animal.cpp) + +# Files from `./test` directory +set(TEST test/test.cpp) + +# Running tests +# Use PROJECT_NAME dependent names of targets for the plugin support to work correctly. +configure_test_target(${PROJECT_NAME}-test "${SRC}" ${TEST}) \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/PlacementNew/include/animal.hpp b/ObjectOrientedProgramming/MemoryOwnership/PlacementNew/include/animal.hpp new file mode 100644 index 0000000..21d9f69 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/PlacementNew/include/animal.hpp @@ -0,0 +1,56 @@ +#ifndef CPPBASICS_ANIMAL_HPP +#define CPPBASICS_ANIMAL_HPP + +#include +#include +#include + +class Cat { +public: + explicit Cat(const std::string& name) : name(name) { + std::cout << "Cat " << this->name << " jumped into the box!\n"; + counter++; + } + + ~Cat() { + counter--; + std::cout << "Cat " << this->name << " jumped out of the box!\n"; + } + + std::string getName() const { + return name; + } + + static size_t getCounter() { + return counter; + } +private: + std::string name; + static size_t counter; +}; + +class Mouse { +public: + explicit Mouse(const std::string& name) : name(name) { + std::cout << "Mouse " << this->name << " jumped into the box!\n"; + counter++; + } + + ~Mouse() { + counter--; + std::cout << "Mouse " << this->name << " jumped out of the box!\n"; + } + + std::string getName() const { + return name; + } + + static size_t getCounter() { + return counter; + } +private: + std::string name; + static size_t counter; +}; + +#endif // CPPBASICS_ANIMAL_HPP diff --git a/ObjectOrientedProgramming/MemoryOwnership/PlacementNew/src/animal.cpp b/ObjectOrientedProgramming/MemoryOwnership/PlacementNew/src/animal.cpp new file mode 100644 index 0000000..08bee44 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/PlacementNew/src/animal.cpp @@ -0,0 +1,4 @@ +#include "../include/animal.hpp" + +size_t Cat::counter = 0; +size_t Mouse::counter = 0; \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/PlacementNew/src/task.cpp b/ObjectOrientedProgramming/MemoryOwnership/PlacementNew/src/task.cpp new file mode 100644 index 0000000..1a516cb --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/PlacementNew/src/task.cpp @@ -0,0 +1,19 @@ +#include + +#include "../include/animal.hpp" + +Cat* createCat(char* memory) { + return new (memory) Cat("Tom"); +} + +void destroyCat(char* memory) { + reinterpret_cast(memory)->~Cat(); +} + +Mouse* createMouse(char* memory) { + return new (memory) Mouse("Jerry"); +} + +void destroyMouse(char* memory) { + reinterpret_cast(memory)->~Mouse(); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/PlacementNew/task-info.yaml b/ObjectOrientedProgramming/MemoryOwnership/PlacementNew/task-info.yaml new file mode 100644 index 0000000..7b23e3c --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/PlacementNew/task-info.yaml @@ -0,0 +1,29 @@ +type: edu +custom_name: Placement New +files: +- name: CMakeLists.txt + visible: false +- name: src/animal.cpp + visible: true + editable: false +- name: include/animal.hpp + visible: true + editable: false +- name: src/task.cpp + visible: true + placeholders: + - offset: 85 + length: 31 + placeholder_text: // create Cat object with the name Tom in the given memory block + - offset: 156 + length: 39 + placeholder_text: // destroy Cat object stored in the given memory block + - offset: 238 + length: 35 + placeholder_text: // create Mouse object with the name Jerry in the given memory + block + - offset: 315 + length: 43 + placeholder_text: // destroy Mouse object stored in the given memory block +- name: test/test.cpp + visible: false diff --git a/ObjectOrientedProgramming/MemoryOwnership/PlacementNew/task-remote-info.yaml b/ObjectOrientedProgramming/MemoryOwnership/PlacementNew/task-remote-info.yaml new file mode 100644 index 0000000..77207aa --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/PlacementNew/task-remote-info.yaml @@ -0,0 +1 @@ +id: 1562916100 diff --git a/ObjectOrientedProgramming/MemoryOwnership/PlacementNew/task.md b/ObjectOrientedProgramming/MemoryOwnership/PlacementNew/task.md new file mode 100644 index 0000000..08b1ffe --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/PlacementNew/task.md @@ -0,0 +1,76 @@ +The placement `new` operator is a special version of the `new` operator. +It allows for the construction of an object in a pre-allocated memory region. +This can be useful in various scenarios, +such as reducing memory fragmentation and improving performance. + +To use the placement `new` operator, +one first has to allocate a memory region of appropriate size. +Then, this memory region needs to be passed into the placement `new` operator. +The operator will then construct an object of the specified type within the given memory region. + +When an object created with the help of the placement `new` operator is no longer needed, +it must be destroyed by explicitly calling the destructor. +When working with arrays, the destructor should be called for each object in the array. + +Here is an example of how to use the placement `new` operator to construct +an integer object within a pre-allocated memory region: + +```cpp +#include + +class Cat { +public: + explicit Cat(const std::string& name) { /* ... */ } + ~Cat() { ... } + std::string getName() { /* ... */ } + /* ... */ +}; + +int main() { + // Allocate a memory region of the appropriate size. + char buffer[sizeof(Cat)]; + + // Construct a Cat object in the given memory region. + Cat* cat = new (buffer) Cat("Garfield"); + + // Access the object. + std::cout << cat->getName(); << std::endl; + + // Destruct the object. + cat->~Cat(); + + return 0; +} +``` + +With the help of the placement `new` operator, it is possible to +fit the lifetimes of several objects within the same storage duration. +For example, when you reuse a memory region to construct a new object, +the lifetime of the previous object ends, and the lifetime of the new object begins. +However, the storage duration of the memory region remains the same. + +In order to complete this task, please implement the following functions. + +The `createCat` function should create a `Cat` object with the name `"Tom"` in the given memory block: + +```c++ +Cat* createCat(char* memory); +``` + +The `destroyCat` function should destroy the `Cat` object residing in the given memory block: + +```c++ +void destroyCat(char* memory); +``` + +The `createMouse` function should create a `Mouse` object with the name `"Jerry"` in the given memory block: + +```c++ +Mouse* createMouse(char* memory); +``` + +The `destroyMouse` function should destroy the `Cat` object residing in the given memory block: + +```c++ +void destroyMouse(char* memory); +``` \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/PlacementNew/test/test.cpp b/ObjectOrientedProgramming/MemoryOwnership/PlacementNew/test/test.cpp new file mode 100644 index 0000000..8ef6221 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/PlacementNew/test/test.cpp @@ -0,0 +1,37 @@ +#include + +#include +#include + +#include "../include/animal.hpp" + +Cat* createCat(char* memory); +void destroyCat(char* memory); + +Mouse* createMouse(char* memory); +void destroyMouse(char* memory); + +TEST(PlacementNew, PlacementNewTest) { + size_t size = std::max(sizeof(Cat), sizeof(Mouse)); + char* memory = (char*) malloc(size); + + Cat* cat = createCat(memory); + ASSERT_EQ("Tom", cat->getName()); + ASSERT_EQ("Tom", reinterpret_cast(memory)->getName()); + ASSERT_EQ(cat, reinterpret_cast(memory)); + ASSERT_EQ(1, Cat::getCounter()); + + destroyCat(memory); + ASSERT_EQ(0, Cat::getCounter()); + + Mouse* mouse = createMouse(memory); + ASSERT_EQ("Jerry", mouse->getName()); + ASSERT_EQ("Jerry", reinterpret_cast(memory)->getName()); + ASSERT_EQ(mouse, reinterpret_cast(memory)); + ASSERT_EQ(1, Mouse::getCounter()); + + destroyMouse(memory); + ASSERT_EQ(0, Mouse::getCounter()); + + free(memory); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/RAIICopySwapIdiom/CMakeLists.txt b/ObjectOrientedProgramming/MemoryOwnership/RAIICopySwapIdiom/CMakeLists.txt new file mode 100644 index 0000000..8d1909c --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/RAIICopySwapIdiom/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.26) + +project(ObjectOrientedProgramming-MemoryOwnership-RAIICopySwapIdiom) + +set(CMAKE_CXX_STANDARD 14) + +# Files from `./src` directory +set(SRC src/main.cpp) + +# Running learner side code +# Use PROJECT_NAME dependent names of targets for the plugin support to work correctly. +add_executable(${PROJECT_NAME}-run ${SRC}) \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/RAIICopySwapIdiom/include/int_array.hpp b/ObjectOrientedProgramming/MemoryOwnership/RAIICopySwapIdiom/include/int_array.hpp new file mode 100644 index 0000000..057b15e --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/RAIICopySwapIdiom/include/int_array.hpp @@ -0,0 +1,83 @@ +#ifndef CPPBASICS_INT_ARRAY_HPP +#define CPPBASICS_INT_ARRAY_HPP + +#include +#include + +class int_array { +public: + int_array() + : data_(nullptr) + , size_(0) + {}; + + explicit int_array(std::size_t size) + : data_(new int[size]) + , size_(size) + {}; + + ~int_array() { + delete[] data_; + } + + int_array(const int_array& other) + : data_(new int[other.size_]) + , size_(other.size_) + { + for (size_t i = 0; i < size_; ++i) { + data_[i] = other.data_[i]; + } + }; + + int_array(int_array&& other) + : data_(other.data_) + , size_(other.size_) + { + other.data_ = nullptr; + other.size_ = 0; + }; + + int_array& operator=(int_array other) { + swap(other); + return *this; + } + + std::size_t size() const { + return size_; + } + + int& operator[](std::size_t i) { + return data_[i]; + } + + const int& operator[](std::size_t i) const { + return data_[i]; + } + + void swap(int_array& other) { + std::swap(data_, other.data_); + std::swap(size_, other.size_); + } + + friend std::ostream& operator<<(std::ostream &os, const int_array& array) { + if (array.size() == 0) { + os << "[]"; + return os; + } + os << "[ "; + for (std::size_t i = 0; i < array.size(); ++i) { + os << array[i]; + if (i != array.size() - 1) { + os << ", "; + } + } + os << " ]"; + return os; + } + +private: + int* data_; + size_t size_; +}; + +#endif // CPPBASICS_INT_ARRAY_HPP diff --git a/ObjectOrientedProgramming/MemoryOwnership/RAIICopySwapIdiom/src/main.cpp b/ObjectOrientedProgramming/MemoryOwnership/RAIICopySwapIdiom/src/main.cpp new file mode 100644 index 0000000..d2d6aa7 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/RAIICopySwapIdiom/src/main.cpp @@ -0,0 +1,44 @@ +#include "../include/int_array.hpp" + +int main() { + + std::cout << "create an empty array 'a':\n"; + int_array a; + std::cout << "a = " << a << "\n"; + std::cout << "\n"; + + std::cout << "create a non-empty array 'b':\n"; + int_array b = int_array(10); + for (size_t i = 0; i < b.size(); ++i) { + b[i] = (int) i + 1; + } + std::cout << "b = " << b << "\n"; + std::cout << "\n"; + + std::cout << "create a copy 'c' of array 'b':\n"; + int_array c = b; + std::cout << "c = " << c << "\n"; + std::cout << "\n"; + + std::cout << "modify 'c', check that the original 'b' is left unchanged:\n"; + for (size_t i = 0; i < c.size(); ++i) { + c[i] = -c[i]; + } + std::cout << "b = " << b << "\n"; + std::cout << "c = " << c << "\n"; + std::cout << "\n"; + + std::cout << "move data from 'c' to 'b':\n"; + b = std::move(c); + std::cout << "b = " << b << "\n"; + std::cout << "c = " << c << "\n"; + std::cout << "\n"; + + std::cout << "move data from 'b' to 'a':\n"; + a = std::move(b); + std::cout << "a = " << a << "\n"; + std::cout << "b = " << b << "\n"; + std::cout << "\n"; + + return 0; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/RAIICopySwapIdiom/task-info.yaml b/ObjectOrientedProgramming/MemoryOwnership/RAIICopySwapIdiom/task-info.yaml new file mode 100644 index 0000000..4754376 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/RAIICopySwapIdiom/task-info.yaml @@ -0,0 +1,10 @@ +type: theory +custom_name: RAII & Copy-and-Swap Idiom +files: +- name: CMakeLists.txt + visible: false +- name: src/main.cpp + visible: true +- name: include/int_array.hpp + visible: true + editable: false diff --git a/ObjectOrientedProgramming/MemoryOwnership/RAIICopySwapIdiom/task-remote-info.yaml b/ObjectOrientedProgramming/MemoryOwnership/RAIICopySwapIdiom/task-remote-info.yaml new file mode 100644 index 0000000..f7a9788 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/RAIICopySwapIdiom/task-remote-info.yaml @@ -0,0 +1 @@ +id: 1965960933 diff --git a/ObjectOrientedProgramming/MemoryOwnership/RAIICopySwapIdiom/task.md b/ObjectOrientedProgramming/MemoryOwnership/RAIICopySwapIdiom/task.md new file mode 100644 index 0000000..91ef021 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/RAIICopySwapIdiom/task.md @@ -0,0 +1,189 @@ +Concluding the presentation of the ownership model in the C++ language, +we need to discuss several essential concepts and idioms. + +[_Resource Acquisition Is Initialization_](https://en.cppreference.com/w/cpp/language/raii), +or __RAII__ for short, is a programming technique that involves linking a resource +(such as a dynamically allocated memory block, an open file, a network connection, _etc._) +to the lifetime of an object. +According to this idiom: + +1. the constructor of the class should allocate and initialize + all the resources the object will own during its lifetime; +2. the copy constructor and copy assignment operator of the class + should copy the resource; + if the resource is not copyable, then this constructor should be deleted; +3. the move constructor and move assignment operator of the class + should transfer the ownership of the resource; +4. the destructor of the class should release all the resources that the object owns. + +The [**Rule of five**](https://en.cppreference.com/w/cpp/language/rule_of_three) +is a related concept; it states that if a class requires a custom + +1. destructor +2. copy constructor +3. copy assignment operator +4. move constructor +5. move assignment operator + +then it actually requires all five. + +Note that if your class does not define any of these methods, +the compiler will generate default implementations for them: + +* the default copy constructor/assignment performs a shallow copy of the object, + invoking the copy constructor/assignment of its member fields; +* the default move constructor/assignment performs an elementwise move of the class' members, + invoking the move constructor/assignment of its member fields; +* additionally, if there is no custom constructor, the default constructor + is automatically generated — it initializes all member fields to their default values. + +One can enforce the generation of default implementations using +the `= default` syntax: + +```c++ +class X { +public: + X() = default; + X(const X&) = default; + X(X&&) = default; + + X& operator=(const X&) = default; + X& operator=(X&&) = default; + /* ... */ +}; +``` + +This might be useful when you have defined a custom implementation +for one of the "five" methods, but still want to use the default +implementation for others. + +On the other hand, you might want to explicitly forbid auto-generating +any of these methods, using the `= delete` syntax: + +```c++ +class X { +public: + X() = delete; + X(const X&) = delete; + X(X&&) = delete; + + X& operator=(const X&) = delete; + X& operator=(X&&) = delete; + /* ... */ +}; +``` + +For example, this can be useful in cases when the class should not be copyable. +In such a scenario, you can define the move constructor and assignment operator, +but delete the copy constructor and copy assignment operator. + + +
+ +Before the advent of the [C++11 edition](https://en.cppreference.com/w/cpp/11) +of the language, the rule of five was known as the _rule of three_. +This is because before C++11, the language did not have the feature of move semantics, +so it was not possible to define the move constructor and move assignment operator. + +
+ +Let us revisit the `int_array` class to see how it fits into RAII and the rule of five +(see complete definition of the class in the file `include/int_array.hpp`). +This class manages a resource — a dynamically allocated array of integers. +The lifecycle of this array is bound to the lifetime of the `int_array` object. + +The default constructor creates an empty array: + +```c++ +int_array() + : data_(nullptr) + , size_(0) +{}; +``` + +Another way to look at the default constructor +is that it creates an array in a special "empty" state — +this point of view will become handy later on. + +Another constructor of the class creates an array of the given size +by actually allocating the memory for it: + +```c++ +explicit int_array(std::size_t size) + : data_(new int[size]) + , size_(size) +{}; +``` + +As such, the destructor has to deallocate this memory: + +```c++ +~int_array() { + delete[] data_; +} +``` + +Now, the rule of five dictates that the class should also define +custom copy and move constructors, as well as copy and move assignment operators. +We have already seen these constructors in previous lessons: + +```c++ +int_array(const int_array& other) + : data_(new int[other.size_]) + , size_(other.size_) + { + for (size_t i = 0; i < size_; ++i) { + data_[i] = other.data_[i]; + } + }; + +int_array(int_array&& other) + : data_(other.data_) + , size_(other.size_) +{ + other.data_ = nullptr; + other.size_ = 0; +}; +``` + +Notice how the move constructor resets the argument object to an "empty" state. +Calling the destructor on this "empty" object later will essentially have no effect. +Validly so, as the ownership of the resource has been transferred to the new object. + +Next, we need to define the assignment operators. +To avoid code duplication between the copy constructor and copy assignment operator +(and similarly, between the move constructor and move assignment operator), +it is possible to use another clever trick called +the [__Copy-and-Swap idiom__](https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Copy-and-swap). + +With the help of this idiom, it is sufficient to define +just a single version of the assignment operator, +which should take the argument by value: + +```c++ +int_array& operator=(int_array other) { + swap(other); + return *this; +} +``` + +Depending on whether the operator is called with +an lvalue (taken as `const int_array&`) or an rvalue (taken as `int_array&&`) +as an argument, the copy constructor or move constructor +will create a local copy (`int_array`) of the data in the `other` variable. +Then, with the help of the swap function (see definition below), +the assignment operator swaps the data of the current object with that of the local copy. +The temporary local copy then destructs, releasing the old data along the way. + +The `swap` function performs an exchange of data between two objects, +with the help of the standard function `std::swap`, which is capable of swapping variables of primitive types: + +```c++ +void swap(int_array& other) { + std::swap(data_, other.data_); + std::swap(size_, other.size_); +} +``` + +This way, the `int_array` class implements the _RAII_ principle, +following the _rule of five_ and the _copy-and-swap_ idiom. \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/SharedPtr/CMakeLists.txt b/ObjectOrientedProgramming/MemoryOwnership/SharedPtr/CMakeLists.txt new file mode 100644 index 0000000..0fc777e --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/SharedPtr/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.26) + +project(ObjectOrientedProgramming-MemoryOwnership-SharedPtr) + +set(CMAKE_CXX_STANDARD 14) + +# Files from `./src` directory +set(SRC src/task.cpp) + +# Files from `./test` directory +set(TEST test/test.cpp) + + +# Running learner side code +# Use PROJECT_NAME dependent names of targets for the plugin support to work correctly. +add_executable(${PROJECT_NAME}-run ${SRC}) + +# Running tests +# Use PROJECT_NAME dependent names of targets for the plugin support to work correctly. +configure_test_target(${PROJECT_NAME}-test ${SRC} ${TEST}) \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/SharedPtr/include/chat.hpp b/ObjectOrientedProgramming/MemoryOwnership/SharedPtr/include/chat.hpp new file mode 100644 index 0000000..dfb5f03 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/SharedPtr/include/chat.hpp @@ -0,0 +1,61 @@ +#ifndef CPPBASICS_CHAT_HPP +#define CPPBASICS_CHAT_HPP + +#include +#include +#include + +class Chat { +public: + Chat(int id, std::string name) + : id(id) + , name(std::move(name)) + {}; + + inline int getId() const { + return id; + } + + inline std::string getName() const { + return name; + } +private: + int id; + std::string name; +}; + +class User { +public: + + explicit User(std::string name) + : id(nextId++) + , name(std::move(name)) + , chat(nullptr) + {}; + + inline int getId() const { + return id; + } + + inline std::string getName() const { + return name; + } + + inline const std::shared_ptr& getChat() const { + return chat; + } + + void createNewChat(std::string name); + + void joinChatByInvite(const User& user); + + void leaveChat(); +private: + int id; + std::string name; + std::shared_ptr chat; + static int nextId; + static int nextChatId; +}; + +#endif // CPPBASICS_CHAT_HPP diff --git a/ObjectOrientedProgramming/MemoryOwnership/SharedPtr/src/task.cpp b/ObjectOrientedProgramming/MemoryOwnership/SharedPtr/src/task.cpp new file mode 100644 index 0000000..688e60c --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/SharedPtr/src/task.cpp @@ -0,0 +1,30 @@ +#include + +#include "../include/chat.hpp" + +int User::nextId = 0; +int User::nextChatId = 0; + +void User::createNewChat(std::string name) { + chat = std::make_shared(nextChatId++, std::move(name)); +} + +void User::joinChatByInvite(const User& user) { + chat = user.chat; +} + +void User::leaveChat() { + chat.reset(); +} + +int main() { + User bob("Bob"); + User alice("Alice"); + bob.createNewChat("C++ discussion"); + alice.joinChatByInvite(bob); + + std::cout << "Bob is currently in the chat " << bob.getChat()->getName() << "\n"; + std::cout << "Alice is currently in the chat " << alice.getChat()->getName() << "\n"; + + return 0; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/SharedPtr/task-info.yaml b/ObjectOrientedProgramming/MemoryOwnership/SharedPtr/task-info.yaml new file mode 100644 index 0000000..1a7690c --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/SharedPtr/task-info.yaml @@ -0,0 +1,21 @@ +type: edu +custom_name: Shared Pointer +files: +- name: CMakeLists.txt + visible: false +- name: test/test.cpp + visible: false +- name: src/task.cpp + visible: true + placeholders: + - offset: 151 + length: 61 + placeholder_text: /* TODO */ + - offset: 268 + length: 17 + placeholder_text: /* TODO */ + - offset: 318 + length: 13 + placeholder_text: /* TODO */ +- name: include/chat.hpp + visible: true diff --git a/ObjectOrientedProgramming/MemoryOwnership/SharedPtr/task-remote-info.yaml b/ObjectOrientedProgramming/MemoryOwnership/SharedPtr/task-remote-info.yaml new file mode 100644 index 0000000..c110f8c --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/SharedPtr/task-remote-info.yaml @@ -0,0 +1 @@ +id: 1307427631 diff --git a/ObjectOrientedProgramming/MemoryOwnership/SharedPtr/task.md b/ObjectOrientedProgramming/MemoryOwnership/SharedPtr/task.md new file mode 100644 index 0000000..09cd4b2 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/SharedPtr/task.md @@ -0,0 +1,78 @@ +In contrast to `std::unique_ptr`, the smart pointer class +[`std::shared_ptr`](https://en.cppreference.com/w/cpp/memory/shared_ptr) introduces +shared ownership semantics, allowing multiple smart pointers to share control +over the same dynamically allocated object. + +Under the hood, `std::shared_ptr` implements a reference count to track +the number of shared pointers pointing to the same object. +The reference count is dynamically adjusted as shared pointers are created or destroyed. +When the reference count reaches zero, the managed object is automatically destroyed. + +While `std::unique_ptr` is movable but not copyable, `std::shared_ptr` is both movable and copyable. +Copying a `std::shared_ptr` results in shared ownership. +It increments the reference counter. +The underlying object is only deallocated when +the last shared pointer releases its ownership. + +Let us have a look at an example of `std::shared_ptr` usage: + +```c++ +void test() { + // creating a shared pointer + std::shared_ptr dog = std::make_shared("Pluto"); + // using the pointer + dog->bark(); + // creating a copy of the shared pointer + std::shared_ptr copy = dog; + // both dog and copy share ownership of the same object + std::cout << *dog << " " << *copy << "\n"; + // one can query the count of shared pointers + // pointing to the given object + std::cout << "dog.use_count() = " << dog.use_count() << "\n"; + // similar to std::unique_ptr, it is possible to obtain a plain pointer + std::cout << dog.get() << "\n"; + // when the function exits, + // destructors of both shared pointers are called, + // dropping the reference count to 0 and thus + // triggering the deallocation of the pointed-to Dog object +} +``` + +As an exercise, let us develop a simple chat system. +It consists of two classes: `Chat` and `User` (see file `include/chat.hpp`). +Each user has a shared pointer to the chat object in which they are currently logged in. +Your task is to write the implementation of the following methods for the `User` class. + +```c++ +void createNewChat(std::string name); +``` + +* The `createNewChat` method should create a new chat with the given name + and log the user into it. + +
+ +To create a new object pointed to by a shared pointer, +use the function `std::make_shared` + +
+ +
+ +To assign a unique identifier to the newly created `Chat` object, +use the static field `nextChatId`. + +
+ +```c++ +void joinChatByInvite(const User& user); +``` + +* The `joinChatByInvite` method should log the user into another user's chat + (by reassigning its chat pointer). + +```c++ +void leaveChat(); +``` + +* The `leaveChat` method should log the user out of the chat. \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/SharedPtr/test/test.cpp b/ObjectOrientedProgramming/MemoryOwnership/SharedPtr/test/test.cpp new file mode 100644 index 0000000..393df01 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/SharedPtr/test/test.cpp @@ -0,0 +1,41 @@ +#include +#include + +#include "../include/chat.hpp" + +TEST(UserTest, CreateNewChatTest) { + User bob("Bob"); + bob.createNewChat("Chat"); + ASSERT_NE(nullptr, bob.getChat()); + ASSERT_EQ("Chat", bob.getChat()->getName()); + ASSERT_EQ(1, bob.getChat().use_count()); + + std::shared_ptr chat = bob.getChat(); + bob.createNewChat("New Chat"); + ASSERT_NE(nullptr, bob.getChat()); + ASSERT_NE(chat, bob.getChat()); + ASSERT_NE(chat->getId(), bob.getChat()->getId()); + ASSERT_EQ("New Chat", bob.getChat()->getName()); + ASSERT_EQ(1, bob.getChat().use_count()); +} + +TEST(UserTest, JoinChatByInviteTest) { + User bob("Bob"); + User alice("Alice"); + bob.createNewChat("Chat"); + alice.joinChatByInvite(bob); + ASSERT_NE(nullptr, alice.getChat()); + ASSERT_EQ(bob.getChat(), alice.getChat()); + ASSERT_EQ(2, bob.getChat().use_count()); +} + +TEST(UserTest, LeaveChatTest) { + User bob("Bob"); + User alice("Alice"); + bob.createNewChat("Chat"); + alice.joinChatByInvite(bob); + bob.leaveChat(); + ASSERT_EQ(nullptr, bob.getChat()); + ASSERT_NE(nullptr, alice.getChat()); + ASSERT_EQ(1, alice.getChat().use_count()); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/SmartPointersSummary/CMakeLists.txt b/ObjectOrientedProgramming/MemoryOwnership/SmartPointersSummary/CMakeLists.txt new file mode 100644 index 0000000..5d20ce4 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/SmartPointersSummary/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.26) + +project(ObjectOrientedProgramming-MemoryOwnership-SmartPointersSummary) + +set(CMAKE_CXX_STANDARD 14) + +# Files from `./src` directory +set(SRC src/main.cpp) + + +# Running learner side code +# Use PROJECT_NAME dependent names of targets for the plugin support to work correctly. +add_executable(${PROJECT_NAME}-run ${SRC}) \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/SmartPointersSummary/src/main.cpp b/ObjectOrientedProgramming/MemoryOwnership/SmartPointersSummary/src/main.cpp new file mode 100644 index 0000000..9b1a251 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/SmartPointersSummary/src/main.cpp @@ -0,0 +1,4 @@ +int main() { + // Put your code here + return 0; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/SmartPointersSummary/task-info.yaml b/ObjectOrientedProgramming/MemoryOwnership/SmartPointersSummary/task-info.yaml new file mode 100644 index 0000000..87f9701 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/SmartPointersSummary/task-info.yaml @@ -0,0 +1,7 @@ +type: theory +custom_name: Smart Pointers Summary +files: + - name: CMakeLists.txt + visible: false + - name: src/main.cpp + visible: true diff --git a/ObjectOrientedProgramming/MemoryOwnership/SmartPointersSummary/task-remote-info.yaml b/ObjectOrientedProgramming/MemoryOwnership/SmartPointersSummary/task-remote-info.yaml new file mode 100644 index 0000000..77e46d2 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/SmartPointersSummary/task-remote-info.yaml @@ -0,0 +1 @@ +id: 911313023 diff --git a/ObjectOrientedProgramming/MemoryOwnership/SmartPointersSummary/task.md b/ObjectOrientedProgramming/MemoryOwnership/SmartPointersSummary/task.md new file mode 100644 index 0000000..8a9c490 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/SmartPointersSummary/task.md @@ -0,0 +1,12 @@ +To summarize the differences between different types of pointers, let's take a look at the table: + +| Feature | `T*` | `std::unique_ptr` | `std::shared_ptr` | `std::weak_ptr` | +|-------------------------------------|-------------------------------|-----------------------------------------|--------------------------------------------------|------------------------------------------------------------| +| **Ownership Semantics** | Manual ownership management | Unique ownership | Shared ownership | Non-intrusive observer, no ownership | +| **Memory Management** | Manual deallocation required | Automatic deallocation | Automatic deallocation | No deallocation responsibility | +| **Copyable** | Copyable (shallow copy) | Movable (transfer ownership) | Copyable (shared ownership) | Copyable (shared ownership) | +| **Reference Counting** | No | No | Yes | Yes | +| **Circular Dependency Resolution** | N/A | N/A | Resolved using weak_ptr | Resolved using weak_ptr | +| **Use Case** | Low-level memory manipulation | Exclusive ownership, non-transferable | Shared ownership, multiple references | Observing shared ownership without affecting lifetime | +| **Common Usage** | Basic pointer usage | Scoped ownership, avoiding memory leaks | Managing shared resources, avoiding memory leaks | Breaking circular dependencies, observing shared ownership | +| **Automatic Cleanup on Scope Exit** | No | Yes | Yes | N/A (no ownership) | diff --git a/ObjectOrientedProgramming/MemoryOwnership/UniquePtr/CMakeLists.txt b/ObjectOrientedProgramming/MemoryOwnership/UniquePtr/CMakeLists.txt new file mode 100644 index 0000000..37423d2 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/UniquePtr/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.26) + +project(ObjectOrientedProgramming-MemoryOwnership-UniquePtr) + +set(CMAKE_CXX_STANDARD 14) + +# Files from `./src` directory +set(SRC src/task.cpp) + +# Files from `./test` directory +set(TEST test/test.cpp) + + +# Running learner side code +# Use PROJECT_NAME dependent names of targets for the plugin support to work correctly. +add_executable(${PROJECT_NAME}-run ${SRC}) + +# Running tests +# Use PROJECT_NAME dependent names of targets for the plugin support to work correctly. +configure_test_target(${PROJECT_NAME}-test ${SRC} ${TEST}) \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/UniquePtr/src/task.cpp b/ObjectOrientedProgramming/MemoryOwnership/UniquePtr/src/task.cpp new file mode 100644 index 0000000..c87f2d9 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/UniquePtr/src/task.cpp @@ -0,0 +1,26 @@ +#include +#include + +std::unique_ptr copy(const int* array, size_t size) { + if (size == 0) { + return nullptr; + } + std::unique_ptr ptr = std::make_unique(size); + for (size_t i = 0; i < size; ++i) { + ptr[i] = array[i]; + } + return ptr; +} + +int main() { + const size_t size = 10; + int array[size]; + for (int i = 0; i < size; i++) { + array[i] = i + 1; + } + std::unique_ptr ptr = copy(array, size); + for (int i = 0; i < size; i++) { + std::cout << ptr[i] << std::endl; + } + return 0; +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/UniquePtr/task-info.yaml b/ObjectOrientedProgramming/MemoryOwnership/UniquePtr/task-info.yaml new file mode 100644 index 0000000..4a4fc26 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/UniquePtr/task-info.yaml @@ -0,0 +1,13 @@ +type: edu +custom_name: Unique Pointer +files: +- name: CMakeLists.txt + visible: false +- name: test/test.cpp + visible: false +- name: src/task.cpp + visible: true + placeholders: + - offset: 104 + length: 199 + placeholder_text: return nullptr; diff --git a/ObjectOrientedProgramming/MemoryOwnership/UniquePtr/task-remote-info.yaml b/ObjectOrientedProgramming/MemoryOwnership/UniquePtr/task-remote-info.yaml new file mode 100644 index 0000000..929e0c5 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/UniquePtr/task-remote-info.yaml @@ -0,0 +1 @@ +id: 742587605 diff --git a/ObjectOrientedProgramming/MemoryOwnership/UniquePtr/task.md b/ObjectOrientedProgramming/MemoryOwnership/UniquePtr/task.md new file mode 100644 index 0000000..8e3e098 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/UniquePtr/task.md @@ -0,0 +1,166 @@ +The first kind of smart pointers in C++ we are going to look at is +[std::unique_ptr](https://en.cppreference.com/w/cpp/memory/unique_ptr). + +This smart pointer is designed to manage the lifetime of +dynamically allocated objects and provides exclusive ownership semantics. +The unique ownership model ensures that at any given time, +only one `std::unique_ptr` instance owns a particular dynamically allocated object. +When the owning `std::unique_ptr` is destroyed or explicitly reset, +the pointed-to object is automatically destroyed and +the associated memory is deallocated. + +Another advantage of `std::unique_ptr` is that it helps to prevent +memory errors, such as memory leaks and double deletions. +The `std::unique_ptr` ensures that an object will be deleted +when it is no longer needed and that it will be deleted only once. + +In addition, the usage of `std::unique_ptr` improves code readability +by making clear which pointer owns an object and is responsible for deleting it. + +Let us look at some examples. +Suppose we have a class `Dog` defined as follows: + +```c++ +class Dog { +public: + explicit Dog(const std::string& name) : name(name) {} + ~Dog() {} + + std::string bark() const { + std::cout << "Woof!\n"; + } +private: + std::string name; +}; +``` + +The following code snippet demonstrates how to create a new `Dog` object owned by a `unique_ptr`: + +```c++ +void makeBark() { + // creating a unique pointer + std::unique_ptr dog = std::make_unique("Snoopy"); + // you can test if the unique pointer is a null pointer, + // similar to how you would do with a plain pointer + if (!dog) { + return; + } + // unique pointer can be used + // similar to how plain pointers are used + dog->bark(); + // when the function exits, + // the unique_ptr automatically destroys + // the Dog object and deallocated the memory +} +``` + +
+ +How is automated destruction of the pointed-to object +by the `std::unique_ptr` achieved? +In fact, the standard library's implementation of the `std::unique_ptr` class +simply overrides the destructor of this class. + +
+ +Alternatively, one can explicitly reset the pointer, +thus triggering the deletion of the pointed-to object: + +```c++ +std::unique_ptr dog = std::make_unique("Snoopy"); +dog->bark(); +// at this point, the pointer will be reset to null, +// and the pointed-to object will be destroyed +dog.reset(); +assert(dog == nullptr); +} +``` + +It is also possible to transfer an existing plain pointer +to a new `std::unique_ptr`: + +```c++ +Dog* dog = new Dog("Snoopy"); +// ownership of the Dog object is taken by the unique_ptr, +// it should not be manually deleted, +// as the unique pointer itself will do that +std::unique_ptr smartDog(dog); +``` + +Conversely, it is possible to remove the pointer together with its ownership +from the `std::unique_ptr` command: + +```c++ +std::unique_ptr smartDog = std::make_unique("Snoopy"); +// the Dog object is transferred to the plain pointer, +// it should be manually deleted eventually +// because the unique pointer would not do that +Dog* dog = smartDog.release(); +// after release, the unique pointer is in the null state +assert(smartDog == nullptr); +``` + +It is possible to obtain a plain pointer without releasing ownership. +However, the plain pointer should not outlive the unique pointer; +otherwise, it can result in a use-after-free error. + +```c++ +std::unique_ptr smartDog = std::make_unique("Snoopy"); +// now the dog and smartDog point to the same object; +// ownership still belongs to the smartDog pointer +Dog* dog = smartDog.get(); +``` + +
+ +The `get()` method is typically used to pass a unique pointer +to a function expecting a plain pointer. +This is often the case when C++ code needs to interact with C libraries. + +
+ +Note that by converting plain pointers to unique pointers and vice versa, +using the methods given above, you might accidentally create +two unique pointers pointing to the same object, +thus violating the ownership rules of `std::unique_ptr`. +This would result in undefined behavior: + +```c++ +std::unique_ptr dog = std::make_unique("Snoopy"); +// the dog and anotherDog point to the same object, +// and both incorrectly assume unique ownership of the object; +// thus both can attempt to destroy the object, +// leading to undefined behavior. +std::unique_ptr anotherDog(dog.get()); +``` + +This is why you should be extremely careful when converting +between plain and unique pointers. +As a rule of thumb, try to completely avoid these conversions in your code. + +By default, `std::unique_ptr` attempts to prevent such misuses +and enforce the single ownership rule. +This is why, for example, the copy constructor of `std::unique_ptr` is disabled: + +```c++ +std::unique_ptr dog = std::make_unique("Snoopy"); +// compilation error +std::unique_ptr anotherDog = dog; +``` + +In order to consolidate the material of this lesson, +complete the implementation of the `copy` function. +This function takes an array of integers as an argument, +given as a plain pointer and its size, +and should return a copied array as a unique pointer. +If the given array size is `0`, +then the function should return a null pointer. + +
+ +To create a unique pointer to an array, use the following syntax: +```c++ +std::make_unique(size) +``` + +
\ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/UniquePtr/test/test.cpp b/ObjectOrientedProgramming/MemoryOwnership/UniquePtr/test/test.cpp new file mode 100644 index 0000000..65eb313 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/UniquePtr/test/test.cpp @@ -0,0 +1,23 @@ +#include +#include +#include + +std::unique_ptr copy(const int* array, size_t size); + +TEST(CopyTest, CopyEmpty) { + int a[10]; + ASSERT_EQ(nullptr, copy(a, 0)); +} + +TEST(CopyTest, Copy) { + const size_t SIZE = 10; + int a[SIZE]; + for (int i = 0; i < SIZE; ++i) { + a[i] = rand() % 100; + } + std::unique_ptr p = copy(a, SIZE); + for (int i = 0; i < SIZE; ++i) { + ASSERT_NE(a + i, p.get() + i); + ASSERT_EQ(a[i], p[i]); + } +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/WeakPtr/CMakeLists.txt b/ObjectOrientedProgramming/MemoryOwnership/WeakPtr/CMakeLists.txt new file mode 100644 index 0000000..cbef030 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/WeakPtr/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.26) + +project(ObjectOrientedProgramming-MemoryOwnership-WeakPtr) + +set(CMAKE_CXX_STANDARD 14) + +# Files from `./src` directory +set(SRC src/task.cpp src/user.cpp) + +# Files from `./test` directory +set(TEST test/test.cpp) + +# Use PROJECT_NAME dependent names of targets for the plugin support to work correctly. +add_executable(${PROJECT_NAME}-run ${SRC}) + +# Running tests +# Use PROJECT_NAME dependent names of targets for the plugin support to work correctly. +configure_test_target(${PROJECT_NAME}-test "${SRC}" ${TEST}) \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/WeakPtr/include/chat.hpp b/ObjectOrientedProgramming/MemoryOwnership/WeakPtr/include/chat.hpp new file mode 100644 index 0000000..008cf3a --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/WeakPtr/include/chat.hpp @@ -0,0 +1,37 @@ +#ifndef CPPBASICS_CHAT_HPP +#define CPPBASICS_CHAT_HPP + +#include +#include +#include + +class User; + +class Chat { +public: + Chat(int id, std::string name, const std::shared_ptr& owner) + : id(id) + , name(std::move(name)) + , host(owner) + {}; + + inline int getId() const { + return id; + } + + inline std::string getName() const { + return name; + } + + inline std::shared_ptr getHost() const { + return host.lock(); + } +private: + int id; + std::string name; + std::weak_ptr host; +}; + +std::shared_ptr createNewChat(std::string name, const std::shared_ptr& host); + +#endif // CPPBASICS_CHAT_HPP diff --git a/ObjectOrientedProgramming/MemoryOwnership/WeakPtr/include/user.hpp b/ObjectOrientedProgramming/MemoryOwnership/WeakPtr/include/user.hpp new file mode 100644 index 0000000..9926b41 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/WeakPtr/include/user.hpp @@ -0,0 +1,61 @@ +#ifndef CPPBASICS_USER_HPP +#define CPPBASICS_USER_HPP + +#include +#include +#include + +#include "../include/chat.hpp" + +class User { +public: + explicit User(std::string name) + : id(nextId++) + , name(std::move(name)) + , chat(nullptr) + { + counter++; + }; + + ~User() { + counter--; + } + + User(const User&) = default; + User(User&&) = default; + + User& operator=(const User&) = default; + User& operator=(User&&) = default; + + inline int getId() const { + return id; + } + + inline std::string getName() const { + return name; + } + + inline const std::shared_ptr& getChat() const { + return chat; + } + + inline static int getUserCount() { + return counter; + } + + void joinChatByInvite(const User& user); + + void leaveChat(); + + friend std::shared_ptr createNewChat(std::string name, const std::shared_ptr& host); +private: + int id; + std::string name; + std::shared_ptr chat; + static int nextId; + static int nextChatId; + static int counter; +}; + + +#endif // CPPBASICS_USER_HPP \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/WeakPtr/src/task.cpp b/ObjectOrientedProgramming/MemoryOwnership/WeakPtr/src/task.cpp new file mode 100644 index 0000000..cf8cfeb --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/WeakPtr/src/task.cpp @@ -0,0 +1,23 @@ +#include +#include + +#include "../include/chat.hpp" +#include "../include/user.hpp" + +std::shared_ptr createNewChat(std::string name, const std::shared_ptr& host) { + host->chat = std::make_shared(User::nextChatId++, std::move(name), host); + return host->chat; +} + +int main() { + std::shared_ptr bob = std::make_shared("Bob"); + std::shared_ptr alice = std::make_shared("Alice"); + std::shared_ptr chat = createNewChat("C++ discussion", bob); + alice->joinChatByInvite(*bob); + + std::cout << "Bob is currently in the chat " << bob->getChat()->getName() << "\n"; + std::cout << "Alice is currently in the chat " << alice->getChat()->getName() << "\n"; + std::cout << "Host of the chat " << chat->getName() << "is " << chat->getHost() << "\n"; + + return 0; +} diff --git a/ObjectOrientedProgramming/MemoryOwnership/WeakPtr/src/user.cpp b/ObjectOrientedProgramming/MemoryOwnership/WeakPtr/src/user.cpp new file mode 100644 index 0000000..6d69f31 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/WeakPtr/src/user.cpp @@ -0,0 +1,13 @@ +#include "../include/user.hpp" + +int User::nextId = 0; +int User::nextChatId = 0; +int User::counter = 0; + +void User::joinChatByInvite(const User& user) { + chat = user.chat; +} + +void User::leaveChat() { + chat.reset(); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/WeakPtr/task-info.yaml b/ObjectOrientedProgramming/MemoryOwnership/WeakPtr/task-info.yaml new file mode 100644 index 0000000..e8c982f --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/WeakPtr/task-info.yaml @@ -0,0 +1,31 @@ +type: edu +custom_name: Weak Pointer +files: +- name: CMakeLists.txt + visible: false +- name: src/user.cpp + visible: true + editable: false +- name: include/user.hpp + visible: false + editable: false +- name: include/chat.hpp + visible: true + placeholders: + - offset: 273 + length: 13 + placeholder_text: ", host(owner)" + - offset: 481 + length: 19 + placeholder_text: return host; + - offset: 554 + length: 25 + placeholder_text: std::shared_ptr host; +- name: src/task.cpp + visible: true + placeholders: + - offset: 197 + length: 102 + placeholder_text: return nullptr; +- name: test/test.cpp + visible: false diff --git a/ObjectOrientedProgramming/MemoryOwnership/WeakPtr/task-remote-info.yaml b/ObjectOrientedProgramming/MemoryOwnership/WeakPtr/task-remote-info.yaml new file mode 100644 index 0000000..46ab583 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/WeakPtr/task-remote-info.yaml @@ -0,0 +1 @@ +id: 223242583 diff --git a/ObjectOrientedProgramming/MemoryOwnership/WeakPtr/task.md b/ObjectOrientedProgramming/MemoryOwnership/WeakPtr/task.md new file mode 100644 index 0000000..99aa48a --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/WeakPtr/task.md @@ -0,0 +1,74 @@ +In C++ memory ownership, +[std::weak_ptr](https://en.cppreference.com/w/cpp/memory/weak_ptr) stands out as a tool +for managing transient ownership without affecting the object's lifetime. +While `std::shared_ptr` enables shared ownership, +`std::weak_ptr` complements this by providing a non-intrusive, observer-like role. +This pointer does not contribute to the object's reference count, +allowing for the detection of the object state without extending its lifetime. + +The main use case of `std::weak_ptr` is to break circular reference cycles, +which could otherwise lead to memory leaks. + +Going back the chat example from the previous lesson, +suppose we want to extend the `Chat` class +and add the possibility of assigning a host to the chat. + +For this purpose, we might rework the `Chat` class as follows +(also see the `include/chat.hpp` file): + +```c++ +class Chat { +public: + Chat(int id, std::string name, const std::shared_ptr& owner) + : id(id) + , name(std::move(name)) + , host(owner) + {}; + + /* ... */ +private: + int id; + std::string name; + std::shared_ptr host; +}; +``` + +Also, now, instead of the `User` method `createNewChat`, +we would declare a function with the same name: + +```c++ +std::shared_ptr createNewChat(std::string name, const std::shared_ptr& host); +``` + +This function should take the name of the chat to be created, +as well as the shared pointer to the user who will become its host. + +Now, suppose this function creates a shared pointer `chat`, +pointing to a new `Chat` object, then assigns pointer `chat->host` to `host`, +and `host->chat` to `chat`. +This would result in a reference cycle. +As long as `chat` and `host` store shared pointers to each other, +both their reference counters cannot drop below `1`. +This means that the object will never be deallocated — +in other words, we got a memory leak! + +In order to avoid this, we need to use `std::weak_ptr` to break the reference cycle. +In particular, the `host` field of the `Chat` object should be declared as a weak pointer. + +Note that the method `getHost` of the `Chat` class should still return +a shared pointer: + +```c++ +inline std::shared_ptr getHost() const; +``` + +To achieve this, you need to use the `lock()` method of `std::weak_ptr`. +This method creates a new `std::shared_ptr` pointing to the same object +as the given `std::weak_ptr`, if it still exists; +otherwise, it returns an empty `std::shared_ptr`. + +To complete this lesson, please fix the implementation of +the `Chat` class (see file `include/chat.hpp`) +by using `std::weak_ptr` instead of `std::shared_ptr` for the `host` field. +Also, provide an implementation for the `createNewChat` function +(see file `task.cpp` ). \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/WeakPtr/test/test.cpp b/ObjectOrientedProgramming/MemoryOwnership/WeakPtr/test/test.cpp new file mode 100644 index 0000000..ef5134f --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/WeakPtr/test/test.cpp @@ -0,0 +1,26 @@ +#include +#include + +#include "../include/chat.hpp" +#include "../include/user.hpp" + +TEST(ChatTest, HostTest) { + std::shared_ptr bob = std::make_shared("Bob"); + std::shared_ptr alice = std::make_shared("Alice"); + std::shared_ptr chat = createNewChat("C++ discussion", bob); + alice->joinChatByInvite(*bob); + + ASSERT_EQ(chat, bob->getChat()); + ASSERT_EQ(chat, alice->getChat()); + + ASSERT_EQ(bob, chat->getHost()); + ASSERT_EQ(2, User::getUserCount()); + + alice.reset(); + ASSERT_EQ(bob, chat->getHost()); + ASSERT_EQ(1, User::getUserCount()); + + bob.reset(); + ASSERT_EQ(nullptr, chat->getHost()); + ASSERT_EQ(0, User::getUserCount()); +} \ No newline at end of file diff --git a/ObjectOrientedProgramming/MemoryOwnership/lesson-info.yaml b/ObjectOrientedProgramming/MemoryOwnership/lesson-info.yaml new file mode 100644 index 0000000..ec0e64e --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/lesson-info.yaml @@ -0,0 +1,14 @@ +custom_name: Memory Ownership +content: +- Introduction +- ObjectLifetime +- NewAndDeleteOperators +- PlacementNew +- CopyConstructor +- Ownership +- UniquePtr +- MoveSemantics +- SharedPtr +- WeakPtr +- SmartPointersSummary +- RAIICopySwapIdiom diff --git a/ObjectOrientedProgramming/MemoryOwnership/lesson-remote-info.yaml b/ObjectOrientedProgramming/MemoryOwnership/lesson-remote-info.yaml new file mode 100644 index 0000000..9e2ad20 --- /dev/null +++ b/ObjectOrientedProgramming/MemoryOwnership/lesson-remote-info.yaml @@ -0,0 +1 @@ +id: 1459191764 diff --git a/ObjectOrientedProgramming/section-info.yaml b/ObjectOrientedProgramming/section-info.yaml new file mode 100644 index 0000000..b9778cc --- /dev/null +++ b/ObjectOrientedProgramming/section-info.yaml @@ -0,0 +1,4 @@ +custom_name: Object Oriented Programming +content: +- ClassesAndObjects +- MemoryOwnership diff --git a/ObjectOrientedProgramming/section-remote-info.yaml b/ObjectOrientedProgramming/section-remote-info.yaml new file mode 100644 index 0000000..4025156 --- /dev/null +++ b/ObjectOrientedProgramming/section-remote-info.yaml @@ -0,0 +1 @@ +id: 255032253 diff --git a/README.md b/README.md index c7009d6..a57116c 100644 --- a/README.md +++ b/README.md @@ -83,5 +83,27 @@ with each module covering specific topics and aspects of the C++ language. * type cast operators: C style casts, `static_cast`, `reinterpret_cast` * C style strings -* __TBA__ ... +* __Object-Oriented Programming and Ownership Semantics__ + * operators overloading + * classes and objects + * class fields and methods, `virtual` methods + * abstract classes and interfaces + * inheritance, polymorphism, and encapsulation + * visibility modifiers: `public`, `protected`, and `private` + * class invariants + * `static` members + * `class` vs `struct`, plain old data types (POD) + * constructors and destructors + * `explicit` constructor + * object's lifetime, storage duration vs lifetime + * `new` and `delete` operators + * placement `new` operator + * copy and move constructors + * copy and move assignment operators + * copy-and-swap idiom + * ownership and move semantics + * resource acquisition is initialization idiom (RAII) + * smart pointers: `std::unique_ptr`, `std::shared_ptr`, and `std::weak_ptr` + +* __TBA__ ... diff --git a/WarmUp/HelloWorld/TypesOfVariables/task.md b/WarmUp/HelloWorld/TypesOfVariables/task.md index af80e60..5abb1e2 100644 --- a/WarmUp/HelloWorld/TypesOfVariables/task.md +++ b/WarmUp/HelloWorld/TypesOfVariables/task.md @@ -33,7 +33,7 @@ If the variable is uninitialized, then its value before the point of its first assignment is *undefined*. Uninitialized variables are unpleasant errors (also known as *bugs*) in programs. -Thus, it is very **important** to **always** initialize variables +Thus, it is very **important** to **always** activate variables (there might be some rare exceptions to this rule, which we are not going to discuss now). @@ -57,7 +57,7 @@ of some types are actually initialized by default to some predefined value. An example of such type is `std::string`. Variables of this type are initialized with an empty string `""` automatically upon declaration, and thus they do not require -manual initialization, except if you want to initialize them +manual initialization, except if you want to activate them with some other value. We will learn more about types in C++ later in this course. diff --git a/WarmUp/HelloWorld/Welcome/task-info.yaml b/WarmUp/HelloWorld/Welcome/task-info.yaml index 4318276..0f0ebb8 100644 --- a/WarmUp/HelloWorld/Welcome/task-info.yaml +++ b/WarmUp/HelloWorld/Welcome/task-info.yaml @@ -5,4 +5,4 @@ files: visible: false - name: src/main.cpp visible: true -feedback_link: https://docs.google.com/forms/d/e/1FAIpQLScfBp0gzdxWOmXvQVdyNmeO1od7CG7zxLgNUP4LzKxLBCzkhQ/viewform?usp=pp_url&entry.2103429047=Warm+Up/Hello+World!/Welcome \ No newline at end of file +feedback_link: https://docs.google.com/forms/d/e/1FAIpQLScfBp0gzdxWOmXvQVdyNmeO1od7CG7zxLgNUP4LzKxLBCzkhQ/viewform?usp=pp_url&entry.2103429047=Warm+Up/Hello+World!/Welcome diff --git a/WarmUp/MovingOn/ALittleSpontaneity/src/borders.cpp b/WarmUp/MovingOn/ALittleSpontaneity/src/borders.cpp index b5ca906..c9df954 100644 --- a/WarmUp/MovingOn/ALittleSpontaneity/src/borders.cpp +++ b/WarmUp/MovingOn/ALittleSpontaneity/src/borders.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" Point2D adjustToBorders(Point2D position) { Point2D result = position; diff --git a/WarmUp/MovingOn/ALittleSpontaneity/src/collision.cpp b/WarmUp/MovingOn/ALittleSpontaneity/src/collision.cpp index 639aad3..449016a 100644 --- a/WarmUp/MovingOn/ALittleSpontaneity/src/collision.cpp +++ b/WarmUp/MovingOn/ALittleSpontaneity/src/collision.cpp @@ -1,6 +1,6 @@ #include -#include "scene.hpp" +#include "game.hpp" float distance(Point2D a, Point2D b) { float dx = a.x - b.x; diff --git a/WarmUp/MovingOn/ALittleSpontaneity/src/direction.cpp b/WarmUp/MovingOn/ALittleSpontaneity/src/direction.cpp index 2195184..ad7c2bd 100644 --- a/WarmUp/MovingOn/ALittleSpontaneity/src/direction.cpp +++ b/WarmUp/MovingOn/ALittleSpontaneity/src/direction.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" Point2D getDirection(Direction direction) { switch (direction) { diff --git a/WarmUp/MovingOn/ALittleSpontaneity/src/generate.cpp b/WarmUp/MovingOn/ALittleSpontaneity/src/generate.cpp index 8b854eb..bc95378 100644 --- a/WarmUp/MovingOn/ALittleSpontaneity/src/generate.cpp +++ b/WarmUp/MovingOn/ALittleSpontaneity/src/generate.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" #include diff --git a/WarmUp/MovingOn/ALittleSpontaneity/src/main.cpp b/WarmUp/MovingOn/ALittleSpontaneity/src/main.cpp index 0a7a693..27e36f3 100644 --- a/WarmUp/MovingOn/ALittleSpontaneity/src/main.cpp +++ b/WarmUp/MovingOn/ALittleSpontaneity/src/main.cpp @@ -1,6 +1,6 @@ #include -#include "scene.hpp" +#include "game.hpp" void processEvent(sf::RenderWindow& window, const sf::Event& event) { switch (event.type) { diff --git a/WarmUp/MovingOn/ALittleSpontaneity/src/point.cpp b/WarmUp/MovingOn/ALittleSpontaneity/src/point.cpp index 6e55434..5c74f69 100644 --- a/WarmUp/MovingOn/ALittleSpontaneity/src/point.cpp +++ b/WarmUp/MovingOn/ALittleSpontaneity/src/point.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" Point2D add(Point2D a, Point2D b) { Point2D c = { 0, 0 }; diff --git a/WarmUp/MovingOn/ALittleSpontaneity/task-info.yaml b/WarmUp/MovingOn/ALittleSpontaneity/task-info.yaml index 0fdbc5e..99d8e53 100644 --- a/WarmUp/MovingOn/ALittleSpontaneity/task-info.yaml +++ b/WarmUp/MovingOn/ALittleSpontaneity/task-info.yaml @@ -4,52 +4,52 @@ files: - name: src/generate.cpp visible: true placeholders: - - offset: 121 + - offset: 120 length: 197 placeholder_text: /* TODO */ - - offset: 394 + - offset: 393 length: 49 placeholder_text: return min; - name: src/collision.cpp visible: true placeholders: - - offset: 83 - length: 83 - placeholder_text: return 0.0f; - dependency: - section: WarmUp - lesson: MovingOn - task: ConsumeAnObject - file: src/collision.cpp - placeholder: 1 - is_visible: false - - offset: 223 - length: 82 - placeholder_text: return false; - dependency: - section: WarmUp - lesson: MovingOn - task: ConsumeAnObject - file: src/collision.cpp - placeholder: 2 - is_visible: false + - offset: 82 + length: 83 + placeholder_text: return 0.0f; + dependency: + section: WarmUp + lesson: MovingOn + task: ConsumeAnObject + file: src/collision.cpp + placeholder: 1 + is_visible: false + - offset: 222 + length: 82 + placeholder_text: return false; + dependency: + section: WarmUp + lesson: MovingOn + task: ConsumeAnObject + file: src/collision.cpp + placeholder: 2 + is_visible: false - name: src/borders.cpp visible: true placeholders: - - offset: 101 - length: 359 - placeholder_text: /* TODO */ - dependency: - section: WarmUp - lesson: MovingOn - task: ConsumeAnObject - file: src/borders.cpp - placeholder: 1 - is_visible: false + - offset: 100 + length: 359 + placeholder_text: /* TODO */ + dependency: + section: WarmUp + lesson: MovingOn + task: ConsumeAnObject + file: src/borders.cpp + placeholder: 1 + is_visible: false - name: src/direction.cpp visible: true placeholders: - - offset: 70 + - offset: 69 length: 304 placeholder_text: "return { 0.0f, 0.0f };" dependency: @@ -62,53 +62,53 @@ files: - name: src/point.cpp visible: true placeholders: - - offset: 88 - length: 37 - placeholder_text: /* TODO */ - dependency: - section: WarmUp - lesson: MovingOn - task: ConsumeAnObject - file: src/point.cpp - placeholder: 1 - is_visible: false - - offset: 207 - length: 33 - placeholder_text: /* TODO */ - dependency: - section: WarmUp - lesson: MovingOn - task: ConsumeAnObject - file: src/point.cpp - placeholder: 2 - is_visible: false - - offset: 333 - length: 35 - placeholder_text: position - dependency: - section: WarmUp - lesson: MovingOn - task: ConsumeAnObject - file: src/point.cpp - placeholder: 3 - is_visible: false + - offset: 87 + length: 37 + placeholder_text: /* TODO */ + dependency: + section: WarmUp + lesson: MovingOn + task: ConsumeAnObject + file: src/point.cpp + placeholder: 1 + is_visible: false + - offset: 206 + length: 33 + placeholder_text: /* TODO */ + dependency: + section: WarmUp + lesson: MovingOn + task: ConsumeAnObject + file: src/point.cpp + placeholder: 2 + is_visible: false + - offset: 332 + length: 35 + placeholder_text: position + dependency: + section: WarmUp + lesson: MovingOn + task: ConsumeAnObject + file: src/point.cpp + placeholder: 3 + is_visible: false - name: src/move.cpp visible: true placeholders: - - offset: 69 - length: 27 - placeholder_text: position - dependency: - section: WarmUp - lesson: MovingOn - task: ConsumeAnObject - file: src/move.cpp - placeholder: 1 - is_visible: false + - offset: 69 + length: 27 + placeholder_text: position + dependency: + section: WarmUp + lesson: MovingOn + task: ConsumeAnObject + file: src/move.cpp + placeholder: 1 + is_visible: false +- name: test/test.cpp + visible: false - name: src/main.cpp visible: true - name: CMakeLists.txt visible: false -- name: test/test.cpp - visible: false feedback_link: https://docs.google.com/forms/d/e/1FAIpQLScfBp0gzdxWOmXvQVdyNmeO1od7CG7zxLgNUP4LzKxLBCzkhQ/viewform?usp=pp_url&entry.2103429047=Warm+Up/Moving+On/A+Little+Spontaneity diff --git a/WarmUp/MovingOn/ALittleSpontaneity/test/test.cpp b/WarmUp/MovingOn/ALittleSpontaneity/test/test.cpp index af50514..4f7a151 100644 --- a/WarmUp/MovingOn/ALittleSpontaneity/test/test.cpp +++ b/WarmUp/MovingOn/ALittleSpontaneity/test/test.cpp @@ -1,6 +1,6 @@ #include -#include "scene.hpp" +#include "game.hpp" #include "operators.hpp" #include "testing.hpp" diff --git a/WarmUp/MovingOn/ConsumeAnObject/src/borders.cpp b/WarmUp/MovingOn/ConsumeAnObject/src/borders.cpp index b5ca906..c9df954 100644 --- a/WarmUp/MovingOn/ConsumeAnObject/src/borders.cpp +++ b/WarmUp/MovingOn/ConsumeAnObject/src/borders.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" Point2D adjustToBorders(Point2D position) { Point2D result = position; diff --git a/WarmUp/MovingOn/ConsumeAnObject/src/collision.cpp b/WarmUp/MovingOn/ConsumeAnObject/src/collision.cpp index 639aad3..449016a 100644 --- a/WarmUp/MovingOn/ConsumeAnObject/src/collision.cpp +++ b/WarmUp/MovingOn/ConsumeAnObject/src/collision.cpp @@ -1,6 +1,6 @@ #include -#include "scene.hpp" +#include "game.hpp" float distance(Point2D a, Point2D b) { float dx = a.x - b.x; diff --git a/WarmUp/MovingOn/ConsumeAnObject/src/direction.cpp b/WarmUp/MovingOn/ConsumeAnObject/src/direction.cpp index 2195184..ad7c2bd 100644 --- a/WarmUp/MovingOn/ConsumeAnObject/src/direction.cpp +++ b/WarmUp/MovingOn/ConsumeAnObject/src/direction.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" Point2D getDirection(Direction direction) { switch (direction) { diff --git a/WarmUp/MovingOn/ConsumeAnObject/src/main.cpp b/WarmUp/MovingOn/ConsumeAnObject/src/main.cpp index dfa57c6..083ffd1 100644 --- a/WarmUp/MovingOn/ConsumeAnObject/src/main.cpp +++ b/WarmUp/MovingOn/ConsumeAnObject/src/main.cpp @@ -1,6 +1,6 @@ #include -#include "scene.hpp" +#include "game.hpp" void processEvent(sf::RenderWindow& window, const sf::Event& event) { switch (event.type) { diff --git a/WarmUp/MovingOn/ConsumeAnObject/src/point.cpp b/WarmUp/MovingOn/ConsumeAnObject/src/point.cpp index 6e55434..5c74f69 100644 --- a/WarmUp/MovingOn/ConsumeAnObject/src/point.cpp +++ b/WarmUp/MovingOn/ConsumeAnObject/src/point.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" Point2D add(Point2D a, Point2D b) { Point2D c = { 0, 0 }; diff --git a/WarmUp/MovingOn/ConsumeAnObject/task-info.yaml b/WarmUp/MovingOn/ConsumeAnObject/task-info.yaml index b5fd3c6..4177a4f 100644 --- a/WarmUp/MovingOn/ConsumeAnObject/task-info.yaml +++ b/WarmUp/MovingOn/ConsumeAnObject/task-info.yaml @@ -4,16 +4,16 @@ files: - name: src/collision.cpp visible: true placeholders: - - offset: 83 - length: 83 - placeholder_text: return 0.0f; - - offset: 223 - length: 82 - placeholder_text: return false; + - offset: 82 + length: 83 + placeholder_text: return 0.0f; + - offset: 222 + length: 82 + placeholder_text: return false; - name: src/borders.cpp visible: true placeholders: - - offset: 101 + - offset: 100 length: 359 placeholder_text: /* TODO */ dependency: @@ -26,7 +26,7 @@ files: - name: src/direction.cpp visible: true placeholders: - - offset: 70 + - offset: 69 length: 304 placeholder_text: "return { 0.0f, 0.0f };" dependency: @@ -39,7 +39,7 @@ files: - name: src/point.cpp visible: true placeholders: - - offset: 88 + - offset: 87 length: 37 placeholder_text: /* TODO */ dependency: @@ -49,7 +49,7 @@ files: file: src/point.cpp placeholder: 1 is_visible: false - - offset: 207 + - offset: 206 length: 33 placeholder_text: /* TODO */ dependency: @@ -59,7 +59,7 @@ files: file: src/point.cpp placeholder: 2 is_visible: false - - offset: 333 + - offset: 332 length: 35 placeholder_text: position dependency: @@ -72,20 +72,20 @@ files: - name: src/move.cpp visible: true placeholders: - - offset: 69 - length: 27 - placeholder_text: position - dependency: - section: WarmUp - lesson: MovingOn - task: DrawALine - file: src/move.cpp - placeholder: 1 - is_visible: false + - offset: 69 + length: 27 + placeholder_text: position + dependency: + section: WarmUp + lesson: MovingOn + task: DrawALine + file: src/move.cpp + placeholder: 1 + is_visible: false +- name: test/test.cpp + visible: false - name: src/main.cpp visible: true - name: CMakeLists.txt visible: false -- name: test/test.cpp - visible: false feedback_link: https://docs.google.com/forms/d/e/1FAIpQLScfBp0gzdxWOmXvQVdyNmeO1od7CG7zxLgNUP4LzKxLBCzkhQ/viewform?usp=pp_url&entry.2103429047=Warm+Up/Moving+On/Consume+an+Object diff --git a/WarmUp/MovingOn/ConsumeAnObject/test/test.cpp b/WarmUp/MovingOn/ConsumeAnObject/test/test.cpp index c7ca914..9ac85eb 100644 --- a/WarmUp/MovingOn/ConsumeAnObject/test/test.cpp +++ b/WarmUp/MovingOn/ConsumeAnObject/test/test.cpp @@ -2,7 +2,7 @@ #include -#include "scene.hpp" +#include "game.hpp" #include "operators.hpp" #include "testing.hpp" diff --git a/WarmUp/MovingOn/DrawALine/src/borders.cpp b/WarmUp/MovingOn/DrawALine/src/borders.cpp index b5ca906..c9df954 100644 --- a/WarmUp/MovingOn/DrawALine/src/borders.cpp +++ b/WarmUp/MovingOn/DrawALine/src/borders.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" Point2D adjustToBorders(Point2D position) { Point2D result = position; diff --git a/WarmUp/MovingOn/DrawALine/src/direction.cpp b/WarmUp/MovingOn/DrawALine/src/direction.cpp index 2195184..ad7c2bd 100644 --- a/WarmUp/MovingOn/DrawALine/src/direction.cpp +++ b/WarmUp/MovingOn/DrawALine/src/direction.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" Point2D getDirection(Direction direction) { switch (direction) { diff --git a/WarmUp/MovingOn/DrawALine/src/main.cpp b/WarmUp/MovingOn/DrawALine/src/main.cpp index 20c558f..b434a92 100644 --- a/WarmUp/MovingOn/DrawALine/src/main.cpp +++ b/WarmUp/MovingOn/DrawALine/src/main.cpp @@ -1,6 +1,6 @@ #include -#include "scene.hpp" +#include "game.hpp" void processEvent(sf::RenderWindow& window, const sf::Event& event) { switch (event.type) { diff --git a/WarmUp/MovingOn/DrawALine/src/point.cpp b/WarmUp/MovingOn/DrawALine/src/point.cpp index 6e55434..5c74f69 100644 --- a/WarmUp/MovingOn/DrawALine/src/point.cpp +++ b/WarmUp/MovingOn/DrawALine/src/point.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" Point2D add(Point2D a, Point2D b) { Point2D c = { 0, 0 }; diff --git a/WarmUp/MovingOn/DrawALine/task-info.yaml b/WarmUp/MovingOn/DrawALine/task-info.yaml index a42e11d..b937bdd 100644 --- a/WarmUp/MovingOn/DrawALine/task-info.yaml +++ b/WarmUp/MovingOn/DrawALine/task-info.yaml @@ -4,13 +4,13 @@ files: - name: src/borders.cpp visible: true placeholders: - - offset: 101 + - offset: 100 length: 359 placeholder_text: /* TODO */ - name: src/direction.cpp visible: true placeholders: - - offset: 70 + - offset: 69 length: 304 placeholder_text: "return { 0.0f, 0.0f };" dependency: @@ -23,7 +23,7 @@ files: - name: src/point.cpp visible: true placeholders: - - offset: 88 + - offset: 87 length: 37 placeholder_text: /* TODO */ dependency: @@ -33,7 +33,7 @@ files: file: src/point.cpp placeholder: 1 is_visible: false - - offset: 207 + - offset: 206 length: 33 placeholder_text: /* TODO */ dependency: @@ -43,7 +43,7 @@ files: file: src/point.cpp placeholder: 2 is_visible: false - - offset: 333 + - offset: 332 length: 35 placeholder_text: position dependency: @@ -56,20 +56,20 @@ files: - name: src/move.cpp visible: true placeholders: - - offset: 69 - length: 27 - placeholder_text: position - dependency: - section: WarmUp - lesson: MovingOn - task: GivingDirection - file: src/move.cpp - placeholder: 1 - is_visible: false + - offset: 69 + length: 27 + placeholder_text: position + dependency: + section: WarmUp + lesson: MovingOn + task: GivingDirection + file: src/move.cpp + placeholder: 1 + is_visible: false +- name: test/test.cpp + visible: false - name: src/main.cpp visible: true - name: CMakeLists.txt visible: false -- name: test/test.cpp - visible: false feedback_link: https://docs.google.com/forms/d/e/1FAIpQLScfBp0gzdxWOmXvQVdyNmeO1od7CG7zxLgNUP4LzKxLBCzkhQ/viewform?usp=pp_url&entry.2103429047=Warm+Up/Moving+On/Draw+a+Line diff --git a/WarmUp/MovingOn/DrawALine/test/test.cpp b/WarmUp/MovingOn/DrawALine/test/test.cpp index bef66e9..5358582 100644 --- a/WarmUp/MovingOn/DrawALine/test/test.cpp +++ b/WarmUp/MovingOn/DrawALine/test/test.cpp @@ -1,6 +1,6 @@ #include -#include "scene.hpp" +#include "game.hpp" #include "operators.hpp" #include "testing.hpp" diff --git a/WarmUp/MovingOn/GivingDirection/src/direction.cpp b/WarmUp/MovingOn/GivingDirection/src/direction.cpp index 2195184..ad7c2bd 100644 --- a/WarmUp/MovingOn/GivingDirection/src/direction.cpp +++ b/WarmUp/MovingOn/GivingDirection/src/direction.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" Point2D getDirection(Direction direction) { switch (direction) { diff --git a/WarmUp/MovingOn/GivingDirection/src/main.cpp b/WarmUp/MovingOn/GivingDirection/src/main.cpp index ec02486..f621b03 100644 --- a/WarmUp/MovingOn/GivingDirection/src/main.cpp +++ b/WarmUp/MovingOn/GivingDirection/src/main.cpp @@ -1,6 +1,6 @@ #include -#include "scene.hpp" +#include "game.hpp" void processEvent(sf::RenderWindow& window, const sf::Event& event) { switch (event.type) { diff --git a/WarmUp/MovingOn/GivingDirection/src/point.cpp b/WarmUp/MovingOn/GivingDirection/src/point.cpp index 6e55434..5c74f69 100644 --- a/WarmUp/MovingOn/GivingDirection/src/point.cpp +++ b/WarmUp/MovingOn/GivingDirection/src/point.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" Point2D add(Point2D a, Point2D b) { Point2D c = { 0, 0 }; diff --git a/WarmUp/MovingOn/GivingDirection/task-info.yaml b/WarmUp/MovingOn/GivingDirection/task-info.yaml index ef79e56..d8b77bf 100644 --- a/WarmUp/MovingOn/GivingDirection/task-info.yaml +++ b/WarmUp/MovingOn/GivingDirection/task-info.yaml @@ -4,13 +4,13 @@ files: - name: src/direction.cpp visible: true placeholders: - - offset: 70 + - offset: 69 length: 304 placeholder_text: "return { 0.0f, 0.0f };" - name: src/point.cpp visible: true placeholders: - - offset: 88 + - offset: 87 length: 37 placeholder_text: /* TODO */ dependency: @@ -20,7 +20,7 @@ files: file: src/point.cpp placeholder: 1 is_visible: false - - offset: 207 + - offset: 206 length: 33 placeholder_text: /* TODO */ dependency: @@ -30,7 +30,7 @@ files: file: src/point.cpp placeholder: 2 is_visible: false - - offset: 333 + - offset: 332 length: 35 placeholder_text: position dependency: @@ -43,20 +43,20 @@ files: - name: src/move.cpp visible: true placeholders: - - offset: 69 - length: 27 - placeholder_text: position - dependency: - section: WarmUp - lesson: MovingOn - task: MovingUp - file: src/move.cpp - placeholder: 1 - is_visible: false + - offset: 69 + length: 27 + placeholder_text: position + dependency: + section: WarmUp + lesson: MovingOn + task: MovingUp + file: src/move.cpp + placeholder: 1 + is_visible: false +- name: test/test.cpp + visible: false - name: src/main.cpp visible: true - name: CMakeLists.txt visible: false -- name: test/test.cpp - visible: false feedback_link: https://docs.google.com/forms/d/e/1FAIpQLScfBp0gzdxWOmXvQVdyNmeO1od7CG7zxLgNUP4LzKxLBCzkhQ/viewform?usp=pp_url&entry.2103429047=Warm+Up/Moving+On/Giving+Direction diff --git a/WarmUp/MovingOn/GivingDirection/test/test.cpp b/WarmUp/MovingOn/GivingDirection/test/test.cpp index 63d463c..85246d8 100644 --- a/WarmUp/MovingOn/GivingDirection/test/test.cpp +++ b/WarmUp/MovingOn/GivingDirection/test/test.cpp @@ -1,6 +1,6 @@ #include -#include "scene.hpp" +#include "game.hpp" #include "operators.hpp" #include "testing.hpp" diff --git a/WarmUp/MovingOn/HavingFun/src/main.cpp b/WarmUp/MovingOn/HavingFun/src/main.cpp index af46561..df459f4 100644 --- a/WarmUp/MovingOn/HavingFun/src/main.cpp +++ b/WarmUp/MovingOn/HavingFun/src/main.cpp @@ -2,7 +2,7 @@ #include -#include "scene.hpp" +#include "game.hpp" void processEvent(sf::RenderWindow& window, const sf::Event& event) { switch (event.type) diff --git a/WarmUp/MovingOn/HavingFun/task-info.yaml b/WarmUp/MovingOn/HavingFun/task-info.yaml index a9fd962..197c1bb 100644 --- a/WarmUp/MovingOn/HavingFun/task-info.yaml +++ b/WarmUp/MovingOn/HavingFun/task-info.yaml @@ -14,9 +14,8 @@ files: file: src/move.cpp placeholder: 1 is_visible: false -- name: src/main.cpp - visible: true - editable: true - name: CMakeLists.txt visible: false +- name: src/main.cpp + visible: true feedback_link: https://docs.google.com/forms/d/e/1FAIpQLScfBp0gzdxWOmXvQVdyNmeO1od7CG7zxLgNUP4LzKxLBCzkhQ/viewform?usp=pp_url&entry.2103429047=Warm+Up/Moving+On/Having+Fun diff --git a/WarmUp/MovingOn/HuntingBugs/src/approaching.cpp b/WarmUp/MovingOn/HuntingBugs/src/approaching.cpp index ba0307c..12404e0 100644 --- a/WarmUp/MovingOn/HuntingBugs/src/approaching.cpp +++ b/WarmUp/MovingOn/HuntingBugs/src/approaching.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" void approachingLoop(Circle player, Circle consumable[], bool concerned[], int size) { for (int i = 0; i < size; ++i) { diff --git a/WarmUp/MovingOn/HuntingBugs/src/borders.cpp b/WarmUp/MovingOn/HuntingBugs/src/borders.cpp index b5ca906..c9df954 100644 --- a/WarmUp/MovingOn/HuntingBugs/src/borders.cpp +++ b/WarmUp/MovingOn/HuntingBugs/src/borders.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" Point2D adjustToBorders(Point2D position) { Point2D result = position; diff --git a/WarmUp/MovingOn/HuntingBugs/src/collision.cpp b/WarmUp/MovingOn/HuntingBugs/src/collision.cpp index 639aad3..449016a 100644 --- a/WarmUp/MovingOn/HuntingBugs/src/collision.cpp +++ b/WarmUp/MovingOn/HuntingBugs/src/collision.cpp @@ -1,6 +1,6 @@ #include -#include "scene.hpp" +#include "game.hpp" float distance(Point2D a, Point2D b) { float dx = a.x - b.x; diff --git a/WarmUp/MovingOn/HuntingBugs/src/direction.cpp b/WarmUp/MovingOn/HuntingBugs/src/direction.cpp index 2195184..ad7c2bd 100644 --- a/WarmUp/MovingOn/HuntingBugs/src/direction.cpp +++ b/WarmUp/MovingOn/HuntingBugs/src/direction.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" Point2D getDirection(Direction direction) { switch (direction) { diff --git a/WarmUp/MovingOn/HuntingBugs/src/generate.cpp b/WarmUp/MovingOn/HuntingBugs/src/generate.cpp index 8b854eb..bc95378 100644 --- a/WarmUp/MovingOn/HuntingBugs/src/generate.cpp +++ b/WarmUp/MovingOn/HuntingBugs/src/generate.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" #include diff --git a/WarmUp/MovingOn/HuntingBugs/src/loop.cpp b/WarmUp/MovingOn/HuntingBugs/src/loop.cpp index 5fe6ce3..8148f4a 100644 --- a/WarmUp/MovingOn/HuntingBugs/src/loop.cpp +++ b/WarmUp/MovingOn/HuntingBugs/src/loop.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" void collisionLoop(Circle player, Circle consumable[], bool consumed[], int size) { for (int i = 0; i < size; ++i) { diff --git a/WarmUp/MovingOn/HuntingBugs/src/main.cpp b/WarmUp/MovingOn/HuntingBugs/src/main.cpp index 2906421..aa8d82c 100644 --- a/WarmUp/MovingOn/HuntingBugs/src/main.cpp +++ b/WarmUp/MovingOn/HuntingBugs/src/main.cpp @@ -1,6 +1,6 @@ #include -#include "scene.hpp" +#include "game.hpp" void processEvent(sf::RenderWindow& window, const sf::Event& event) { switch (event.type) { diff --git a/WarmUp/MovingOn/HuntingBugs/src/point.cpp b/WarmUp/MovingOn/HuntingBugs/src/point.cpp index 6e55434..5c74f69 100644 --- a/WarmUp/MovingOn/HuntingBugs/src/point.cpp +++ b/WarmUp/MovingOn/HuntingBugs/src/point.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" Point2D add(Point2D a, Point2D b) { Point2D c = { 0, 0 }; diff --git a/WarmUp/MovingOn/HuntingBugs/task-info.yaml b/WarmUp/MovingOn/HuntingBugs/task-info.yaml index 96d1bfe..fb97fee 100644 --- a/WarmUp/MovingOn/HuntingBugs/task-info.yaml +++ b/WarmUp/MovingOn/HuntingBugs/task-info.yaml @@ -17,20 +17,20 @@ files: - name: src/loop.cpp visible: true placeholders: - - offset: 110 - length: 136 - placeholder_text: return; - dependency: - section: WarmUp - lesson: MovingOn - task: LimitsOfPossible - file: src/loop.cpp - placeholder: 1 - is_visible: false + - offset: 109 + length: 136 + placeholder_text: return; + dependency: + section: WarmUp + lesson: MovingOn + task: LimitsOfPossible + file: src/loop.cpp + placeholder: 1 + is_visible: false - name: src/generate.cpp visible: true placeholders: - - offset: 121 + - offset: 120 length: 197 placeholder_text: /* TODO */ dependency: @@ -40,7 +40,7 @@ files: file: src/generate.cpp placeholder: 1 is_visible: false - - offset: 394 + - offset: 393 length: 49 placeholder_text: return min; dependency: @@ -53,7 +53,7 @@ files: - name: src/collision.cpp visible: true placeholders: - - offset: 83 + - offset: 82 length: 83 placeholder_text: return 0.0f; dependency: @@ -63,7 +63,7 @@ files: file: src/collision.cpp placeholder: 1 is_visible: false - - offset: 223 + - offset: 222 length: 82 placeholder_text: return false; dependency: @@ -76,7 +76,7 @@ files: - name: src/borders.cpp visible: true placeholders: - - offset: 101 + - offset: 100 length: 359 placeholder_text: /* TODO */ dependency: @@ -89,66 +89,66 @@ files: - name: src/direction.cpp visible: true placeholders: - - offset: 70 - length: 304 - placeholder_text: "return { 0.0f, 0.0f };" - dependency: - section: WarmUp - lesson: MovingOn - task: LimitsOfPossible - file: src/direction.cpp - placeholder: 1 - is_visible: false + - offset: 69 + length: 304 + placeholder_text: "return { 0.0f, 0.0f };" + dependency: + section: WarmUp + lesson: MovingOn + task: LimitsOfPossible + file: src/direction.cpp + placeholder: 1 + is_visible: false - name: src/point.cpp visible: true placeholders: - - offset: 88 - length: 37 - placeholder_text: /* TODO */ - dependency: - section: WarmUp - lesson: MovingOn - task: LimitsOfPossible - file: src/point.cpp - placeholder: 1 - is_visible: false - - offset: 207 - length: 33 - placeholder_text: /* TODO */ - dependency: - section: WarmUp - lesson: MovingOn - task: LimitsOfPossible - file: src/point.cpp - placeholder: 2 - is_visible: false - - offset: 333 - length: 35 - placeholder_text: position - dependency: - section: WarmUp - lesson: MovingOn - task: LimitsOfPossible - file: src/point.cpp - placeholder: 3 - is_visible: false + - offset: 87 + length: 37 + placeholder_text: /* TODO */ + dependency: + section: WarmUp + lesson: MovingOn + task: LimitsOfPossible + file: src/point.cpp + placeholder: 1 + is_visible: false + - offset: 206 + length: 33 + placeholder_text: /* TODO */ + dependency: + section: WarmUp + lesson: MovingOn + task: LimitsOfPossible + file: src/point.cpp + placeholder: 2 + is_visible: false + - offset: 332 + length: 35 + placeholder_text: position + dependency: + section: WarmUp + lesson: MovingOn + task: LimitsOfPossible + file: src/point.cpp + placeholder: 3 + is_visible: false - name: src/move.cpp visible: true placeholders: - - offset: 69 - length: 27 - placeholder_text: position - dependency: - section: WarmUp - lesson: MovingOn - task: LimitsOfPossible - file: src/move.cpp - placeholder: 1 - is_visible: false -- name: CMakeLists.txt - visible: false + - offset: 69 + length: 27 + placeholder_text: position + dependency: + section: WarmUp + lesson: MovingOn + task: LimitsOfPossible + file: src/move.cpp + placeholder: 1 + is_visible: false - name: src/main.cpp visible: true - name: test/test.cpp visible: false +- name: CMakeLists.txt + visible: false feedback_link: https://docs.google.com/forms/d/e/1FAIpQLScfBp0gzdxWOmXvQVdyNmeO1od7CG7zxLgNUP4LzKxLBCzkhQ/viewform?usp=pp_url&entry.2103429047=Warm+Up/Moving+On/Hunting+Bugs diff --git a/WarmUp/MovingOn/HuntingBugs/test/test.cpp b/WarmUp/MovingOn/HuntingBugs/test/test.cpp index 479a2bf..d19aedb 100644 --- a/WarmUp/MovingOn/HuntingBugs/test/test.cpp +++ b/WarmUp/MovingOn/HuntingBugs/test/test.cpp @@ -2,7 +2,7 @@ #include -#include "scene.hpp" +#include "game.hpp" #include "operators.hpp" #include "testing.hpp" diff --git a/WarmUp/MovingOn/KeepSpinning/src/borders.cpp b/WarmUp/MovingOn/KeepSpinning/src/borders.cpp index b5ca906..c9df954 100644 --- a/WarmUp/MovingOn/KeepSpinning/src/borders.cpp +++ b/WarmUp/MovingOn/KeepSpinning/src/borders.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" Point2D adjustToBorders(Point2D position) { Point2D result = position; diff --git a/WarmUp/MovingOn/KeepSpinning/src/collision.cpp b/WarmUp/MovingOn/KeepSpinning/src/collision.cpp index 639aad3..449016a 100644 --- a/WarmUp/MovingOn/KeepSpinning/src/collision.cpp +++ b/WarmUp/MovingOn/KeepSpinning/src/collision.cpp @@ -1,6 +1,6 @@ #include -#include "scene.hpp" +#include "game.hpp" float distance(Point2D a, Point2D b) { float dx = a.x - b.x; diff --git a/WarmUp/MovingOn/KeepSpinning/src/direction.cpp b/WarmUp/MovingOn/KeepSpinning/src/direction.cpp index 2195184..ad7c2bd 100644 --- a/WarmUp/MovingOn/KeepSpinning/src/direction.cpp +++ b/WarmUp/MovingOn/KeepSpinning/src/direction.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" Point2D getDirection(Direction direction) { switch (direction) { diff --git a/WarmUp/MovingOn/KeepSpinning/src/generate.cpp b/WarmUp/MovingOn/KeepSpinning/src/generate.cpp index 8b854eb..bc95378 100644 --- a/WarmUp/MovingOn/KeepSpinning/src/generate.cpp +++ b/WarmUp/MovingOn/KeepSpinning/src/generate.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" #include diff --git a/WarmUp/MovingOn/KeepSpinning/src/loop.cpp b/WarmUp/MovingOn/KeepSpinning/src/loop.cpp index 5fe6ce3..8148f4a 100644 --- a/WarmUp/MovingOn/KeepSpinning/src/loop.cpp +++ b/WarmUp/MovingOn/KeepSpinning/src/loop.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" void collisionLoop(Circle player, Circle consumable[], bool consumed[], int size) { for (int i = 0; i < size; ++i) { diff --git a/WarmUp/MovingOn/KeepSpinning/src/main.cpp b/WarmUp/MovingOn/KeepSpinning/src/main.cpp index 6bdc8bb..b208448 100644 --- a/WarmUp/MovingOn/KeepSpinning/src/main.cpp +++ b/WarmUp/MovingOn/KeepSpinning/src/main.cpp @@ -1,6 +1,6 @@ #include -#include "scene.hpp" +#include "game.hpp" void processEvent(sf::RenderWindow& window, const sf::Event& event) { switch (event.type) { diff --git a/WarmUp/MovingOn/KeepSpinning/src/point.cpp b/WarmUp/MovingOn/KeepSpinning/src/point.cpp index 6e55434..5c74f69 100644 --- a/WarmUp/MovingOn/KeepSpinning/src/point.cpp +++ b/WarmUp/MovingOn/KeepSpinning/src/point.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" Point2D add(Point2D a, Point2D b) { Point2D c = { 0, 0 }; diff --git a/WarmUp/MovingOn/KeepSpinning/task-info.yaml b/WarmUp/MovingOn/KeepSpinning/task-info.yaml index 94cac8a..2602205 100644 --- a/WarmUp/MovingOn/KeepSpinning/task-info.yaml +++ b/WarmUp/MovingOn/KeepSpinning/task-info.yaml @@ -4,79 +4,79 @@ files: - name: src/loop.cpp visible: true placeholders: - - offset: 110 - length: 136 - placeholder_text: return; - dependency: - section: WarmUp - lesson: MovingOn - task: RunningInALoop - file: src/loop.cpp - placeholder: 1 - is_visible: false -- name: src/generate.cpp - visible: true - placeholders: - - offset: 121 - length: 197 - placeholder_text: /* TODO */ + - offset: 109 + length: 136 + placeholder_text: return; dependency: section: WarmUp lesson: MovingOn task: RunningInALoop - file: src/generate.cpp + file: src/loop.cpp placeholder: 1 is_visible: false - - offset: 394 - length: 49 - placeholder_text: return min; - dependency: - section: WarmUp - lesson: MovingOn - task: RunningInALoop - file: src/generate.cpp - placeholder: 2 - is_visible: false -- name: src/collision.cpp +- name: src/generate.cpp visible: true placeholders: - - offset: 83 - length: 83 - placeholder_text: return 0.0f; + - offset: 120 + length: 197 + placeholder_text: /* TODO */ dependency: section: WarmUp lesson: MovingOn task: RunningInALoop - file: src/collision.cpp + file: src/generate.cpp placeholder: 1 is_visible: false - - offset: 223 - length: 82 - placeholder_text: return false; + - offset: 393 + length: 49 + placeholder_text: return min; dependency: section: WarmUp lesson: MovingOn task: RunningInALoop - file: src/collision.cpp + file: src/generate.cpp placeholder: 2 is_visible: false +- name: src/collision.cpp + visible: true + placeholders: + - offset: 82 + length: 83 + placeholder_text: return 0.0f; + dependency: + section: WarmUp + lesson: MovingOn + task: RunningInALoop + file: src/collision.cpp + placeholder: 1 + is_visible: false + - offset: 222 + length: 82 + placeholder_text: return false; + dependency: + section: WarmUp + lesson: MovingOn + task: RunningInALoop + file: src/collision.cpp + placeholder: 2 + is_visible: false - name: src/borders.cpp visible: true placeholders: - - offset: 101 - length: 359 - placeholder_text: /* TODO */ - dependency: - section: WarmUp - lesson: MovingOn - task: RunningInALoop - file: src/borders.cpp - placeholder: 1 - is_visible: false + - offset: 100 + length: 359 + placeholder_text: /* TODO */ + dependency: + section: WarmUp + lesson: MovingOn + task: RunningInALoop + file: src/borders.cpp + placeholder: 1 + is_visible: false - name: src/direction.cpp visible: true placeholders: - - offset: 70 + - offset: 69 length: 304 placeholder_text: "return { 0.0f, 0.0f };" dependency: @@ -89,7 +89,7 @@ files: - name: src/point.cpp visible: true placeholders: - - offset: 88 + - offset: 87 length: 37 placeholder_text: /* TODO */ dependency: @@ -99,7 +99,7 @@ files: file: src/point.cpp placeholder: 1 is_visible: false - - offset: 207 + - offset: 206 length: 33 placeholder_text: /* TODO */ dependency: @@ -109,7 +109,7 @@ files: file: src/point.cpp placeholder: 2 is_visible: false - - offset: 333 + - offset: 332 length: 35 placeholder_text: position dependency: @@ -122,19 +122,18 @@ files: - name: src/move.cpp visible: true placeholders: - - offset: 69 - length: 27 - placeholder_text: position - dependency: - section: WarmUp - lesson: MovingOn - task: RunningInALoop - file: src/move.cpp - placeholder: 1 - is_visible: false + - offset: 69 + length: 27 + placeholder_text: position + dependency: + section: WarmUp + lesson: MovingOn + task: RunningInALoop + file: src/move.cpp + placeholder: 1 + is_visible: false - name: src/main.cpp visible: true - editable: true - name: CMakeLists.txt visible: false feedback_link: https://docs.google.com/forms/d/e/1FAIpQLScfBp0gzdxWOmXvQVdyNmeO1od7CG7zxLgNUP4LzKxLBCzkhQ/viewform?usp=pp_url&entry.2103429047=Warm+Up/Moving+On/Keep+Spinning diff --git a/WarmUp/MovingOn/LimitsOfPossible/src/borders.cpp b/WarmUp/MovingOn/LimitsOfPossible/src/borders.cpp index b5ca906..c9df954 100644 --- a/WarmUp/MovingOn/LimitsOfPossible/src/borders.cpp +++ b/WarmUp/MovingOn/LimitsOfPossible/src/borders.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" Point2D adjustToBorders(Point2D position) { Point2D result = position; diff --git a/WarmUp/MovingOn/LimitsOfPossible/src/collision.cpp b/WarmUp/MovingOn/LimitsOfPossible/src/collision.cpp index 639aad3..449016a 100644 --- a/WarmUp/MovingOn/LimitsOfPossible/src/collision.cpp +++ b/WarmUp/MovingOn/LimitsOfPossible/src/collision.cpp @@ -1,6 +1,6 @@ #include -#include "scene.hpp" +#include "game.hpp" float distance(Point2D a, Point2D b) { float dx = a.x - b.x; diff --git a/WarmUp/MovingOn/LimitsOfPossible/src/direction.cpp b/WarmUp/MovingOn/LimitsOfPossible/src/direction.cpp index 2195184..ad7c2bd 100644 --- a/WarmUp/MovingOn/LimitsOfPossible/src/direction.cpp +++ b/WarmUp/MovingOn/LimitsOfPossible/src/direction.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" Point2D getDirection(Direction direction) { switch (direction) { diff --git a/WarmUp/MovingOn/LimitsOfPossible/src/generate.cpp b/WarmUp/MovingOn/LimitsOfPossible/src/generate.cpp index 8b854eb..bc95378 100644 --- a/WarmUp/MovingOn/LimitsOfPossible/src/generate.cpp +++ b/WarmUp/MovingOn/LimitsOfPossible/src/generate.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" #include diff --git a/WarmUp/MovingOn/LimitsOfPossible/src/loop.cpp b/WarmUp/MovingOn/LimitsOfPossible/src/loop.cpp index 5fe6ce3..8148f4a 100644 --- a/WarmUp/MovingOn/LimitsOfPossible/src/loop.cpp +++ b/WarmUp/MovingOn/LimitsOfPossible/src/loop.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" void collisionLoop(Circle player, Circle consumable[], bool consumed[], int size) { for (int i = 0; i < size; ++i) { diff --git a/WarmUp/MovingOn/LimitsOfPossible/src/main.cpp b/WarmUp/MovingOn/LimitsOfPossible/src/main.cpp index 6bdc8bb..b208448 100644 --- a/WarmUp/MovingOn/LimitsOfPossible/src/main.cpp +++ b/WarmUp/MovingOn/LimitsOfPossible/src/main.cpp @@ -1,6 +1,6 @@ #include -#include "scene.hpp" +#include "game.hpp" void processEvent(sf::RenderWindow& window, const sf::Event& event) { switch (event.type) { diff --git a/WarmUp/MovingOn/LimitsOfPossible/src/point.cpp b/WarmUp/MovingOn/LimitsOfPossible/src/point.cpp index 6e55434..5c74f69 100644 --- a/WarmUp/MovingOn/LimitsOfPossible/src/point.cpp +++ b/WarmUp/MovingOn/LimitsOfPossible/src/point.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" Point2D add(Point2D a, Point2D b) { Point2D c = { 0, 0 }; diff --git a/WarmUp/MovingOn/LimitsOfPossible/task-info.yaml b/WarmUp/MovingOn/LimitsOfPossible/task-info.yaml index 736d24b..41393bf 100644 --- a/WarmUp/MovingOn/LimitsOfPossible/task-info.yaml +++ b/WarmUp/MovingOn/LimitsOfPossible/task-info.yaml @@ -4,7 +4,7 @@ files: - name: src/loop.cpp visible: true placeholders: - - offset: 110 + - offset: 109 length: 136 placeholder_text: return; dependency: @@ -17,7 +17,7 @@ files: - name: src/generate.cpp visible: true placeholders: - - offset: 121 + - offset: 120 length: 197 placeholder_text: /* TODO */ dependency: @@ -27,7 +27,7 @@ files: file: src/generate.cpp placeholder: 1 is_visible: false - - offset: 394 + - offset: 393 length: 49 placeholder_text: return min; dependency: @@ -40,30 +40,43 @@ files: - name: src/collision.cpp visible: true placeholders: - - offset: 83 - length: 83 - placeholder_text: return 0.0f; + - offset: 82 + length: 83 + placeholder_text: return 0.0f; + dependency: + section: WarmUp + lesson: MovingOn + task: KeepSpinning + file: src/collision.cpp + placeholder: 1 + is_visible: false + - offset: 222 + length: 82 + placeholder_text: return false; + dependency: + section: WarmUp + lesson: MovingOn + task: KeepSpinning + file: src/collision.cpp + placeholder: 2 + is_visible: false +- name: src/borders.cpp + visible: true + placeholders: + - offset: 100 + length: 359 + placeholder_text: /* TODO */ dependency: section: WarmUp lesson: MovingOn task: KeepSpinning - file: src/collision.cpp + file: src/borders.cpp placeholder: 1 is_visible: false - - offset: 223 - length: 82 - placeholder_text: return false; - dependency: - section: WarmUp - lesson: MovingOn - task: KeepSpinning - file: src/collision.cpp - placeholder: 2 - is_visible: false - name: src/direction.cpp visible: true placeholders: - - offset: 70 + - offset: 69 length: 304 placeholder_text: "return { 0.0f, 0.0f };" dependency: @@ -73,68 +86,54 @@ files: file: src/direction.cpp placeholder: 1 is_visible: false -- name: src/borders.cpp - visible: true - placeholders: - - offset: 101 - length: 359 - placeholder_text: /* TODO */ - dependency: - section: WarmUp - lesson: MovingOn - task: KeepSpinning - file: src/borders.cpp - placeholder: 1 - is_visible: false - name: src/point.cpp visible: true placeholders: - - offset: 88 - length: 37 - placeholder_text: /* TODO */ - dependency: - section: WarmUp - lesson: MovingOn - task: KeepSpinning - file: src/point.cpp - placeholder: 1 - is_visible: false - - offset: 207 - length: 33 - placeholder_text: /* TODO */ - dependency: - section: WarmUp - lesson: MovingOn - task: KeepSpinning - file: src/point.cpp - placeholder: 2 - is_visible: false - - offset: 333 - length: 35 - placeholder_text: position - dependency: - section: WarmUp - lesson: MovingOn - task: KeepSpinning - file: src/point.cpp - placeholder: 3 - is_visible: false + - offset: 87 + length: 37 + placeholder_text: /* TODO */ + dependency: + section: WarmUp + lesson: MovingOn + task: KeepSpinning + file: src/point.cpp + placeholder: 1 + is_visible: false + - offset: 206 + length: 33 + placeholder_text: /* TODO */ + dependency: + section: WarmUp + lesson: MovingOn + task: KeepSpinning + file: src/point.cpp + placeholder: 2 + is_visible: false + - offset: 332 + length: 35 + placeholder_text: position + dependency: + section: WarmUp + lesson: MovingOn + task: KeepSpinning + file: src/point.cpp + placeholder: 3 + is_visible: false - name: src/move.cpp visible: true placeholders: - - offset: 69 - length: 27 - placeholder_text: position - dependency: - section: WarmUp - lesson: MovingOn - task: KeepSpinning - file: src/move.cpp - placeholder: 1 - is_visible: false + - offset: 69 + length: 27 + placeholder_text: position + dependency: + section: WarmUp + lesson: MovingOn + task: KeepSpinning + file: src/move.cpp + placeholder: 1 + is_visible: false - name: src/main.cpp visible: true - editable: true - name: CMakeLists.txt visible: false feedback_link: https://docs.google.com/forms/d/e/1FAIpQLScfBp0gzdxWOmXvQVdyNmeO1od7CG7zxLgNUP4LzKxLBCzkhQ/viewform?usp=pp_url&entry.2103429047=Warm+Up/Moving+On/Limits+of+Possible diff --git a/WarmUp/MovingOn/LookAround/src/main.cpp b/WarmUp/MovingOn/LookAround/src/main.cpp index 691a5c4..215e90f 100644 --- a/WarmUp/MovingOn/LookAround/src/main.cpp +++ b/WarmUp/MovingOn/LookAround/src/main.cpp @@ -2,7 +2,7 @@ #include -#include "scene.hpp" +#include "game.hpp" void processEvent(sf::RenderWindow& window, const sf::Event& event) { switch (event.type) { diff --git a/WarmUp/MovingOn/LookAround/task-info.yaml b/WarmUp/MovingOn/LookAround/task-info.yaml index a9916e4..5ef9982 100644 --- a/WarmUp/MovingOn/LookAround/task-info.yaml +++ b/WarmUp/MovingOn/LookAround/task-info.yaml @@ -1,9 +1,6 @@ type: theory custom_name: Look Around files: -- name: src/main.cpp - visible: true - editable: true - name: CMakeLists.txt visible: false - name: src/move.cpp @@ -19,4 +16,6 @@ files: file: src/move.cpp placeholder: 1 is_visible: false +- name: src/main.cpp + visible: true feedback_link: https://docs.google.com/forms/d/e/1FAIpQLScfBp0gzdxWOmXvQVdyNmeO1od7CG7zxLgNUP4LzKxLBCzkhQ/viewform?usp=pp_url&entry.2103429047=Warm+Up/Moving+On/Look+Around diff --git a/WarmUp/MovingOn/MoveAnObject/src/main.cpp b/WarmUp/MovingOn/MoveAnObject/src/main.cpp index 691a5c4..215e90f 100644 --- a/WarmUp/MovingOn/MoveAnObject/src/main.cpp +++ b/WarmUp/MovingOn/MoveAnObject/src/main.cpp @@ -2,7 +2,7 @@ #include -#include "scene.hpp" +#include "game.hpp" void processEvent(sf::RenderWindow& window, const sf::Event& event) { switch (event.type) { diff --git a/WarmUp/MovingOn/MoveAnObject/task-info.yaml b/WarmUp/MovingOn/MoveAnObject/task-info.yaml index 00c7ab7..c3112a8 100644 --- a/WarmUp/MovingOn/MoveAnObject/task-info.yaml +++ b/WarmUp/MovingOn/MoveAnObject/task-info.yaml @@ -3,8 +3,6 @@ custom_name: Move an Object files: - name: CMakeLists.txt visible: false -- name: src/main.cpp - visible: false - name: src/move.cpp visible: true placeholders: @@ -13,4 +11,6 @@ files: placeholder_text: position - name: test/test.cpp visible: false +- name: src/main.cpp + visible: false feedback_link: https://docs.google.com/forms/d/e/1FAIpQLScfBp0gzdxWOmXvQVdyNmeO1od7CG7zxLgNUP4LzKxLBCzkhQ/viewform?usp=pp_url&entry.2103429047=Warm+Up/Moving+On/Move+an+Object diff --git a/WarmUp/MovingOn/MoveAnObject/test/test.cpp b/WarmUp/MovingOn/MoveAnObject/test/test.cpp index ee64744..a9ba049 100644 --- a/WarmUp/MovingOn/MoveAnObject/test/test.cpp +++ b/WarmUp/MovingOn/MoveAnObject/test/test.cpp @@ -1,6 +1,6 @@ #include -#include "scene.hpp" +#include "game.hpp" #include "testing.hpp" testing::Environment* const env = diff --git a/WarmUp/MovingOn/MovingUp/src/main.cpp b/WarmUp/MovingOn/MovingUp/src/main.cpp index 5f26629..a96ff52 100644 --- a/WarmUp/MovingOn/MovingUp/src/main.cpp +++ b/WarmUp/MovingOn/MovingUp/src/main.cpp @@ -2,7 +2,7 @@ #include -#include "scene.hpp" +#include "game.hpp" void processEvent(sf::RenderWindow& window, const sf::Event& event) { switch (event.type) { diff --git a/WarmUp/MovingOn/MovingUp/src/point.cpp b/WarmUp/MovingOn/MovingUp/src/point.cpp index 6e55434..5c74f69 100644 --- a/WarmUp/MovingOn/MovingUp/src/point.cpp +++ b/WarmUp/MovingOn/MovingUp/src/point.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" Point2D add(Point2D a, Point2D b) { Point2D c = { 0, 0 }; diff --git a/WarmUp/MovingOn/MovingUp/task-info.yaml b/WarmUp/MovingOn/MovingUp/task-info.yaml index 7da5ec1..c0c53de 100644 --- a/WarmUp/MovingOn/MovingUp/task-info.yaml +++ b/WarmUp/MovingOn/MovingUp/task-info.yaml @@ -4,32 +4,32 @@ files: - name: src/point.cpp visible: true placeholders: - - offset: 88 + - offset: 87 length: 37 placeholder_text: /* TODO */ - - offset: 207 + - offset: 206 length: 33 placeholder_text: /* TODO */ - - offset: 333 + - offset: 332 length: 35 placeholder_text: position - name: src/move.cpp visible: true placeholders: - - offset: 69 - length: 27 - placeholder_text: position - dependency: - section: WarmUp - lesson: MovingOn - task: HavingFun - file: src/move.cpp - placeholder: 1 - is_visible: false + - offset: 69 + length: 27 + placeholder_text: position + dependency: + section: WarmUp + lesson: MovingOn + task: HavingFun + file: src/move.cpp + placeholder: 1 + is_visible: false +- name: test/test.cpp + visible: false - name: src/main.cpp visible: true - name: CMakeLists.txt visible: false -- name: test/test.cpp - visible: false feedback_link: https://docs.google.com/forms/d/e/1FAIpQLScfBp0gzdxWOmXvQVdyNmeO1od7CG7zxLgNUP4LzKxLBCzkhQ/viewform?usp=pp_url&entry.2103429047=Warm+Up/Moving+On/Moving+Up diff --git a/WarmUp/MovingOn/MovingUp/test/test.cpp b/WarmUp/MovingOn/MovingUp/test/test.cpp index ec471cc..7b265b0 100644 --- a/WarmUp/MovingOn/MovingUp/test/test.cpp +++ b/WarmUp/MovingOn/MovingUp/test/test.cpp @@ -1,6 +1,6 @@ #include -#include "scene.hpp" +#include "game.hpp" #include "operators.hpp" #include "testing.hpp" diff --git a/WarmUp/MovingOn/PlayAround/src/approaching.cpp b/WarmUp/MovingOn/PlayAround/src/approaching.cpp index ba0307c..12404e0 100644 --- a/WarmUp/MovingOn/PlayAround/src/approaching.cpp +++ b/WarmUp/MovingOn/PlayAround/src/approaching.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" void approachingLoop(Circle player, Circle consumable[], bool concerned[], int size) { for (int i = 0; i < size; ++i) { diff --git a/WarmUp/MovingOn/PlayAround/src/borders.cpp b/WarmUp/MovingOn/PlayAround/src/borders.cpp index b5ca906..c9df954 100644 --- a/WarmUp/MovingOn/PlayAround/src/borders.cpp +++ b/WarmUp/MovingOn/PlayAround/src/borders.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" Point2D adjustToBorders(Point2D position) { Point2D result = position; diff --git a/WarmUp/MovingOn/PlayAround/src/collision.cpp b/WarmUp/MovingOn/PlayAround/src/collision.cpp index 639aad3..449016a 100644 --- a/WarmUp/MovingOn/PlayAround/src/collision.cpp +++ b/WarmUp/MovingOn/PlayAround/src/collision.cpp @@ -1,6 +1,6 @@ #include -#include "scene.hpp" +#include "game.hpp" float distance(Point2D a, Point2D b) { float dx = a.x - b.x; diff --git a/WarmUp/MovingOn/PlayAround/src/direction.cpp b/WarmUp/MovingOn/PlayAround/src/direction.cpp index 2195184..ad7c2bd 100644 --- a/WarmUp/MovingOn/PlayAround/src/direction.cpp +++ b/WarmUp/MovingOn/PlayAround/src/direction.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" Point2D getDirection(Direction direction) { switch (direction) { diff --git a/WarmUp/MovingOn/PlayAround/src/generate.cpp b/WarmUp/MovingOn/PlayAround/src/generate.cpp index 8b854eb..bc95378 100644 --- a/WarmUp/MovingOn/PlayAround/src/generate.cpp +++ b/WarmUp/MovingOn/PlayAround/src/generate.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" #include diff --git a/WarmUp/MovingOn/PlayAround/src/loop.cpp b/WarmUp/MovingOn/PlayAround/src/loop.cpp index 5fe6ce3..8148f4a 100644 --- a/WarmUp/MovingOn/PlayAround/src/loop.cpp +++ b/WarmUp/MovingOn/PlayAround/src/loop.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" void collisionLoop(Circle player, Circle consumable[], bool consumed[], int size) { for (int i = 0; i < size; ++i) { diff --git a/WarmUp/MovingOn/PlayAround/src/main.cpp b/WarmUp/MovingOn/PlayAround/src/main.cpp index 2906421..aa8d82c 100644 --- a/WarmUp/MovingOn/PlayAround/src/main.cpp +++ b/WarmUp/MovingOn/PlayAround/src/main.cpp @@ -1,6 +1,6 @@ #include -#include "scene.hpp" +#include "game.hpp" void processEvent(sf::RenderWindow& window, const sf::Event& event) { switch (event.type) { diff --git a/WarmUp/MovingOn/PlayAround/src/point.cpp b/WarmUp/MovingOn/PlayAround/src/point.cpp index 6e55434..5c74f69 100644 --- a/WarmUp/MovingOn/PlayAround/src/point.cpp +++ b/WarmUp/MovingOn/PlayAround/src/point.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" Point2D add(Point2D a, Point2D b) { Point2D c = { 0, 0 }; diff --git a/WarmUp/MovingOn/PlayAround/task-info.yaml b/WarmUp/MovingOn/PlayAround/task-info.yaml index 1dea63f..7e39269 100644 --- a/WarmUp/MovingOn/PlayAround/task-info.yaml +++ b/WarmUp/MovingOn/PlayAround/task-info.yaml @@ -24,7 +24,7 @@ files: - name: src/loop.cpp visible: true placeholders: - - offset: 110 + - offset: 109 length: 136 placeholder_text: return; dependency: @@ -37,7 +37,7 @@ files: - name: src/generate.cpp visible: true placeholders: - - offset: 121 + - offset: 120 length: 197 placeholder_text: /* TODO */ dependency: @@ -47,7 +47,7 @@ files: file: src/generate.cpp placeholder: 1 is_visible: false - - offset: 394 + - offset: 393 length: 49 placeholder_text: return min; dependency: @@ -60,43 +60,43 @@ files: - name: src/collision.cpp visible: true placeholders: - - offset: 83 - length: 83 - placeholder_text: return 0.0f; - dependency: - section: WarmUp - lesson: MovingOn - task: HuntingBugs - file: src/collision.cpp - placeholder: 1 - is_visible: false - - offset: 223 - length: 82 - placeholder_text: return false; - dependency: - section: WarmUp - lesson: MovingOn - task: HuntingBugs - file: src/collision.cpp - placeholder: 2 - is_visible: false + - offset: 82 + length: 83 + placeholder_text: return 0.0f; + dependency: + section: WarmUp + lesson: MovingOn + task: HuntingBugs + file: src/collision.cpp + placeholder: 1 + is_visible: false + - offset: 222 + length: 82 + placeholder_text: return false; + dependency: + section: WarmUp + lesson: MovingOn + task: HuntingBugs + file: src/collision.cpp + placeholder: 2 + is_visible: false - name: src/borders.cpp visible: true placeholders: - - offset: 101 - length: 359 - placeholder_text: /* TODO */ - dependency: - section: WarmUp - lesson: MovingOn - task: HuntingBugs - file: src/borders.cpp - placeholder: 1 - is_visible: false + - offset: 100 + length: 359 + placeholder_text: /* TODO */ + dependency: + section: WarmUp + lesson: MovingOn + task: HuntingBugs + file: src/borders.cpp + placeholder: 1 + is_visible: false - name: src/direction.cpp visible: true placeholders: - - offset: 70 + - offset: 69 length: 304 placeholder_text: "return { 0.0f, 0.0f };" dependency: @@ -109,49 +109,49 @@ files: - name: src/point.cpp visible: true placeholders: - - offset: 88 - length: 37 - placeholder_text: /* TODO */ - dependency: - section: WarmUp - lesson: MovingOn - task: HuntingBugs - file: src/point.cpp - placeholder: 1 - is_visible: false - - offset: 207 - length: 33 - placeholder_text: /* TODO */ - dependency: - section: WarmUp - lesson: MovingOn - task: HuntingBugs - file: src/point.cpp - placeholder: 2 - is_visible: false - - offset: 333 - length: 35 - placeholder_text: position - dependency: - section: WarmUp - lesson: MovingOn - task: HuntingBugs - file: src/point.cpp - placeholder: 3 - is_visible: false + - offset: 87 + length: 37 + placeholder_text: /* TODO */ + dependency: + section: WarmUp + lesson: MovingOn + task: HuntingBugs + file: src/point.cpp + placeholder: 1 + is_visible: false + - offset: 206 + length: 33 + placeholder_text: /* TODO */ + dependency: + section: WarmUp + lesson: MovingOn + task: HuntingBugs + file: src/point.cpp + placeholder: 2 + is_visible: false + - offset: 332 + length: 35 + placeholder_text: position + dependency: + section: WarmUp + lesson: MovingOn + task: HuntingBugs + file: src/point.cpp + placeholder: 3 + is_visible: false - name: src/move.cpp visible: true placeholders: - - offset: 69 - length: 27 - placeholder_text: position - dependency: - section: WarmUp - lesson: MovingOn - task: HuntingBugs - file: src/move.cpp - placeholder: 1 - is_visible: false + - offset: 69 + length: 27 + placeholder_text: position + dependency: + section: WarmUp + lesson: MovingOn + task: HuntingBugs + file: src/move.cpp + placeholder: 1 + is_visible: false - name: src/main.cpp visible: true - name: CMakeLists.txt diff --git a/WarmUp/MovingOn/RunningInALoop/src/borders.cpp b/WarmUp/MovingOn/RunningInALoop/src/borders.cpp index b5ca906..c9df954 100644 --- a/WarmUp/MovingOn/RunningInALoop/src/borders.cpp +++ b/WarmUp/MovingOn/RunningInALoop/src/borders.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" Point2D adjustToBorders(Point2D position) { Point2D result = position; diff --git a/WarmUp/MovingOn/RunningInALoop/src/collision.cpp b/WarmUp/MovingOn/RunningInALoop/src/collision.cpp index 639aad3..449016a 100644 --- a/WarmUp/MovingOn/RunningInALoop/src/collision.cpp +++ b/WarmUp/MovingOn/RunningInALoop/src/collision.cpp @@ -1,6 +1,6 @@ #include -#include "scene.hpp" +#include "game.hpp" float distance(Point2D a, Point2D b) { float dx = a.x - b.x; diff --git a/WarmUp/MovingOn/RunningInALoop/src/direction.cpp b/WarmUp/MovingOn/RunningInALoop/src/direction.cpp index 2195184..ad7c2bd 100644 --- a/WarmUp/MovingOn/RunningInALoop/src/direction.cpp +++ b/WarmUp/MovingOn/RunningInALoop/src/direction.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" Point2D getDirection(Direction direction) { switch (direction) { diff --git a/WarmUp/MovingOn/RunningInALoop/src/generate.cpp b/WarmUp/MovingOn/RunningInALoop/src/generate.cpp index 8b854eb..bc95378 100644 --- a/WarmUp/MovingOn/RunningInALoop/src/generate.cpp +++ b/WarmUp/MovingOn/RunningInALoop/src/generate.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" #include diff --git a/WarmUp/MovingOn/RunningInALoop/src/loop.cpp b/WarmUp/MovingOn/RunningInALoop/src/loop.cpp index 5fe6ce3..8148f4a 100644 --- a/WarmUp/MovingOn/RunningInALoop/src/loop.cpp +++ b/WarmUp/MovingOn/RunningInALoop/src/loop.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" void collisionLoop(Circle player, Circle consumable[], bool consumed[], int size) { for (int i = 0; i < size; ++i) { diff --git a/WarmUp/MovingOn/RunningInALoop/src/main.cpp b/WarmUp/MovingOn/RunningInALoop/src/main.cpp index 6bdc8bb..b208448 100644 --- a/WarmUp/MovingOn/RunningInALoop/src/main.cpp +++ b/WarmUp/MovingOn/RunningInALoop/src/main.cpp @@ -1,6 +1,6 @@ #include -#include "scene.hpp" +#include "game.hpp" void processEvent(sf::RenderWindow& window, const sf::Event& event) { switch (event.type) { diff --git a/WarmUp/MovingOn/RunningInALoop/src/point.cpp b/WarmUp/MovingOn/RunningInALoop/src/point.cpp index 6e55434..5c74f69 100644 --- a/WarmUp/MovingOn/RunningInALoop/src/point.cpp +++ b/WarmUp/MovingOn/RunningInALoop/src/point.cpp @@ -1,4 +1,4 @@ -#include "scene.hpp" +#include "game.hpp" Point2D add(Point2D a, Point2D b) { Point2D c = { 0, 0 }; diff --git a/WarmUp/MovingOn/RunningInALoop/task-info.yaml b/WarmUp/MovingOn/RunningInALoop/task-info.yaml index 9f27e50..3237e04 100644 --- a/WarmUp/MovingOn/RunningInALoop/task-info.yaml +++ b/WarmUp/MovingOn/RunningInALoop/task-info.yaml @@ -4,36 +4,36 @@ files: - name: src/loop.cpp visible: true placeholders: - - offset: 110 - length: 136 - placeholder_text: return; + - offset: 109 + length: 136 + placeholder_text: return; - name: src/generate.cpp visible: true placeholders: - - offset: 121 - length: 197 - placeholder_text: /* TODO */ - dependency: - section: WarmUp - lesson: MovingOn - task: ALittleSpontaneity - file: src/generate.cpp - placeholder: 1 - is_visible: false - - offset: 394 - length: 49 - placeholder_text: return min; - dependency: - section: WarmUp - lesson: MovingOn - task: ALittleSpontaneity - file: src/generate.cpp - placeholder: 2 - is_visible: false + - offset: 120 + length: 197 + placeholder_text: /* TODO */ + dependency: + section: WarmUp + lesson: MovingOn + task: ALittleSpontaneity + file: src/generate.cpp + placeholder: 1 + is_visible: false + - offset: 393 + length: 49 + placeholder_text: return min; + dependency: + section: WarmUp + lesson: MovingOn + task: ALittleSpontaneity + file: src/generate.cpp + placeholder: 2 + is_visible: false - name: src/collision.cpp visible: true placeholders: - - offset: 83 + - offset: 82 length: 83 placeholder_text: return 0.0f; dependency: @@ -43,7 +43,7 @@ files: file: src/collision.cpp placeholder: 1 is_visible: false - - offset: 223 + - offset: 222 length: 82 placeholder_text: return false; dependency: @@ -56,7 +56,7 @@ files: - name: src/borders.cpp visible: true placeholders: - - offset: 101 + - offset: 100 length: 359 placeholder_text: /* TODO */ dependency: @@ -69,66 +69,66 @@ files: - name: src/direction.cpp visible: true placeholders: - - offset: 70 - length: 304 - placeholder_text: "return { 0.0f, 0.0f };" - dependency: - section: WarmUp - lesson: MovingOn - task: ConsumeAnObject - file: src/direction.cpp - placeholder: 1 - is_visible: false + - offset: 69 + length: 304 + placeholder_text: "return { 0.0f, 0.0f };" + dependency: + section: WarmUp + lesson: MovingOn + task: ConsumeAnObject + file: src/direction.cpp + placeholder: 1 + is_visible: false - name: src/point.cpp visible: true placeholders: - - offset: 88 - length: 37 - placeholder_text: /* TODO */ - dependency: - section: WarmUp - lesson: MovingOn - task: ALittleSpontaneity - file: src/point.cpp - placeholder: 1 - is_visible: false - - offset: 207 - length: 33 - placeholder_text: /* TODO */ - dependency: - section: WarmUp - lesson: MovingOn - task: ALittleSpontaneity - file: src/point.cpp - placeholder: 2 - is_visible: false - - offset: 333 - length: 35 - placeholder_text: position - dependency: - section: WarmUp - lesson: MovingOn - task: ALittleSpontaneity - file: src/point.cpp - placeholder: 3 - is_visible: false + - offset: 87 + length: 37 + placeholder_text: /* TODO */ + dependency: + section: WarmUp + lesson: MovingOn + task: ALittleSpontaneity + file: src/point.cpp + placeholder: 1 + is_visible: false + - offset: 206 + length: 33 + placeholder_text: /* TODO */ + dependency: + section: WarmUp + lesson: MovingOn + task: ALittleSpontaneity + file: src/point.cpp + placeholder: 2 + is_visible: false + - offset: 332 + length: 35 + placeholder_text: position + dependency: + section: WarmUp + lesson: MovingOn + task: ALittleSpontaneity + file: src/point.cpp + placeholder: 3 + is_visible: false - name: src/move.cpp visible: true placeholders: - - offset: 69 - length: 27 - placeholder_text: position - dependency: - section: WarmUp - lesson: MovingOn - task: ALittleSpontaneity - file: src/move.cpp - placeholder: 1 - is_visible: false + - offset: 69 + length: 27 + placeholder_text: position + dependency: + section: WarmUp + lesson: MovingOn + task: ALittleSpontaneity + file: src/move.cpp + placeholder: 1 + is_visible: false +- name: test/test.cpp + visible: false - name: src/main.cpp visible: true - name: CMakeLists.txt visible: false -- name: test/test.cpp - visible: false feedback_link: https://docs.google.com/forms/d/e/1FAIpQLScfBp0gzdxWOmXvQVdyNmeO1od7CG7zxLgNUP4LzKxLBCzkhQ/viewform?usp=pp_url&entry.2103429047=Warm+Up/Moving+On/Running+a+Loop diff --git a/WarmUp/MovingOn/RunningInALoop/test/test.cpp b/WarmUp/MovingOn/RunningInALoop/test/test.cpp index 4cc932f..217ffe6 100644 --- a/WarmUp/MovingOn/RunningInALoop/test/test.cpp +++ b/WarmUp/MovingOn/RunningInALoop/test/test.cpp @@ -2,7 +2,7 @@ #include -#include "scene.hpp" +#include "game.hpp" #include "operators.hpp" #include "testing.hpp" diff --git a/WarmUp/MovingOn/StartTheGame/src/main.cpp b/WarmUp/MovingOn/StartTheGame/src/main.cpp index f9ea279..6a0303b 100644 --- a/WarmUp/MovingOn/StartTheGame/src/main.cpp +++ b/WarmUp/MovingOn/StartTheGame/src/main.cpp @@ -2,7 +2,7 @@ #include -#include "scene.hpp" +#include "game.hpp" void processEvent(sf::RenderWindow& window, const sf::Event& event) { switch (event.type) { diff --git a/WarmUp/MovingOn/StartTheGame/task-info.yaml b/WarmUp/MovingOn/StartTheGame/task-info.yaml index 3b1a8f1..313123d 100644 --- a/WarmUp/MovingOn/StartTheGame/task-info.yaml +++ b/WarmUp/MovingOn/StartTheGame/task-info.yaml @@ -4,8 +4,8 @@ files: - name: CMakeLists.txt visible: false editable: false -- name: src/main.cpp - visible: false - name: src/empty.cpp visible: true -feedback_link: https://docs.google.com/forms/d/e/1FAIpQLScfBp0gzdxWOmXvQVdyNmeO1od7CG7zxLgNUP4LzKxLBCzkhQ/viewform?usp=pp_url&entry.2103429047=Warm+Up/Moving+On/Start+the+Game \ No newline at end of file +- name: src/main.cpp + visible: false +feedback_link: https://docs.google.com/forms/d/e/1FAIpQLScfBp0gzdxWOmXvQVdyNmeO1od7CG7zxLgNUP4LzKxLBCzkhQ/viewform?usp=pp_url&entry.2103429047=Warm+Up/Moving+On/Start+the+Game diff --git a/cmake/utils.cmake b/cmake/utils.cmake index f6cc963..b664b65 100644 --- a/cmake/utils.cmake +++ b/cmake/utils.cmake @@ -28,8 +28,12 @@ macro(add_subprojects _base_dir _ignore_dirs) endforeach () endmacro() +function(create_test_target_lib_name TARGET_NAME OUTPUT_NAME) + set(${OUTPUT_NAME} "${TARGET_NAME}-src-part" PARENT_SCOPE) +endfunction() + macro(configure_test_target _target_name _src_files _test_files) - set(_src_part_lib ${_target_name}-src-part) + create_test_target_lib_name(${_target_name} _src_part_lib) # Create utility library to separate src files from test files add_library(${_src_part_lib} STATIC ${_src_files}) @@ -76,7 +80,10 @@ macro(prepare_sfml_framework_lesson_task _lesson_path _target_name _test_name) include_directories(${CMAKE_SOURCE_DIR}/include/) target_copy_resources(${_target_name}) target_link_sfml(${_target_name}) - if (${_test_name}) + if (NOT "${_test_name}" STREQUAL "") + target_copy_resources(${_test_name}) + create_test_target_lib_name(${_test_name} _src_part_lib) + target_link_sfml(${_src_part_lib}) target_link_sfml(${_test_name}) endif() endmacro() \ No newline at end of file diff --git a/course-info.yaml b/course-info.yaml index 8715478..942fe63 100644 --- a/course-info.yaml +++ b/course-info.yaml @@ -61,3 +61,4 @@ environment: GoogleTest content: - WarmUp - MemoryManagement +- ObjectOrientedProgramming diff --git a/include/cgobject.hpp b/include/cgobject.hpp new file mode 100644 index 0000000..ad66341 --- /dev/null +++ b/include/cgobject.hpp @@ -0,0 +1,62 @@ +#ifndef CPPBASICS_CGOBJECT_HPP +#define CPPBASICS_CGOBJECT_HPP + +#include + +#include "gobject.hpp" +#include "circle.hpp" + +/** + * A class that represents a game object with a circular shape. + */ +class CircleGameObject : public GameObject { +public: + + /** + * Constructor initializing the object with the provided circle shape. + */ + explicit CircleGameObject(Circle circle); + + /** + * Returns position of the circle's center. + */ + Point2D getPosition() const override; + + /** + * Returns the current status of the object. + */ + GameObjectStatus getStatus() const override; + + /** + * Returns the current shape of the object. + */ + Circle getCircle() const; + + /** + * Returns the bounding box of the circle. + */ + Rectangle getBoundingBox() const override; + + /** + * Render the object on a window. + */ + void draw(sf::RenderWindow &window, TextureManager& textureManager) const override; + +protected: + + /** + * Changes position of the circle's center. + */ + void setPosition(Point2D position) override; + + /** + * Changes the current status of the object. + */ + void setStatus(GameObjectStatus newStatus); + +private: + Circle circle; + GameObjectStatus status; +}; + +#endif // CPPBASICS_CGOBJECT_HPP diff --git a/include/circle.hpp b/include/circle.hpp new file mode 100644 index 0000000..80c186f --- /dev/null +++ b/include/circle.hpp @@ -0,0 +1,11 @@ +#ifndef CPPBASICS_CIRCLE_HPP +#define CPPBASICS_CIRCLE_HPP + +#include "point.hpp" + +struct Circle { + Point2D center; + float radius; +}; + +#endif //CPPBASICS_CIRCLE_HPP diff --git a/include/collision.hpp b/include/collision.hpp new file mode 100644 index 0000000..1c15465 --- /dev/null +++ b/include/collision.hpp @@ -0,0 +1,20 @@ +#ifndef CPPBASICS_COLLISION_HPP +#define CPPBASICS_COLLISION_HPP + +#include "circle.hpp" + +/** + * This structure provides a way to store information about a collision, + * such as whether a collision occurred and the distance between colliding objects. + */ +struct CollisionInfo { + bool collide; + float distance; +}; + +/** + * This function takes in two Circle objects and calculates the collision information between them. + */ +CollisionInfo collisionInfo(const Circle& circle1, const Circle& circle2); + +#endif // CPPBASICS_COLLISION_HPP diff --git a/include/constants.hpp b/include/constants.hpp new file mode 100644 index 0000000..516f304 --- /dev/null +++ b/include/constants.hpp @@ -0,0 +1,32 @@ +#ifndef CPPBASICS_CONSTANTS_HPP +#define CPPBASICS_CONSTANTS_HPP + +const float RADIUS = 40.0f; +const float CONSUMABLE_RADIUS = 20.0f; +const float ENEMY_RADIUS = 60.0f; + +const float SPEED = 150.0f; + +const float WINDOW_WIDTH = 800.0f; +const float WINDOW_HEIGHT = 600.0f; + +const float SCENE_WIDTH = 800.0f; +const float SCENE_HEIGHT = 600.0f; + +const float NORTH_BORDER = 0.0f; +const float WEST_BORDER = 0.0f; +const float EAST_BORDER = WEST_BORDER + SCENE_WIDTH; +const float SOUTH_BORDER = NORTH_BORDER + SCENE_HEIGHT; + +const float PLAYER_START_X = 400.0f; +const float PLAYER_START_Y = 300.0f; + +const float CONSUMABLE_START_X = 600.0f; +const float CONSUMABLE_START_Y = 150.0f; + +const float ENEMY_START_X = 100.0f; +const float ENEMY_START_Y = 450.0f; + +const float CONSUMABLE_WARNED_MULTIPLIER = 6.0f; + +#endif // CPPBASICS_CONSTANTS_HPP diff --git a/include/consumable.hpp b/include/consumable.hpp new file mode 100644 index 0000000..78c680d --- /dev/null +++ b/include/consumable.hpp @@ -0,0 +1,48 @@ +#ifndef CPPBASICS_CONSUMABLE_HPP +#define CPPBASICS_CONSUMABLE_HPP + +#include "cgobject.hpp" + +/** + * A class representing a consumable object in the game. + */ +class ConsumableObject : public CircleGameObject { +public: + + /** + * Constructor that initializes consumable's shape and position. + */ + ConsumableObject(); + + /** + * Returns the `CONSUMABLE` kind. + */ + GameObjectKind getKind() const override; + + /** + * Returns the current velocity of the object. + * Consumable objects are static and thus their velocity is always zero. + */ + Point2D getVelocity() const override; + + /** + * Updates the state of an object based on the elapsed time. + * Always resets consumable status back to `NORMAL`, unless it was `DESTROYED`. + */ + void update(sf::Time delta) override; + + /** + * Handles the potential collision of consumable object with another object. + * Sets the status to `CONCERNED` if some other object is approaching the consumable. + * Sets the status to `DESTROYED` in case of an actual collision. + */ + void onCollision(const GameObject &object, const CollisionInfo &info) override; + + /** + * Retrieves the texture associated with the consumable object based on its current status. + */ + const sf::Texture* getTexture(TextureManager& textureManager) const override; + +}; + +#endif // CPPBASICS_CONSUMABLE_HPP diff --git a/include/direction.hpp b/include/direction.hpp new file mode 100644 index 0000000..bccd4b2 --- /dev/null +++ b/include/direction.hpp @@ -0,0 +1,36 @@ +#ifndef CPPBASICS_DIRECTION_HPP +#define CPPBASICS_DIRECTION_HPP + +#include + +#include "point.hpp" +#include "constants.hpp" + +enum Direction { + North, + East, + South, + West, +}; + +Point2D getDirection(Direction direction); + +inline Point2D calculateVelocity() { + Point2D velocity = { 0.0f, 0.0f }; + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) { + velocity = add(velocity, getDirection(North)); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) { + velocity = add(velocity, getDirection(East)); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) { + velocity = add(velocity, getDirection(South)); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) { + velocity = add(velocity, getDirection(West)); + } + velocity = mul(SPEED, velocity); + return velocity; +} + +#endif // CPPBASICS_DIRECTION_HPP diff --git a/include/dynscene.hpp b/include/dynscene.hpp new file mode 100644 index 0000000..e08b4af --- /dev/null +++ b/include/dynscene.hpp @@ -0,0 +1,78 @@ +#ifndef CPPBASICS_DYNSCENE_HPP +#define CPPBASICS_DYNSCENE_HPP + +#include + +#include + +#include "scene.hpp" +#include "gobjectlist.hpp" +#include "player.hpp" + +/** + * DynamicScene is a scene capable of managing and updating the list of game objects in the scene: + * objects might be added to or removed from the scene dynamically. + */ +class GameplayDynamicScene : public Scene { +public: + + /** + * Constructs the scene. + */ + GameplayDynamicScene(); + + /** + * Activates the scene. + */ + void activate() override; + + /** + * Deactivates the scene. + */ + void deactivate() override; + + /** + * Returns the ID of the scene. + */ + SceneID getID() const override; + + /** + * Returns the ID of the next scene to transition into. + */ + SceneID getNextSceneID() const override; + + /** + * Process the given event. + */ + void processEvent(const sf::Event &event) override; + + /** + * Updates the object's state based on the elapsed time since the last update. + */ + void update(sf::Time delta) override; + + /** + * Draws the scene with all its game objects. + */ + void draw(sf::RenderWindow &window, TextureManager& textureManager) override; + +protected: + + /** + * Update the list of objects present on the scene. In particular: + * - removes all destroyed objects; + * - creates new objects if there is enough room for them. + */ + void updateObjectsList(); + + /** + * Adds to the scene new object of the given kind. + */ + std::shared_ptr addNewGameObject(GameObjectKind kind); + +private: + GameObjectList objects; +}; + + +#endif // CPPBASICS_DYNSCENE_HPP diff --git a/include/enemy.hpp b/include/enemy.hpp new file mode 100644 index 0000000..f4363c5 --- /dev/null +++ b/include/enemy.hpp @@ -0,0 +1,62 @@ +#ifndef CPPBASICS_ENEMY_HPP +#define CPPBASICS_ENEMY_HPP + +#include "cgobject.hpp" + + +/** + * A class representing an enemy object in the game. + */ +class EnemyObject : public CircleGameObject { +public: + + /** + * Constructor that initializes enemy's shape and position. + */ + EnemyObject(); + + /** + * Returns the `ENEMY` kind. + */ + GameObjectKind getKind() const override; + + /** + * Returns the current velocity of the object. + * Enemy objects change their velocity randomly on every fixed period of time. + */ + Point2D getVelocity() const override; + + /** + * Updates the state of an object based on the elapsed time. + * Changes the velocity on every fixed period of time. + */ + void update(sf::Time delta) override; + + /** + * Handles the potential collision of enemy object with another object. + */ + void onCollision(const GameObject &object, const CollisionInfo &info) override; + + /** + * Retrieves the texture associated with the enemy object based on its current status. + */ + const sf::Texture* getTexture(TextureManager& textureManager) const override; + +protected: + + /** + * Sets the velocity of the enemy object. + */ + void setVelocity(Point2D velocity); + + /** + * Updates the velocity of an enemy object. + */ + void updateVelocity(); + +private: + Point2D velocity; + sf::Time updateTimer; +}; + +#endif // CPPBASICS_ENEMY_HPP diff --git a/include/engine.hpp b/include/engine.hpp new file mode 100644 index 0000000..74b887b --- /dev/null +++ b/include/engine.hpp @@ -0,0 +1,96 @@ +#ifndef CPPBASICS_ENGINE_HPP +#define CPPBASICS_ENGINE_HPP + +#include + +#include "scenes.hpp" +#include "textures.hpp" + +class GameEngine { +public: + + /** + * Returns the game engine instance. + * + * @return A pointer to the game engine instance. + * + * @note This function does not transfer ownership of the game engine to the caller. + * The lifetime of the game engine is the entire duration of the program, and thus there is no need + * to manually delete the returned pointer. + */ + static GameEngine* create(); + + /** + * The run function implements the main loop of the game. + * + * While game is active, on each iteration of the loop, it processes any possible input, + * updates the scene according to the delta time, renders the scene, + * and, finally, performs a transition to a new scene if required. + */ + void run(); + + /** + * Checks if the game engine is active and the application window is open. + * + * @return true if engine is active, false otherwise. + */ + bool isActive() const; + + /** + * Closes the game engine, forcing it to exit its running loop and close the application window. + */ + void close(); + +private: + + /** + * Constructs the game engine. + */ + GameEngine(); + + /** + * Processes all the input events accumulated in the event queue by passing them + * to the GameEngine::processEvent method. + */ + void processInput(); + + /** + * Processes single input event. + * + * Window-related events, such as user resizing or closing a window, are processed by + * the engine itself, while all other kinds of events are delegated to the scene. + * + * @param event the processing event. + */ + void processEvent(const sf::Event& event); + + /** + * Updates the state of the current scene. + * + * @param delta The time elapsed since the last update. + */ + void update(sf::Time delta); + + /** + * Renders the current scene on the window. + */ + void render(); + + /** + * Performs a scene transition in the game. + */ + void sceneTransition(); + + /** + * Resizes the game window to fit the current scene into it. + */ + void resizeWindow(); + + sf::RenderWindow window; + SceneManager sceneManager; + TextureManager textureManager; + Scene* scene; + bool active; +}; + +#endif //CPPBASICS_ENGINE_HPP diff --git a/include/enums.hpp b/include/enums.hpp new file mode 100644 index 0000000..ba85bc7 --- /dev/null +++ b/include/enums.hpp @@ -0,0 +1,43 @@ +#ifndef CPPBASICS_ENUMS_HPP +#define CPPBASICS_ENUMS_HPP + +enum class SceneID { + STATIC_GAME_FIELD, + DYNAMIC_GAME_FIELD, +}; + +/** +* The enumeration represents the IDs of textures used in the game. +* These IDs are used to identify specific textures when loading or rendering game graphics. +*/ +enum class GameTextureID { + SPACE, + PLANET, + PLANET_DEAD, + STAR, + STAR_CONCERNED, + BLACKHOLE, + SIZE +}; + +/** + * This enumeration is used to categorize different kinds of game objects. + * Each game object can have one of the following kinds - PLAYER, CONSUMABLE, or ENEMY. + * The kind of a game object can be used to determine its behavior or interaction with the game. + */ +enum class GameObjectKind { + PLAYER, CONSUMABLE, ENEMY +}; + +/** + * Represents the status of a game object. + * - NORMAL - represents a status of an alive object in its normal state. + * - WARNED - represents a status of an alive object when it is in some dangerous situation, + * the exact meaning of which depends on the particular kind of the object. + * - DESTROYED - represents a status of a destroyed object. + */ + enum class GameObjectStatus { + NORMAL, WARNED, DESTROYED, +}; + +#endif // CPPBASICS_ENUMS_HPP diff --git a/include/game.hpp b/include/game.hpp new file mode 100644 index 0000000..5fd78c9 --- /dev/null +++ b/include/game.hpp @@ -0,0 +1,115 @@ +#ifndef CPPBASICS_GAME_HPP +#define CPPBASICS_GAME_HPP + +#include + +#include + +#include "point.hpp" +#include "circle.hpp" +#include "direction.hpp" +#include "utils.hpp" +#include "constants.hpp" + +float move(float position, float velocity, float delta); + +Point2D adjustToBorders(Point2D position); + +bool collision(Circle circle1, Circle circle2); +void collisionLoop(Circle player, Circle consumable[], bool consumed[], int size); +void approachingLoop(Circle player, Circle consumable[], bool warned[], int size); + +float generateCoordinate(float min, float max); +Circle generateCircle(float radius); + +struct Consumable { + Circle circle; + bool concerned; + bool destroyed; +}; + +/******************************************************************************/ + +inline int initWindow(sf::RenderWindow& window) { + window.create(sf::VideoMode(SCENE_WIDTH, SCENE_HEIGHT), "Space Game"); + window.setFramerateLimit(60); + return 0; +} + +inline int initBackrground(sf::Sprite& sprite, sf::Texture& texture) { + if (!texture.loadFromFile("resources/space.png")) { + return 1; + } + texture.setRepeated(true); + sprite.setTexture(texture); + sprite.setTextureRect(sf::IntRect(0, 0, SCENE_WIDTH, SCENE_HEIGHT)); + return 0; +} + + +inline int initPlayer(sf::CircleShape& shape, sf::Texture& texture) { + shape.setRadius(RADIUS); + shape.setOrigin(RADIUS, RADIUS); + shape.setPosition(PLAYER_START_X, PLAYER_START_Y); + if (!texture.loadFromFile("resources/planet.png")) { + return 1; + } + shape.setTexture(&texture); + return 0; +} + +inline int initConsumableTexture(sf::Texture& texture) { + std::string filename = "resources/star.png"; + if (!texture.loadFromFile(filename)) { + return 1; + } + texture.setSmooth(true); + return 0; +} + +inline int initConsumable(sf::CircleShape& shape, const Circle& circle, const sf::Texture& texture) { + shape.setRadius(circle.radius); + shape.setOrigin(circle.radius, circle.radius); + shape.setPosition(circle.center.x, circle.center.y); + shape.setTexture(&texture); + return 0; +} + +inline int initConsumable(sf::CircleShape& shape, const sf::Texture& texture) { + Circle circle = { { CONSUMABLE_START_X, CONSUMABLE_START_Y }, CONSUMABLE_RADIUS }; + return initConsumable(shape, circle, texture); +} + +inline int initConsumableRandom(sf::CircleShape& shape, const sf::Texture& texture, Circle playerCircle) { + Circle consumableCircle; + while (true) { + consumableCircle = generateCircle(CONSUMABLE_RADIUS); + if (collision(consumableCircle, playerCircle)) + continue; + break; + } + return initConsumable(shape, consumableCircle, texture); +} + +inline int initConsumablesRandom(sf::CircleShape* shapes, int count, const sf::Texture& texture, Circle playerCircle) { + std::vector circles(count); + for (int i = 0; i < count; ++i) { + while (true) { + circles[i] = generateCircle(CONSUMABLE_RADIUS); + bool collides = collision(circles[i], playerCircle); + for (int j = 0; (j < i) && !collides; ++j) { + collides = collision(circles[i], circles[j]); + } + if (collides) continue; + break; + } + int status = initConsumable(shapes[i], circles[i], texture); + if (status != 0) { + return status; + } + } + return 0; +} + + +#endif // CPPBASICS_GAME_HPP diff --git a/include/gobject.hpp b/include/gobject.hpp new file mode 100644 index 0000000..84ec76a --- /dev/null +++ b/include/gobject.hpp @@ -0,0 +1,105 @@ +#ifndef CPPBASICS_GOBJECT_HPP +#define CPPBASICS_GOBJECT_HPP + +#include + +#include "point.hpp" +#include "rectangle.hpp" +#include "collision.hpp" +#include "textures.hpp" +#include "enums.hpp" + + +/** + * GameObject is a base class for game objects in a game engine. + * It defines functions for getting and setting object properties, + * updating object status, and drawing the object on a screen. + */ +class GameObject { +public: + + /** + * Returns the current position of the object. + */ + virtual Point2D getPosition() const = 0; + + /** + * Returns the current status of the object. + */ + virtual GameObjectStatus getStatus() const = 0; + + /** + * Returns the kind of the object. + */ + virtual GameObjectKind getKind() const = 0; + + /** + * Returns the current velocity of the object. + */ + virtual Point2D getVelocity() const = 0; + + /** + * Returns the bounding box of the object. + * A bounding box is the smallest rectangle that entirely encompasses an object. + */ + virtual Rectangle getBoundingBox() const = 0; + + /** + * Updates the state of an object based on the elapsed time. + * + * @param delta The time elapsed since last update. + */ + virtual void update(sf::Time delta) = 0; + + /** + * Handler function called when a potential collision occurs between this object and another object. + * + * @param object The potentially collided object. + * @param info The information about the potential collision. + */ + virtual void onCollision(const GameObject& object, const CollisionInfo& info) = 0; + + /** + * This function is used to render the object on a SFML window passed as an argument. + * + * @param window The window on which the object should be drawn. + * @param textureManager The texture manager object used to retrieve the required textures for drawing. + */ + virtual void draw(sf::RenderWindow& window, TextureManager& textureManager) const = 0; + + /** + * Retrieves the texture associated with the object. + * + * @param textureManager The texture manager object used to retrieve the texture. + * @return A constant pointer to the texture. If the object should not be drawn in the current status, + * the pointer should be null. + */ + virtual const sf::Texture* getTexture(TextureManager& textureManager) const = 0; + + /** + * Destructor of a game object. + */ + virtual ~GameObject() = default; + +protected: + friend class Scene; + + /** + * Changes the current position of the object. + */ + virtual void setPosition(Point2D position) = 0; + + /** + * Moves an object in a direction given by the passed vector. + */ + void move(Point2D vector); +}; + + +/** + * This function takes in two game objects as parameters and checks whether they are colliding. + * It returns a CollisionInfo structure that contains information about the collision. + */ +CollisionInfo collisionInfo(const GameObject& object1, const GameObject& object2); + +#endif // CPPBASICS_GOBJECT_HPP diff --git a/include/gobjectlist.hpp b/include/gobjectlist.hpp new file mode 100644 index 0000000..a28bf98 --- /dev/null +++ b/include/gobjectlist.hpp @@ -0,0 +1,109 @@ +#ifndef CPPBASICS_GOBJECTLIST_HPP +#define CPPBASICS_GOBJECTLIST_HPP + +#include +#include + +#include "gobject.hpp" + + +/** + * @class GameObjectList + * @brief Represents a list of game objects. + * + * The class provides functionality to insert, remove, and iterate over game objects. + */ +class GameObjectList { +public: + + /** + * Constructs an empty list. + */ + GameObjectList(); + + /** + * Constructs a copy of the given list. + * The game objects itself are not copied, but shared by the original list and its copy. + */ + GameObjectList(const GameObjectList& other); + + /** + * Moves game objects from another list. + * The list passed as the argument becomes empty. + */ + GameObjectList(GameObjectList&& other) noexcept; + + /** + * Re-assigns the list (either by copying or by moving elements from another list). + */ + GameObjectList& operator=(GameObjectList other); + + /** + * Destructs the list. + */ + ~GameObjectList() = default; + + /** + * Swaps the contents of two game object lists. + */ + friend void swap(GameObjectList& first, GameObjectList& second); + + /** + * Inserts a game object passed as a std::shared_ptr into the list. + */ + void insert(const std::shared_ptr& object); + + /** + * Removes objects from the collection for which the given predicate evaluates to true. + * + * @param pred The predicate function used to determine whether an object should be removed. + */ + void remove(const std::function& pred); + + /** + * Applies a given function to each game object inside the list. + * + * @param apply A function that accept a single game object reference parameter. + */ + void foreach(const std::function& apply); + +private: + + /** + * A node of the doubly-linked list of game objects. + */ + struct Node { + Node* prev = nullptr; + std::unique_ptr next; + std::shared_ptr object; + }; + + /** + * Links a new node into the list after the given cursor node. + * + * @param cursor A pointer to the cursor node. + * @param node A unique pointer to the new node to be linked. + * + * @note The ownership of the new node is transferred to the cursor. + * @note The cursor and node should not be null pointers, otherwise this function will have undefined behavior. + */ + static void link(Node* cursor, std::unique_ptr&& node); + + /** + * Unlinks a given node from a linked list it currently belongs to. + * It does so by updating the pointers of adjacent nodes. + * + * @param node The node to be unlinked from the linked list. + * + * @note The provided node should be a valid node in the linked list and should + * not be the head or tail of the list. + * + * @warning This function does not free the memory occupied by the unlinked node. + */ + static void unlink(Node* node); + + std::unique_ptr head; + Node* tail; +}; + +#endif // CPPBASICS_GOBJECTLIST_HPP diff --git a/include/operators.hpp b/include/operators.hpp index 1e458f8..a43f422 100644 --- a/include/operators.hpp +++ b/include/operators.hpp @@ -4,15 +4,49 @@ #include #include "point.hpp" +#include "circle.hpp" +#include "rectangle.hpp" +#include "collision.hpp" +#include "direction.hpp" +#include "enums.hpp" + +Point2D operator+(Point2D a, Point2D b); +Point2D operator-(Point2D a, Point2D b); +Point2D operator-(Point2D a); +Point2D operator*(float s, Point2D a); + +Circle operator+(Circle c, Point2D v); +Circle operator-(Circle c, Point2D v); +Circle operator*(float s, Circle c); + +Rectangle operator+(Rectangle r, Point2D v); +Rectangle operator-(Rectangle r, Point2D v); +Rectangle operator*(float s, Rectangle r); inline std::ostream& operator<<(std::ostream& os, const Point2D& p) { return os << "(" << p.x << ", " << p.y << ")"; } +inline std::istream& operator>>(std::istream& is, Point2D& p) { + return is >> p.x >> p.y; +} + inline std::ostream& operator<<(std::ostream& os, const Circle& c) { return os << "{ " << "center: " << c.center << "; radius: " << c.radius << " }"; } +inline std::istream& operator>>(std::istream& is, Circle& c) { + return is >> c.center >> c.radius; +} + +inline std::ostream& operator<<(std::ostream& os, const Rectangle& r) { + return os << "{ " << "top-left: " << r.topLeft << "; bottom-right: " << r.botRight << " }"; +} + +inline std::istream& operator>>(std::istream& is, Rectangle& r) { + return is >> r.topLeft >> r.botRight; +} + inline std::string to_string(Direction direction) { switch (direction) { case North: @@ -32,4 +66,42 @@ inline std::ostream& operator<<(std::ostream& os, Direction direction) { return os << to_string(direction); } +inline std::string to_string(GameObjectKind kind) { + switch (kind) { + case GameObjectKind::PLAYER: + return "GameObjectKind::PLAYER"; + case GameObjectKind::CONSUMABLE: + return "GameObjectKind::CONSUMABLE"; + case GameObjectKind::ENEMY: + return "GameObjectKind::ENEMY"; + default: + return ""; + } +} + +inline std::ostream& operator<<(std::ostream& os, GameObjectKind kind) { + return os << to_string(kind); +} + +inline std::string to_string(GameObjectStatus status) { + switch (status) { + case GameObjectStatus::NORMAL: + return "GameObjectStatus::NORMAL"; + case GameObjectStatus::WARNED: + return "GameObjectStatus::WARNED"; + case GameObjectStatus::DESTROYED: + return "GameObjectStatus::DESTROYED"; + default: + return ""; + } +} + +inline std::ostream& operator<<(std::ostream& os, GameObjectStatus status) { + return os << to_string(status); +} + +inline std::ostream& operator<<(std::ostream& os, const CollisionInfo& info) { + return os << "CollisionInfo = { " << "collide: " << info.collide << "; distance: " << info.distance << " }"; +} + #endif // CPPBASICS_OPERATORS_HPP diff --git a/include/player.hpp b/include/player.hpp new file mode 100644 index 0000000..c839fae --- /dev/null +++ b/include/player.hpp @@ -0,0 +1,47 @@ +#ifndef CPPBASICS_PLAYER_HPP +#define CPPBASICS_PLAYER_HPP + +#include "cgobject.hpp" + + +/** + * A class represents a player in the game. + */ +class PlayerObject : public CircleGameObject { +public: + + /** + * Constructor that initializes player's shape and position. + */ + PlayerObject(); + + /** + * Returns the `PLAYER` kind. + */ + GameObjectKind getKind() const override; + + /** + * Returns the current velocity of the object. + * The object's velocity depends on the control keys pressed by the player at the moment. + */ + Point2D getVelocity() const override; + + /** + * Idle for the player object. + */ + void update(sf::Time delta) override; + + /** + * Handles a potential collision of player object with another object. + * In particular, if player collided with an enemy object, sets its status to `DESTROYED`. + */ + void onCollision(const GameObject &object, const CollisionInfo &info) override; + + /** + * Retrieves the texture associated with the player object based on its current status. + */ + const sf::Texture* getTexture(TextureManager& textureManager) const override; + +}; + +#endif // CPPBASICS_PLAYER_HPP diff --git a/include/rectangle.hpp b/include/rectangle.hpp new file mode 100644 index 0000000..5ff197f --- /dev/null +++ b/include/rectangle.hpp @@ -0,0 +1,34 @@ +#ifndef CPPBASICS_RECTANGLE_HPP +#define CPPBASICS_RECTANGLE_HPP + +#include "point.hpp" + +struct Rectangle { + Point2D topLeft; + Point2D botRight; +}; + +inline bool isEmpty(const Rectangle& rect) { + return (rect.topLeft.x == rect.botRight.x) || (rect.topLeft.y == rect.botRight.y); +} + +inline float width(const Rectangle& rect) { + return rect.botRight.x - rect.topLeft.x; +} + +inline float height(const Rectangle& rect) { + return rect.botRight.y - rect.topLeft.y; +} + +inline bool isInRectangle(Point2D p, const Rectangle& rect) { + return (rect.topLeft.x <= p.x) && (rect.topLeft.y <= p.y) && + (p.x <= rect.botRight.x) && (p.y <= rect.botRight.y); +} + +Point2D center(const Rectangle& rect); + +Rectangle createRectangle(Point2D p1, Point2D p2); + +Rectangle fitInto(const Rectangle& rect, const Rectangle& intoRect); + +#endif // CPPBASICS_RECTANGLE_HPP diff --git a/include/scene.hpp b/include/scene.hpp index c2698d7..6361d8b 100644 --- a/include/scene.hpp +++ b/include/scene.hpp @@ -1,162 +1,150 @@ #ifndef CPPBASICS_SCENE_HPP #define CPPBASICS_SCENE_HPP -#include - #include #include "point.hpp" - -const float RADIUS = 40.0f; -const float CONSUMABLE_RADIUS = 20.0f; - -float move(float position, float velocity, float delta); - -enum Direction { - North, - East, - South, - West, +#include "gobject.hpp" +#include "textures.hpp" + +/** + * The Scene class provides functionalities for rendering game scenes. + */ +class Scene { +public: + + /** + * Constructs the scene. + */ + Scene(float width, float height); + + /** + * Destructs the scene. + */ + virtual ~Scene() = default; + + /** + * Activates the scene. + * + * Scene is activated by the game engine when it sets it as a the current scene. + * Activation should initialize the state of the scene, including all the game objects belonging to it. + * + * @note This method must be implemented in any derived scene class. + */ + virtual void activate() = 0; + + /** + * Deactivates the scene. + * + * Scene is deactivated by the game engine when it performs a transition to another scene. + * Deactivation should reset the state of the scene. + * + * @note This method must be implemented in any derived scene class. + */ + virtual void deactivate() = 0; + + /** + * Returns the ID of the scene. + */ + virtual SceneID getID() const = 0; + + /** + * Returns the ID of the next scene to transition into. + */ + virtual SceneID getNextSceneID() const = 0; + + /** + * Processes scene-related input event, such as player pressing some button, etc. + * + * @param event the processing event. + * + * @note This method must be implemented in any derived scene class. + */ + virtual void processEvent(const sf::Event& event) = 0; + + /** + * Updates the scene state based on time elapsed since the last update. + * + * @param delta The time elapsed since the last update. + * + * @note This method must be implemented in any derived scene class. + */ + virtual void update(sf::Time delta) = 0; + + /** + * Draws all the game objects belonging to the scene on the window. + * + * @param window the window to draw on. + * @param textureManager the texture manager. + * + * @note This method must be implemented in any derived scene class. + */ + virtual void draw(sf::RenderWindow &window, TextureManager& textureManager) = 0; + + /** + * Returns the bounding box of the scene. + * The bounding box defines the smallest rectangle that completely encloses the scene. + * + * @return The bounding box of the scene as a rectangle. + */ + Rectangle getBoundingBox() const; + +protected: + + /** + * Set the position of a game object. + * Ensures that the object fits within the bounds of the scene. + * + * @param object the game object which position to be set. + * @param position the new position of the object. + */ + void setObjectPosition(GameObject& object, Point2D position); + + /** + * Moves a game object on the scene by the given vector. + * Ensures that the object fits within the bounds of the scene. + * + * @param object The game object to be moved. + * @param vector The vector specifying the movement. + */ + void move(GameObject& object, Point2D vector); + + /** + * Moves a game object on the scene based on a given time delta and object's velocity. + * + * @param object The game object to be moved. + * @param delta The time delta. + */ + void move(GameObject& object, sf::Time delta); + + /** + * @brief Fits the game object into the bound of the scene. + * The fitting operation may involve adjusting object's position. + * + * @param object the object to be fit into the scene. + */ + void fitInto(GameObject& object); + + /** + * Checks for potential collision between two game objects and notifies the objects about the result of the check + * by calling their GameObject::onCollision methods and provided with the CollisionInfo structure. + * + * @param object1 The first game object to check for collision. + * @param object2 The second game object to check for collision. + */ + void detectCollision(GameObject& object1, GameObject& object2); + + /** + * Draws the background, filling it with the provided texture. + * + * @param window the window to draw on. + * @param texture the background texture. + */ + void drawBackground(sf::RenderWindow &window, const sf::Texture* texture) const; + +private: + float width; + float height; }; -Point2D getDirection(Direction direction); - -const float SPEED = 150.0f; - -const float SCENE_WIDTH = 800.0f; -const float SCENE_HEIGHT = 600.0f; - -const float NORTH_BORDER = 0.0f; -const float WEST_BORDER = 0.0f; -const float EAST_BORDER = WEST_BORDER + SCENE_WIDTH; -const float SOUTH_BORDER = NORTH_BORDER + SCENE_HEIGHT; - -Point2D adjustToBorders(Point2D position); - -struct Circle { - Point2D center; - float radius; -}; - -float distance(Point2D a, Point2D b); - -bool collision(Circle circle1, Circle circle2); -void collisionLoop(Circle player, Circle consumable[], bool consumed[], int size); -void approachingLoop(Circle player, Circle consumable[], bool warned[], int size); - -float generateCoordinate(float min, float max); -Circle generateCircle(float radius); - -struct Consumable { - Circle circle; - bool concerned; - bool destroyed; -}; - -/******************************************************************************/ - -inline int initWindow(sf::RenderWindow& window) { - window.create(sf::VideoMode(SCENE_WIDTH, SCENE_HEIGHT), "Space Game"); - window.setFramerateLimit(60); - return 0; -} - -inline int initBackrground(sf::Sprite& sprite, sf::Texture& texture) { - if (!texture.loadFromFile("resources/space.png")) { - return 1; - } - texture.setRepeated(true); - sprite.setTexture(texture); - sprite.setTextureRect(sf::IntRect(0, 0, SCENE_WIDTH, SCENE_HEIGHT)); - return 0; -} - -const float PLAYER_START_X = 400.0f; -const float PLAYER_START_Y = 300.0f; - -inline int initPlayer(sf::CircleShape& shape, sf::Texture& texture) { - shape.setRadius(RADIUS); - shape.setOrigin(RADIUS, RADIUS); - shape.setPosition(PLAYER_START_X, PLAYER_START_Y); - if (!texture.loadFromFile("resources/planet.png")) { - return 1; - } - shape.setTexture(&texture); - return 0; -} - -const float CONSUMABLE_START_X = 600.0f; -const float CONSUMABLE_START_Y = 150.0f; - -inline int initConsumableTexture(sf::Texture& texture) { - std::string filename = "resources/star.png"; - if (!texture.loadFromFile(filename)) { - return 1; - } - texture.setSmooth(true); - return 0; -} - -inline int initConsumable(sf::CircleShape& shape, const Circle& circle, const sf::Texture& texture) { - shape.setRadius(circle.radius); - shape.setOrigin(circle.radius, circle.radius); - shape.setPosition(circle.center.x, circle.center.y); - shape.setTexture(&texture); - return 0; -} - -inline int initConsumable(sf::CircleShape& shape, const sf::Texture& texture) { - Circle circle = { { CONSUMABLE_START_X, CONSUMABLE_START_Y }, CONSUMABLE_RADIUS }; - return initConsumable(shape, circle, texture); -} - -inline int initConsumableRandom(sf::CircleShape& shape, const sf::Texture& texture, Circle playerCircle) { - Circle consumableCircle; - while (true) { - consumableCircle = generateCircle(CONSUMABLE_RADIUS); - if (collision(consumableCircle, playerCircle)) - continue; - break; - } - return initConsumable(shape, consumableCircle, texture); -} - -inline int initConsumablesRandom(sf::CircleShape* shapes, int count, const sf::Texture& texture, Circle playerCircle) { - std::vector circles(count); - for (int i = 0; i < count; ++i) { - while (true) { - circles[i] = generateCircle(CONSUMABLE_RADIUS); - bool collides = collision(circles[i], playerCircle); - for (int j = 0; (j < i) && !collides; ++j) { - collides = collision(circles[i], circles[j]); - } - if (collides) continue; - break; - } - int status = initConsumable(shapes[i], circles[i], texture); - if (status != 0) { - return status; - } - } - return 0; -} - -inline Point2D calculateVelocity() { - Point2D velocity = { 0.0f, 0.0f }; - if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) { - velocity = add(velocity, getDirection(North)); - } - if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) { - velocity = add(velocity, getDirection(East)); - } - if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) { - velocity = add(velocity, getDirection(South)); - } - if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) { - velocity = add(velocity, getDirection(West)); - } - velocity = mul(SPEED, velocity); - return velocity; -} - #endif // CPPBASICS_SCENE_HPP diff --git a/include/scenes.hpp b/include/scenes.hpp new file mode 100644 index 0000000..e3f39f7 --- /dev/null +++ b/include/scenes.hpp @@ -0,0 +1,42 @@ +#ifndef CPPBASICS_SCENES_HPP +#define CPPBASICS_SCENES_HPP + +#include "scene.hpp" +#include "statscene.hpp" +#include "dynscene.hpp" + +/** + * The SceneManager class is responsible for loading and managing the different scenes in a game. + */ +class SceneManager { +public: + + /** + * Initializes the scene manager. + * + * @return true if the initialization was successful, false otherwise. + */ + bool initialize(); + + /** + * Retrieves the current scene of the game. + * + * @return The pointer to the current scene. + * + * @note This method does not transfer ownership of the scene to the caller. + * The lifetime of the scenes is managed by the scene manager itself. + */ + Scene* getCurrentScene(); + + /** + * This function checks if the transition to a new scene is necessary by querying the current scene + * and performs this transition if required. + */ + void transitionScene(SceneID id); + +private: + GameplayStaticScene staticScene; +}; + + +#endif //CPPBASICS_SCENES_HPP diff --git a/include/statscene.hpp b/include/statscene.hpp new file mode 100644 index 0000000..82ae782 --- /dev/null +++ b/include/statscene.hpp @@ -0,0 +1,65 @@ +#ifndef CPPBASICS_STATSCENE_HPP +#define CPPBASICS_STATSCENE_HPP + +#include + +#include "scene.hpp" +#include "player.hpp" +#include "consumable.hpp" +#include "enemy.hpp" + + +/** + * StaticScene is a scene operating a static fixed collection of game objects, + * namely single player, single consumable, and single enemy objects. + */ +class GameplayStaticScene : public Scene { +public: + + /** + * Constructs the scene. + */ + GameplayStaticScene(); + + /** + * Activates the scene. + */ + void activate() override; + + /** + * Deactivates the scene. + */ + void deactivate() override; + + /** + * Returns the ID of the scene. + */ + SceneID getID() const override; + + /** + * Returns the ID of the next scene to transition into. + */ + SceneID getNextSceneID() const override; + + /** + * Process the given event. + */ + void processEvent(const sf::Event &event) override; + + /** + * Updates the object's state based on the elapsed time since the last update. + */ + void update(sf::Time delta) override; + + /** + * Draws the scene with all its game objects. + */ + void draw(sf::RenderWindow &window, TextureManager& textureManager) override; + +private: + PlayerObject player; + ConsumableObject consumable; + EnemyObject enemy; +}; + +#endif // CPPBASICS_STATSCENE_HPP diff --git a/include/testing.hpp b/include/testing.hpp index 221e035..5295091 100644 --- a/include/testing.hpp +++ b/include/testing.hpp @@ -9,6 +9,7 @@ #include #include "point.hpp" +#include "utils.hpp" inline void initGen() { // fix seed to generate test data deterministically diff --git a/include/testscene.hpp b/include/testscene.hpp new file mode 100644 index 0000000..c25cbd8 --- /dev/null +++ b/include/testscene.hpp @@ -0,0 +1,232 @@ +#ifndef CPPBASICS_TESTSCENE_HPP +#define CPPBASICS_TESTSCENE_HPP + +#include "scene.hpp" +#include "gobject.hpp" +#include "cgobject.hpp" +#include "player.hpp" +#include "consumable.hpp" +#include "enemy.hpp" +#include "constants.hpp" + +class TestGameObject : public GameObject { +public: + + inline TestGameObject() + : position(Point2D { 0.0f, 0.0f }) + , velocity(Point2D { 0.0f, 0.0f }) + , status(GameObjectStatus::NORMAL) + , kind(GameObjectKind::CONSUMABLE) + {} + + inline TestGameObject(Point2D position, Point2D velocity, GameObjectStatus status, GameObjectKind kind) + : position(position) + , velocity(velocity) + , status(status) + , kind(kind) + {} + + TestGameObject(const TestGameObject& other) = default; + TestGameObject& operator=(const TestGameObject& other) = default; + + inline Point2D getPosition() const override { + return position; + } + + inline void setPosition(Point2D position) override { + this->position = position; + } + + inline GameObjectStatus getStatus() const override { + return status; + } + + inline void setStatus(GameObjectStatus status) { + this->status = status; + } + + inline GameObjectKind getKind() const override { + return kind; + } + + inline void setKind(GameObjectKind kind) { + this->kind = kind; + } + + inline Point2D getVelocity() const override { + return velocity; + } + + inline Rectangle getBoundingBox() const override { + return Rectangle { position, position }; + } + + inline void update(sf::Time delta) override { + return; + } + + inline void onCollision(const GameObject& object, const CollisionInfo& info) override { + return; + } + + inline void draw(sf::RenderWindow& window, TextureManager& textureManager) const override { + return; + } + + inline const sf::Texture* getTexture(TextureManager& textureManager) const override { + return nullptr; + } + + void performMove(Point2D vector) { + move(vector); + } + +private: + Point2D position; + Point2D velocity; + GameObjectStatus status; + GameObjectKind kind; +}; + +class TestCircleGameObject : public CircleGameObject { +public: + + inline TestCircleGameObject() + : CircleGameObject(Circle { Point2D { CONSUMABLE_START_X, CONSUMABLE_START_Y }, CONSUMABLE_RADIUS }) + , velocity(Point2D { 0.0f, 0.0f }) + , kind(GameObjectKind::CONSUMABLE) + {} + + inline TestCircleGameObject(Circle circle, Point2D velocity, GameObjectKind kind) + : CircleGameObject(circle) + , velocity(velocity) + , kind(kind) + {} + + TestCircleGameObject(const TestCircleGameObject& other) = default; + TestCircleGameObject& operator=(const TestCircleGameObject& other) = default; + + inline GameObjectKind getKind() const override { + return kind; + } + + inline Point2D getVelocity() const override { + return velocity; + } + + inline void update(sf::Time delta) override { + return; + } + + inline void onCollision(const GameObject& object, const CollisionInfo& info) override { + return; + } + + inline const sf::Texture* getTexture(TextureManager& textureManager) const override { + return nullptr; + } + + void performSetStatus(GameObjectStatus status) { + setStatus(status); + } + + void performSetPosition(Point2D position) { + setPosition(position); + } + +private: + Point2D velocity; + GameObjectKind kind; +}; + +class TestPlayerObject : public PlayerObject { +public: + + inline TestPlayerObject() : PlayerObject() {} + + TestPlayerObject(const TestPlayerObject& other) = default; + TestPlayerObject& operator=(const TestPlayerObject& other) = default; + + inline void performSetStatus(GameObjectStatus status) { + setStatus(status); + } + + inline void performSetPosition(Point2D position) { + setPosition(position); + } +}; + +class TestConsumableObject : public ConsumableObject { +public: + + inline TestConsumableObject() : ConsumableObject() {} + + TestConsumableObject(const TestConsumableObject& other) = default; + TestConsumableObject& operator=(const TestConsumableObject& other) = default; + + inline void performSetStatus(GameObjectStatus status) { + setStatus(status); + } + + inline void performSetPosition(Point2D position) { + setPosition(position); + } +}; + +class TestEnemyObject : public EnemyObject { +public: + + inline TestEnemyObject() : EnemyObject() {} + + TestEnemyObject(const TestEnemyObject& other) = default; + TestEnemyObject& operator=(const TestEnemyObject& other) = default; + + inline void performSetStatus(GameObjectStatus status) { + setStatus(status); + } + + inline void performSetPosition(Point2D position) { + setPosition(position); + } + + inline void performSetVelocity(Point2D velocity) { + setVelocity(velocity); + } + + inline void performUpdateVelocity() { + updateVelocity(); + } +}; + +class TestScene : public Scene { +public: + + inline TestScene(float width, float height) : Scene(width, height) {} + + inline void activate() override {} + inline void deactivate() override {} + + inline SceneID getID() const override { + return SceneID::DYNAMIC_GAME_FIELD; + } + + inline SceneID getNextSceneID() const override { + return SceneID::DYNAMIC_GAME_FIELD; + } + + inline void processEvent(const sf::Event& event) override {} + + inline void update(sf::Time delta) override {} + + inline void draw(sf::RenderWindow &window, TextureManager& textureManager) override {} + + inline void performSetObjectPosition(GameObject& object, Point2D position) { + setObjectPosition(object, position); + } + + inline void performMove(GameObject& object, Point2D vector) { + move(object, vector); + } +}; + +#endif //CPPBASICS_TESTSCENE_HPP diff --git a/include/textures.hpp b/include/textures.hpp new file mode 100644 index 0000000..5a79404 --- /dev/null +++ b/include/textures.hpp @@ -0,0 +1,39 @@ +#ifndef CPPBASICS_TEXTURES_HPP +#define CPPBASICS_TEXTURES_HPP + +#include + +#include + +#include "enums.hpp" + +/** + * The TextureManager class manages the loading and retrieval of textures for the game. + */ +class TextureManager { +public: + + /** + * Initializes the texture manager. + * + * @return true if the initialization was successful, false otherwise. + */ + bool initialize(); + + /** + * Get the texture associated with the specified game texture ID. + * + * @param id The ID of the game texture to retrieve. + * @return The texture associated with the specified game texture ID. + * + * @note this method does not transfer the ownership of the texture object, + * since the texture manager itself is the owner of all the textures. + */ + const sf::Texture* getTexture(GameTextureID id) const; + +private: + static const size_t SIZE = static_cast(GameTextureID::SIZE); + sf::Texture textures[SIZE]; +}; + +#endif // CPPBASICS_TEXTURES_HPP diff --git a/include/utils.hpp b/include/utils.hpp new file mode 100644 index 0000000..30bd3ea --- /dev/null +++ b/include/utils.hpp @@ -0,0 +1,77 @@ +#ifndef CPPBASICS_UTILS_HPP +#define CPPBASICS_UTILS_HPP + +#include "point.hpp" +#include "rectangle.hpp" +#include "circle.hpp" + + +/** + * Calculates the distance between two 2D points. + * + * @param a The first point. + * @param b The second point. + * @return The Euclidean distance between the two points. + */ +float distance(Point2D a, Point2D b); + + +/** + * Generates a random boolean value with a given probability. + * + * @param prob the probability of generating a 'true' value (default is 0.5) + * @return a random boolean value + */ +bool generateBool(float prob = 0.5f); + + +/** + * Generates a random integer between the given minimum and maximum values (inclusive). + * that is between `min` and `max` (inclusive). + * + * @param min The minimum value for the generated integer. + * @param max The maximum value for the generated integer. + * @return A random integer between `min` and `max`. + */ +int generateInt(int min, int max); + + +/** + * Generates a random float between the given minimum and maximum values (inclusive). + * + * @param min The minimum value for the range. + * @param max The maximum value for the range. + * @return float A random float value between the minimum and maximum values. + */ +float generateFloat(float min, float max); + + +/** + * Generates a random 2D point within the given bounding box. + * + * @param boundingBox The rectangle bounding box within which to generate the point. + * @return Point2D The generated 2D point. + */ +Point2D generatePoint(const Rectangle& boundingBox); + + +/** + * Generates a random circle with the given radius inside the specified bounding box. + * + * @param radius The radius of the circle to be generated. + * @param boundingBox The rectangle representing the bounding box. + * @return Circle The generated circle. + */ +Circle generateCircle(float radius, const Rectangle& boundingBox); + + +/** + * Generates a random rectangle inside the specified bounding box. + * + * @param boundingBox The bounding box within which the rectangle should be generated. + * @return The generated rectangle within the bounding box. + */ +Rectangle generateRectangle(const Rectangle& boundingBox); + + +#endif // CPPBASICS_UTILS_HPP \ No newline at end of file diff --git a/resources/blackhole.png b/resources/blackhole.png new file mode 100644 index 0000000..64d103a Binary files /dev/null and b/resources/blackhole.png differ diff --git a/resources/planetDead.png b/resources/planetDead.png new file mode 100644 index 0000000..a439b91 Binary files /dev/null and b/resources/planetDead.png differ