Rules are not template anymore (it was dumb)

Entities are now managed with unique_ptr to allow polymorphism (and good memory management)
Rules can be added via the Level object
Fixed Rule::findTarget not caring about targetType

TODO : Find why pathfinding (unlikely) or findTarget (most likely) is broken
This commit is contained in:
trotFunky 2019-06-10 04:04:03 +02:00
parent 4ab83f1124
commit c94fc5a0be
7 changed files with 221 additions and 193 deletions

View file

@ -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

View file

@ -79,6 +79,11 @@ State Entity::getState() const
return currentState;
}
EntityType Entity::getType() const
{
return type;
}
pro_maat::GridPos Entity::getTarget() const
{
return target;

View file

@ -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<State targetState, EntityType targetType>
friend class Rule;
};

View file

@ -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<Entity>(child,textures.at(child.attribute("textureId").as_int(0)).get()));
// Initialize the occupied squares vector with the new entity's squares
std::vector<pro_maat::GridPos> entitySquares = entities.rbegin()->getOccupiedSquares();
std::vector<pro_maat::GridPos> entitySquares = entities.rbegin()->get()->getOccupiedSquares();
std::move(entitySquares.begin(),entitySquares.end(),std::back_inserter(occupiedSquares));
}
}
// FIXME : For testing purposes
Rule<State::Moving,EntityType::House> newRule(*entities.rbegin(),entities,occupiedSquares,size);
addRule(EntityType::Significant,State::Moving,EntityType::Citizen);
}
std::move(entitySquares.begin(),entitySquares.end(),std::back_inserter(occupiedSquares));
void Level::addRule(EntityType affectedEntities, const State targetState, EntityType targetEntities)
{
for(auto& entity : entities)
{
if(entity->getType() == affectedEntities)
{
entity = std::make_unique<Rule>(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<pro_maat::GridPos> 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<pro_maat::GridPos> entitySquares = entity.getOccupiedSquares();
std::vector<pro_maat::GridPos> 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

View file

@ -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<Entity> entities;
std::vector<std::unique_ptr<Entity>> entities;
const pro_maat::TextureStore& textures;

View file

@ -3,3 +3,170 @@
//
#include "Rule.h"
Rule::Rule(Entity* entity, State targetState, EntityType targetType,
std::vector<std::unique_ptr<Entity>>& entities,
const std::vector<pro_maat::GridPos>& 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<Entity> 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<Entity>& leftHandSide, const std::unique_ptr<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(entities.begin(),entities.end(),distanceSortEntities);
for(const auto& processingEntity : entities)
{
if(processingEntity->getType() != targetType) continue;
std::vector<pro_maat::GridPos> 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<entityWidth+2;i++)
{
potentialTargets.emplace_back(topLeftCorner.first+i,topLeftCorner.second);
potentialTargets.emplace_back(topLeftCorner.first+i,topLeftCorner.second+entityHeight+2);
}
// Get the missing adjacent squares from the sides
for(int i = 1;i<=entityHeight;i++)
{
potentialTargets.emplace_back(topLeftCorner.first,topLeftCorner.second+i);
potentialTargets.emplace_back(topLeftCorner.first+entityWidth+2,topLeftCorner.second+i);
}
std::sort(potentialTargets.begin(),potentialTargets.end(),distanceSortSquares);
auto target = std::min_element(potentialTargets.begin(),potentialTargets.end(),bestTarget);
if(target != potentialTargets.end())
{
return (*target);
}
}
return (entity->getPosition());
}
////////////////////////
// //
// 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<pro_maat::GridPos> Rule::getOccupiedSquares() const
{
return entity->getOccupiedSquares();
}

View file

@ -12,11 +12,11 @@
/// Decorates entities with rules which will modify its behaviour
template <State targetState, EntityType targetType>
class Rule : private Entity
class Rule : public Entity
{
public:
Rule(Entity& entity, const std::vector<Entity>& entities,
// The Rule object takes ownership of the Entity*
Rule(Entity* entity, State targetState, EntityType targetType, std::vector<std::unique_ptr<Entity>>& entities,
const std::vector<pro_maat::GridPos>& occupiedSquares, const pro_maat::GridPos& mapSize);
/// Update according to the targetState and targetType
@ -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<Entity>& entities;
std::unique_ptr<Entity> entity;
State targetState;
EntityType targetType;
// TOOD : dropped const-qualifier. Consider using shared_ptr ?
std::vector<std::unique_ptr<Entity>>& entities;
const std::vector<pro_maat::GridPos>& occupiedSquares;
const pro_maat::GridPos& mapSize;
};
template<State targetState, EntityType targetType>
Rule<targetState, targetType>::Rule(Entity& entity,
const std::vector<Entity>& entities,
const std::vector<pro_maat::GridPos>& occupiedSquares,
const pro_maat::GridPos& mapSize)
: entity(entity),
entities(entities),
occupiedSquares(occupiedSquares),
mapSize(mapSize)
{}
template<State targetState, EntityType targetType>
void Rule<targetState, targetType>::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<State targetState, EntityType targetType>
pro_maat::GridPos Rule<targetState, targetType>::findTarget()
{
std::vector<Entity> 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<pro_maat::GridPos> 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<entityWidth+2;i++)
{
potentialTargets.emplace_back(topLeftCorner.first+i,topLeftCorner.second);
potentialTargets.emplace_back(topLeftCorner.first+i,topLeftCorner.second+entityHeight+2);
}
// Get the missing adjacent squares from the sides
for(int i = 1;i<=entityHeight;i++)
{
potentialTargets.emplace_back(topLeftCorner.first,topLeftCorner.second+i);
potentialTargets.emplace_back(topLeftCorner.first+entityWidth+2,topLeftCorner.second+i);
}
std::sort(potentialTargets.begin(),potentialTargets.end(),distanceSortSquares);
auto target = std::min_element(potentialTargets.begin(),potentialTargets.end(),bestTarget);
if(target != potentialTargets.end())
{
return (*target);
}
}
return (entity.getPosition());
}
////////////////////////
// //
// Delegate overrides //
// //
////////////////////////
template<State targetState, EntityType targetType>
void Rule<targetState, targetType>::move(Orientation orientation)
{
entity.move(orientation);
}
template<State targetState, EntityType targetType>
State Rule<targetState, targetType>::getState() const
{
return entity.getState();
}
template<State targetState, EntityType targetType>
const sf::RectangleShape& Rule<targetState, targetType>::getShape() const
{
return entity.getShape();
}
template<State targetState, EntityType targetType>
pro_maat::GridPos Rule<targetState, targetType>::getTarget() const
{
return entity.getTarget();
}
template<State targetState, EntityType targetType>
const pro_maat::GridPos Rule<targetState, targetType>::getPosition() const
{
return entity.getPosition();
}
template<State targetState, EntityType targetType>
const std::vector<pro_maat::GridPos> Rule<targetState, targetType>::getOccupiedSquares() const
{
return entity.getOccupiedSquares();
}
#endif //PROJECT_MAAT_RULES_H