diff --git a/README.md b/README.md index 5056f8f..43eb5f3 100644 --- a/README.md +++ b/README.md @@ -21,10 +21,10 @@ This game is aimed to be a puzzle game in which the player sets different laws f - [x] Moving - [ ] Decorators (Rules) - [ ] Rules - - [ ] Creation - - [ ] Interaction with entities + - [x] Creation + - [x] Interaction with entities - [ ] Parsing of rules and creation of subsequent decorators - - [ ] Raytrace in order to find best goal for pathfinding ? + - [x] Find optimal target for pathfinding - [ ] Graphics - [x] Scene rendering - [ ] UI diff --git a/resources/test_level.xml b/resources/test_level.xml index 0c7812f..2a7f192 100644 --- a/resources/test_level.xml +++ b/resources/test_level.xml @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 32421ba..322c065 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,7 +4,7 @@ project(project_maat) set(CMAKE_CXX_STANDARD 17) set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake_modules" ${CMAKE_MODULE_PATH}) -add_library(engine Level.cpp Level.h Entity.cpp Entity.h Game.cpp Game.h Utils.h Utils.h Utils.cpp) +add_library(engine Level.cpp Level.h Entity.cpp Entity.h Game.cpp Game.h Utils.h Utils.h Utils.cpp Rule.cpp Rule.h) target_link_libraries(engine sfml-window diff --git a/src/Entity.cpp b/src/Entity.cpp index ee3d9f2..81026a4 100644 --- a/src/Entity.cpp +++ b/src/Entity.cpp @@ -33,6 +33,9 @@ Entity::Entity(const pugi::xml_node& entityNode, sf::Texture* texture) entityNode.attribute("w").as_int(1), entityNode.attribute("h").as_int(1)) {} +Entity::Entity() : Entity(0,0,EntityType::Citizen,nullptr,0,0) +{} + void Entity::move(Orientation orientation) { // TODO : Add speed ? diff --git a/src/Entity.h b/src/Entity.h index 275ad11..a0cf0ac 100644 --- a/src/Entity.h +++ b/src/Entity.h @@ -45,21 +45,29 @@ public: Entity(pro_maat::GridUnit x, pro_maat::GridUnit y, EntityType type, sf::Texture* texture, int width = 1, int height = 1); explicit Entity(const pugi::xml_node& entityNode, sf::Texture* texture); + virtual ~Entity() = default; - void move(Orientation orientation); - void update(); + virtual void move(Orientation orientation); + virtual void update(); - State getState() const; + virtual State getState() const; - const sf::RectangleShape& getShape() const; + virtual const sf::RectangleShape& getShape() const; /// Returns the grid coordinates at the center of the entity - const pro_maat::GridPos getPosition() const; + virtual const pro_maat::GridPos getPosition() const; // Don't like it : iterates over every square every tick - const std::vector getOccupiedSquares() const; + virtual const std::vector getOccupiedSquares() const; + // FIXME : Replace with getter /// Position of the target of the current action on the map pro_maat::GridPos target; + +protected: + + /// Empty constructor for derived class instanciation + Entity(); + private: static const std::map entityTypeLookup; @@ -75,6 +83,9 @@ private: /// Used with rules : last to update has priority pro_maat::GridPos nextTarget; + + template + friend class Rule; }; diff --git a/src/Game.h b/src/Game.h index aa149d7..f2ec76a 100644 --- a/src/Game.h +++ b/src/Game.h @@ -13,8 +13,6 @@ #include "Entity.h" #include "Utils.h" -// Used for convenience -using TextureStore = std::vector>; class Game { public: @@ -37,7 +35,7 @@ private: /// Store the paths to texture files const std::vector textureFiles; /// Stores pointers to textures for future use - TextureStore textures; + pro_maat::TextureStore textures; void loadTextures(); }; diff --git a/src/Level.cpp b/src/Level.cpp index 66d2cb5..2653ad5 100644 --- a/src/Level.cpp +++ b/src/Level.cpp @@ -5,7 +5,7 @@ #include "Level.h" -Level::Level(const pugi::xml_document& xmlDoc, const TextureStore& textureStore) +Level::Level(const pugi::xml_document& xmlDoc, const pro_maat::TextureStore& textureStore) : size(xmlDoc.child("Level").attribute("w").as_int(),xmlDoc.child("Level").attribute("h").as_int()), textures(textureStore) { @@ -18,6 +18,10 @@ Level::Level(const pugi::xml_document& xmlDoc, const TextureStore& textureStore) // Initialize the occupied squares vector with the new entity's squares std::vector entitySquares = entities.rbegin()->getOccupiedSquares(); + + // FIXME : For testing purposes + Rule newRule(*entities.rbegin(),entities,occupiedSquares,size); + std::move(entitySquares.begin(),entitySquares.end(),std::back_inserter(occupiedSquares)); } } @@ -89,6 +93,8 @@ Orientation Level::findPath(pro_maat::GridPos start, pro_maat::GridPos goal, int std::map paths{}; + // TODO : Should be OK with raycasting to find goal + // FIXME : Just get to goal (Might break) const std::vector goalNeighbours = pro_maat::getNeighbours(goal,size); // Save the iterators : vector is const and .begin() and .end() might get called a lot auto goalNeighboursBeginIterator = goalNeighbours.begin(); diff --git a/src/Level.h b/src/Level.h index 7f4f8c8..f6219c6 100644 --- a/src/Level.h +++ b/src/Level.h @@ -14,12 +14,12 @@ #include "Utils.h" #include "Entity.h" +#include "Rule.h" -using TextureStore = std::vector>; class Level { public: - Level(const pugi::xml_document& xmlDoc, const TextureStore& textureStore); + Level(const pugi::xml_document& xmlDoc, const pro_maat::TextureStore& textureStore); void render(sf::RenderWindow& renderWindow) const; void runStep(); @@ -29,7 +29,7 @@ private: std::vector entities; - const TextureStore& textures; + const pro_maat::TextureStore& textures; // // Pathfinding diff --git a/src/Rule.cpp b/src/Rule.cpp new file mode 100644 index 0000000..3556db6 --- /dev/null +++ b/src/Rule.cpp @@ -0,0 +1,5 @@ +// +// Created by trotfunky on 09/06/19. +// + +#include "Rule.h" diff --git a/src/Rule.h b/src/Rule.h new file mode 100644 index 0000000..3cb0e4e --- /dev/null +++ b/src/Rule.h @@ -0,0 +1,204 @@ +// +// Created by trotfunky on 09/06/19. +// + +#ifndef PROJECT_MAAT_RULES_H +#define PROJECT_MAAT_RULES_H + +#include +#include + +#include "Entity.h" + + +/// Decorates entities with rules which will modify its behaviour +template +class Rule : public Entity +{ +public: + Rule(Entity& entity, const std::vector& entities, + const std::vector& occupiedSquares, const pro_maat::GridPos& mapSize); + + void update() override; + + void move(Orientation orientation) override; + State getState() const override; + const sf::RectangleShape& getShape() const override; + const pro_maat::GridPos getPosition() const override; + const std::vector getOccupiedSquares() const override; + +private: + /// Finds the closest free square adjacent to the closest target entity type. + /// \return Suitable target square or current position if none was found. + pro_maat::GridPos findTarget(); + + Entity& entity; + const std::vector& entities; + const std::vector& occupiedSquares; + + const pro_maat::GridPos& mapSize; +}; + + +template +Rule::Rule(Entity& entity, + const std::vector& entities, + const std::vector& occupiedSquares, + const pro_maat::GridPos& mapSize) + : entity(entity), + entities(entities), + occupiedSquares(occupiedSquares), + mapSize(mapSize) +{} + +template +void Rule::update() +{ + if constexpr (targetState == State::Moving || targetState == State::Fleeing) + { + entity.nextTarget = findTarget(); + if(entity.nextTarget == entity.getPosition()) + { + entity.nextState = State::Idle; + } + else + { + entity.nextState = targetState; + } + entity.update(); + } + else if constexpr (targetState == State::Waiting) + { + entity.nextTarget = entity.getPosition(); + entity.nextState = State::Waiting; + entity.update(); + } + else + { + entity.nextTarget = entity.getPosition(); + entity.nextState = State::Idle; + entity.update(); + } +} + +template +pro_maat::GridPos Rule::findTarget() +{ + std::vector sortedEntities{}; + sortedEntities.insert(sortedEntities.end(),entities.begin(),entities.end()); + + // Compares entities via their distance to the current entity + auto distanceSortEntities = [this](const Entity& leftHandSide, const Entity& rightHandSide){ + return (pro_maat::manhattanDistance(entity.getPosition(),leftHandSide.getPosition()) < + pro_maat::manhattanDistance(entity.getPosition(),rightHandSide.getPosition())); + }; + + // Same but with grid coordinates + auto distanceSortSquares = [this](const pro_maat::GridPos& leftHandSide, const pro_maat::GridPos& rightHandSide){ + return (pro_maat::manhattanDistance(entity.getPosition(),leftHandSide) < + pro_maat::manhattanDistance(entity.getPosition(),rightHandSide)); + }; + + // Get the smallest, non-occupied, inside the map square + auto bestTarget = [this](const pro_maat::GridPos& leftHandSide, const pro_maat::GridPos& rightHandSide){ + // If the left hand side operand is not in the map or occupied, it is not valid + if(!pro_maat::isInMap(leftHandSide,mapSize) || + std::find(occupiedSquares.begin(),occupiedSquares.end(),leftHandSide) != occupiedSquares.end()) + { + return(false); + } + else if(!pro_maat::isInMap(rightHandSide,mapSize) || + std::find(occupiedSquares.begin(),occupiedSquares.end(),rightHandSide) != occupiedSquares.end()) + { + return(true); + } + else + { + return(leftHandSide < rightHandSide); + }}; + + + // Sort in order to minimize entities to process + std::sort(sortedEntities.begin(),sortedEntities.end(),distanceSortEntities); + + for(const Entity& processingEntity : sortedEntities) + { + std::vector potentialTargets{}; + + pro_maat::GridUnit entityWidth = processingEntity.shape.getSize().x/pro_maat::pixelsPerUnit; + pro_maat::GridUnit entityHeight = processingEntity.shape.getSize().y/pro_maat::pixelsPerUnit; + + potentialTargets.reserve((entityWidth+2)*2+entityHeight*2); + + // Computes the top left corner of the entity + pro_maat::GridPos topLeftCorner = processingEntity.getPosition(); + topLeftCorner.first -= entityWidth*0.5 - 1; + topLeftCorner.second -= entityHeight*0.5 - 1; + + + // Get all the top and bottom adjacent squares + for(int i = 0;i +void Rule::move(Orientation orientation) +{ + entity.move(orientation); +} + +template +State Rule::getState() const +{ + return entity.getState(); +} + +template +const sf::RectangleShape& Rule::getShape() const +{ + return entity.getShape(); +} + +template +const pro_maat::GridPos Rule::getPosition() const +{ + return entity.getPosition(); +} + +template +const std::vector Rule::getOccupiedSquares() const +{ + return entity.getOccupiedSquares(); +} + + +#endif //PROJECT_MAAT_RULES_H diff --git a/src/Utils.cpp b/src/Utils.cpp index 4e820ca..23d78d4 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -37,6 +37,12 @@ namespace pro_maat } } + bool isInMap(const GridPos& square, const GridPos& gridSize) + { + return (square.first < 0 || square.second < 0 || + square.first >= gridSize.first || square.second >= gridSize.second); + } + float manhattanDistance(const GridPos& leftHandSide, const GridPos& rightHandSide) { // The *0.01 helps with breaking ties and minimizing exploration diff --git a/src/Utils.h b/src/Utils.h index 4491e10..af3dfe3 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -12,9 +12,11 @@ namespace pro_maat { -// Used as the base of all grid based computations +/// Used for convenience +using TextureStore = std::vector>; +/// Used as the base of all grid based computations using GridUnit = int16_t; -// Used when dealing with the grid representation of the map +/// Used when dealing with the grid representation of the map using GridPos = std::pair; static constexpr uint8_t pixelsPerUnit = 30; @@ -24,6 +26,8 @@ static constexpr char fontFolder[] = "resources/"; void errorWindow(const std::string& error); +/// Test if the coordinate is inside the map or not +bool isInMap(const GridPos& square, const GridPos& gridSize); // Good heuristic on 4-way grids float manhattanDistance(const GridPos& leftHandSide, const GridPos& rightHandSide); /// Gets the 4 cardinal neighbours on the grid with edge checking