diff --git a/README.md b/README.md index 43eb5f3..cbc732f 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,11 @@ This game is aimed to be a puzzle game in which the player sets different laws f - [x] Structure - [x] Parsing from XML - [x] A* - - [ ] Entities + - [x] Entities - [x] Structure - [x] Parsing from XML - [x] Moving - - [ ] Decorators (Rules) + - [x] Decorators (Rules) - [ ] Rules - [x] Creation - [x] Interaction with entities @@ -32,7 +32,7 @@ This game is aimed to be a puzzle game in which the player sets different laws f - [ ] Gameloop - [ ] Transition from starting to running state and vice-versa - [ ] Entity behaviour evolution - - [ ] Rule application + - [x] Rule application - [ ] Saving state through serialization ? - [ ] UML - [ ] Unit tests \ No newline at end of file diff --git a/src/Entity.cpp b/src/Entity.cpp index 987eaa3..11cc6c6 100644 --- a/src/Entity.cpp +++ b/src/Entity.cpp @@ -79,6 +79,11 @@ State Entity::getState() const return currentState; } +EntityType Entity::getType() const +{ + return type; +} + pro_maat::GridPos Entity::getTarget() const { return target; diff --git a/src/Entity.h b/src/Entity.h index 081f956..3765a09 100644 --- a/src/Entity.h +++ b/src/Entity.h @@ -51,6 +51,7 @@ public: virtual void update(); virtual State getState() const; + virtual EntityType getType() const; virtual pro_maat::GridPos getTarget() const; virtual const sf::RectangleShape& getShape() const; @@ -84,7 +85,6 @@ private: /// Used with rules : last to update has priority pro_maat::GridPos nextTarget; - template friend class Rule; }; diff --git a/src/Level.cpp b/src/Level.cpp index 2e4a2cb..65633b4 100644 --- a/src/Level.cpp +++ b/src/Level.cpp @@ -14,24 +14,34 @@ Level::Level(const pugi::xml_document& xmlDoc, const pro_maat::TextureStore& tex { if(!strncmp(child.name(),"Entity",6)) { - entities.emplace_back(child,textures.at(child.attribute("textureId").as_int(0)).get()); + entities.emplace_back(std::make_unique(child,textures.at(child.attribute("textureId").as_int(0)).get())); // 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::vector entitySquares = entities.rbegin()->get()->getOccupiedSquares(); std::move(entitySquares.begin(),entitySquares.end(),std::back_inserter(occupiedSquares)); } } + + // FIXME : For testing purposes + addRule(EntityType::Significant,State::Moving,EntityType::Citizen); +} + +void Level::addRule(EntityType affectedEntities, const State targetState, EntityType targetEntities) +{ + for(auto& entity : entities) + { + if(entity->getType() == affectedEntities) + { + entity = std::make_unique(entity.release(),targetState,targetEntities,entities,occupiedSquares,size); + } + } } void Level::render(sf::RenderWindow& renderWindow) const { - for(const Entity& entity : entities) + for(const auto& entity : entities) { - renderWindow.draw(entity.getShape()); + renderWindow.draw(entity->getShape()); } } @@ -40,11 +50,11 @@ void Level::runStep() std::vector newOccupiedSquares{}; newOccupiedSquares.reserve(occupiedSquares.size()); - for(Entity& entity: entities) + for(auto& entity: entities) { - entity.update(); + entity->update(); int heuristicSign = 0; - switch (entity.getState()) + switch (entity->getState()) { case State::Moving: { @@ -57,9 +67,9 @@ void Level::runStep() { heuristicSign = -1; } - if(entity.getTarget() != entity.getPosition()) + if(entity->getTarget() != entity->getPosition()) { - entity.move(findPath(entity.getPosition(),entity.getTarget(),heuristicSign)); + entity->move(findPath(entity->getPosition(),entity->getTarget(),heuristicSign)); } break; } @@ -67,7 +77,7 @@ void Level::runStep() case State::Idle:break; } - std::vector entitySquares = entity.getOccupiedSquares(); + std::vector entitySquares = entity->getOccupiedSquares(); // FIXME : Very heavy memory usage and a lot of duplicates, slows down occupiedSquares.find() calls too. // Copy the squares to the currently occupied squares : prevents moving into an entity that just moved diff --git a/src/Level.h b/src/Level.h index f6219c6..0171da9 100644 --- a/src/Level.h +++ b/src/Level.h @@ -21,13 +21,20 @@ class Level { public: Level(const pugi::xml_document& xmlDoc, const pro_maat::TextureStore& textureStore); + /// Add a new rule on top of existing ones, thus with a lower priority + /// + /// \param affectedEntities Entities to which the rule will be applied + /// \param targetState What action will the rule perform + /// \param targetEntities Which entities will be targeted + void addRule(EntityType affectedEntities, State targetState, EntityType targetEntities); + void render(sf::RenderWindow& renderWindow) const; void runStep(); private: const pro_maat::GridPos size; - std::vector entities; + std::vector> entities; const pro_maat::TextureStore& textures; diff --git a/src/Rule.cpp b/src/Rule.cpp index 3556db6..50b5c1b 100644 --- a/src/Rule.cpp +++ b/src/Rule.cpp @@ -3,3 +3,170 @@ // #include "Rule.h" + + +Rule::Rule(Entity* entity, State targetState, EntityType targetType, + std::vector>& entities, + const std::vector& occupiedSquares, const pro_maat::GridPos& mapSize) + : entity(entity), + targetState(targetState), + targetType(targetType), + entities(entities), + occupiedSquares(occupiedSquares), + mapSize(mapSize) +{} + +void Rule::update() +{ + if (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 (targetState == State::Waiting) + { + entity->nextTarget = entity->getPosition(); + entity->nextState = State::Waiting; + entity->update(); + } + else + { + entity->nextTarget = entity->getPosition(); + entity->nextState = State::Idle; + entity->update(); + } +} + +pro_maat::GridPos Rule::findTarget() +{ + // TODO : Sorting in place, consider using shared_ptr ? +// std::vector sortedEntities{}; +// sortedEntities.insert(sortedEntities.end(),entities.begin(),entities.end()); + + // Compares entities via their distance to the current entity + auto distanceSortEntities = [this](const std::unique_ptr& leftHandSide, const std::unique_ptr& 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(entities.begin(),entities.end(),distanceSortEntities); + + for(const auto& processingEntity : entities) + { + if(processingEntity->getType() != targetType) continue; + + 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;igetPosition()); +} + + +//////////////////////// +// // +// Delegate overrides // +// // +//////////////////////// + + +void Rule::move(Orientation orientation) +{ + entity->move(orientation); +} + +State Rule::getState() const +{ + return entity->getState(); +} + +EntityType Rule::getType() const +{ + return entity->getType(); +} + +const sf::RectangleShape& Rule::getShape() const +{ + return entity->getShape(); +} + +pro_maat::GridPos Rule::getTarget() const +{ + return entity->getTarget(); +} + +const pro_maat::GridPos Rule::getPosition() const +{ + return entity->getPosition(); +} + +const std::vector Rule::getOccupiedSquares() const +{ + return entity->getOccupiedSquares(); +} diff --git a/src/Rule.h b/src/Rule.h index fae04f9..4215a51 100644 --- a/src/Rule.h +++ b/src/Rule.h @@ -12,12 +12,12 @@ /// Decorates entities with rules which will modify its behaviour -template -class Rule : private Entity +class Rule : public Entity { public: - Rule(Entity& entity, const std::vector& entities, - const std::vector& occupiedSquares, const pro_maat::GridPos& mapSize); + // The Rule object takes ownership of the Entity* + Rule(Entity* entity, State targetState, EntityType targetType, std::vector>& entities, + const std::vector& occupiedSquares, const pro_maat::GridPos& mapSize); /// Update according to the targetState and targetType void update() override; @@ -26,6 +26,7 @@ public: void move(Orientation orientation) override; State getState() const override; + EntityType getType() const override; const sf::RectangleShape& getShape() const override; pro_maat::GridPos getTarget() const override; const pro_maat::GridPos getPosition() const override; @@ -33,182 +34,20 @@ public: private: /// Finds the closest free square adjacent to the closest target entity type. + /// Currently not thread-safe ! /// \return Suitable target square or current position if none was found. pro_maat::GridPos findTarget(); - Entity& entity; - const std::vector& entities; + std::unique_ptr entity; + + State targetState; + EntityType targetType; + + // TOOD : dropped const-qualifier. Consider using shared_ptr ? + 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 -pro_maat::GridPos Rule::getTarget() const -{ - return entity.getTarget(); -} - -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