Rules can be created from the UI Game can be stopped/started from the UI Current level can be reset (Load from file again) Added "Game" target and main file TODO (A lot): - Unit tests - Fully functionnal UI - Win conditions - Level background - Level switching/progression - Interesting and varied rules - Multi-scale pathfinding - etc
206 lines
No EOL
6.8 KiB
C++
206 lines
No EOL
6.8 KiB
C++
//
|
|
// Created by trotfunky on 06/06/19.
|
|
//
|
|
|
|
#include "Level.h"
|
|
|
|
|
|
Level::Level(const pugi::xml_document& xmlDoc, const pro_maat::TextureStore& textureStore, int id)
|
|
: levelID(id), size(xmlDoc.child("Level").attribute("w").as_int(),xmlDoc.child("Level").attribute("h").as_int()),
|
|
textures(textureStore)
|
|
{
|
|
pugi::xml_node levelNode = xmlDoc.child("Level");
|
|
for(const pugi::xml_node& child : levelNode.children())
|
|
{
|
|
if(!strncmp(child.name(),"Entity",6))
|
|
{
|
|
entities.emplace_back(std::make_shared<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()->get()->getOccupiedSquares();
|
|
std::move(entitySquares.begin(),entitySquares.end(),std::back_inserter(occupiedSquares));
|
|
}
|
|
}
|
|
|
|
// FIXME : For testing purposes
|
|
// addRule(EntityType::Noble,State::Moving,EntityType::House);
|
|
// addRule(EntityType::Citizen,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_shared<Rule>(entity,targetState,targetEntities,entities,occupiedSquares,size);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Level::render(sf::RenderWindow& renderWindow) const
|
|
{
|
|
for(const auto& entity : entities)
|
|
{
|
|
renderWindow.draw(entity->getShape());
|
|
}
|
|
}
|
|
|
|
void Level::runStep()
|
|
{
|
|
std::vector<pro_maat::GridPos> newOccupiedSquares{};
|
|
newOccupiedSquares.reserve(occupiedSquares.size());
|
|
|
|
for(auto& entity: entities)
|
|
{
|
|
entity->update();
|
|
int heuristicSign = 0;
|
|
switch (entity->getState())
|
|
{
|
|
case State::Moving:
|
|
{
|
|
heuristicSign = 1;
|
|
[[fallthrough]];
|
|
}
|
|
case State::Fleeing:
|
|
{
|
|
if(heuristicSign == 0)
|
|
{
|
|
heuristicSign = -1;
|
|
}
|
|
if(entity->getTarget() != entity->getPosition())
|
|
{
|
|
entity->move(findPath(entity->getPosition(),entity->getTarget(),heuristicSign));
|
|
}
|
|
break;
|
|
}
|
|
case State::Waiting:break;
|
|
case State::Idle:break;
|
|
}
|
|
|
|
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
|
|
occupiedSquares.insert(occupiedSquares.end(),entitySquares.begin(),entitySquares.end());
|
|
std::sort(occupiedSquares.begin(),occupiedSquares.end());
|
|
|
|
// Moves the occupied squares from the entity to the new occupied squares vector
|
|
std::move(entitySquares.begin(),entitySquares.end(),std::back_inserter(newOccupiedSquares));
|
|
}
|
|
|
|
occupiedSquares.swap(newOccupiedSquares);
|
|
// Sort the vector as to get O(ln(n)) complexity when searching for a square
|
|
std::sort(occupiedSquares.begin(),occupiedSquares.end());
|
|
}
|
|
|
|
Orientation Level::findPath(pro_maat::GridPos start, pro_maat::GridPos goal, int sign)
|
|
{
|
|
std::set<pro_maat::GridPos> openNodes{start};
|
|
std::set<pro_maat::GridPos> closedNodes{};
|
|
|
|
std::map<pro_maat::GridPos,float> estimatedCosts{{start,pro_maat::manhattanDistance(start,goal)*sign}};
|
|
std::map<pro_maat::GridPos,float> pathCosts{{start,0}};
|
|
std::map<pro_maat::GridPos,pro_maat::GridPos> paths{};
|
|
|
|
|
|
// FIXME : Find an efficient way to get rid of openNodes.find calls
|
|
// Lambda checking if the current element is also in the open nodes set
|
|
auto compWithOpen = [&openNodes](const std::pair<pro_maat::GridPos,float>& leftHandSide,
|
|
const std::pair<pro_maat::GridPos,float>& rightHandSide){
|
|
if(openNodes.find(leftHandSide.first) == openNodes.end())
|
|
{
|
|
return (false);
|
|
}
|
|
else if(openNodes.find(rightHandSide.first) == openNodes.end())
|
|
{
|
|
return (true);
|
|
}
|
|
else
|
|
{
|
|
return (leftHandSide.second < rightHandSide.second);
|
|
}
|
|
};
|
|
|
|
|
|
while(!openNodes.empty())
|
|
{
|
|
// Expand from the open node with the smallest estimated cost
|
|
pro_maat::GridPos currentNode = std::min_element(estimatedCosts.begin(),estimatedCosts.end(),compWithOpen)->first;
|
|
|
|
// FIXME : Lots of bad cases when fleeing
|
|
if(currentNode == goal)
|
|
{
|
|
if(currentNode == start)
|
|
{
|
|
return(Orientation::None);
|
|
}
|
|
|
|
// Trace back to the start
|
|
pro_maat::GridPos& previousNode = currentNode;
|
|
for(;paths[previousNode]!=start;previousNode = paths[previousNode])
|
|
{}
|
|
|
|
pro_maat::GridUnit dx = previousNode.first - start.first;
|
|
pro_maat::GridUnit dy = previousNode.second - start.second;
|
|
|
|
if(dx < 0)
|
|
{
|
|
return(Orientation::West);
|
|
}
|
|
else if(dx > 0)
|
|
{
|
|
return(Orientation::East);
|
|
}
|
|
else if(dy < 0)
|
|
{
|
|
return(Orientation::North);
|
|
}
|
|
else if(dy > 0 )
|
|
{
|
|
return(Orientation::South);
|
|
}
|
|
else
|
|
{
|
|
return(Orientation::None);
|
|
}
|
|
}
|
|
|
|
openNodes.erase(currentNode);
|
|
closedNodes.insert(currentNode);
|
|
|
|
for(pro_maat::GridPos neighbour : pro_maat::getNeighbours(currentNode,size))
|
|
{
|
|
// Checks if node has been closed or is an obstacle
|
|
if(std::find(occupiedSquares.begin(),occupiedSquares.end(),neighbour) != occupiedSquares.end() ||
|
|
closedNodes.find(neighbour) != closedNodes.end())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// As the neighbours are adjacent squares, distance from them is always +-1
|
|
float newPathCost = pathCosts[currentNode] + sign;
|
|
|
|
if(openNodes.find(neighbour) == openNodes.end())
|
|
{
|
|
openNodes.insert(neighbour);
|
|
}
|
|
else if(newPathCost >= pathCosts[neighbour]) // If the node is open but this path is longer, ignore it
|
|
{
|
|
continue;
|
|
}
|
|
|
|
pathCosts.insert_or_assign(neighbour,newPathCost);
|
|
estimatedCosts.insert_or_assign(neighbour,newPathCost + pro_maat::manhattanDistance(neighbour,goal)*sign);
|
|
paths.insert_or_assign(neighbour,currentNode);
|
|
}
|
|
}
|
|
|
|
// If we did not find a path, do not move
|
|
return Orientation::None;
|
|
}
|
|
|
|
int Level::getLevelID()
|
|
{
|
|
return levelID;
|
|
} |