Compare commits

...

7 commits

Author SHA1 Message Date
60770b5395 Added first UI elements
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
2019-06-11 00:33:44 +02:00
8199b7d036 Fixed finding entities finding themselves when targeting their type 2019-06-10 23:22:43 +02:00
2d8b1e53ce Final fix for end of movement
I was being dumb again : if we are adjacent to a target entity, stop moving
2019-06-10 21:53:39 +02:00
b85d46e450 Fixed oscillations for real
Entities do not move after completing their moves...
2019-06-10 20:05:46 +02:00
e1f23f67a2 Fixed oscillating around end position 2019-06-10 19:14:43 +02:00
5ccd7d0c13 Fixed movement of multiple entities.
Changed std::unique_ptr<Entity> to std::shared_ptr<Entity>.
Fixed pathfinding away from targets

TODO :
 - Fix oscillation around destination
 - Fix fleeing from adjacent target
 - Win conditions
 - GUI
2019-06-10 18:46:37 +02:00
293a564a29 Fixed Rule::findTarget() not finding good or even valid targets 2019-06-10 16:04:12 +02:00
20 changed files with 327 additions and 65 deletions

View file

@ -6,6 +6,7 @@ set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake_modules" ${CMAKE_MODULE_PATH})
add_subdirectory(src) add_subdirectory(src)
add_subdirectory(tests) add_subdirectory(tests)
add_subdirectory(game)
# Detect and add SFML # Detect and add SFML
find_package(SFML COMPONENTS system window graphics network audio REQUIRED) find_package(SFML COMPONENTS system window graphics network audio REQUIRED)
@ -13,6 +14,12 @@ if(NOT SFML_FOUND)
message(FATAL_ERROR "SFML could not be found") message(FATAL_ERROR "SFML could not be found")
endif() endif()
# Detect and add TGUI
find_package(TGUI REQUIRED)
if(NOT TGUI_FOUND)
message(FATAL_ERROR "SFML could not be found")
endif()
# Detect and add GTest # Detect and add GTest
find_package(GTest REQUIRED) find_package(GTest REQUIRED)
if(GTest_FOUND) if(GTest_FOUND)

View file

@ -5,10 +5,20 @@ Project Maat is the codename of my C++ course "mini-project" based on the quote
This game is aimed to be a puzzle game in which the player sets different laws for the world and the people within it as to control what happens after hitting "play" and achieve the level's goal. I was inspired both by [The Incredible Machine](https://en.wikipedia.org/wiki/The_Incredible_Machine_(video_game)) and its successors and by a much more recent game : [Baba is you](https://en.wikipedia.org/wiki/Baba_Is_You). This game is aimed to be a puzzle game in which the player sets different laws for the world and the people within it as to control what happens after hitting "play" and achieve the level's goal. I was inspired both by [The Incredible Machine](https://en.wikipedia.org/wiki/The_Incredible_Machine_(video_game)) and its successors and by a much more recent game : [Baba is you](https://en.wikipedia.org/wiki/Baba_Is_You).
## Dependencies
The following libraries must be installed on your system or findable by CMake to be able to build:
- SFML
- TGui
- PugiXML
- GTest
## Structure ## Structure
![UML Diagram](UML_Class_Diagram.png) ![UML Diagram](UML_Class_Diagram.png)
The class diagram omits most constructors and getters.
## TODO ## TODO
- [x] Level - [x] Level
@ -23,16 +33,19 @@ This game is aimed to be a puzzle game in which the player sets different laws f
- [ ] Rules - [ ] Rules
- [x] Creation - [x] Creation
- [x] Interaction with entities - [x] Interaction with entities
- [ ] Parsing of rules and creation of subsequent decorators - [x] Parsing of rules and creation of subsequent decorators
- [x] Find optimal target for pathfinding - [x] Find optimal target for pathfinding
- [ ] Other rules (Waiting...)
- [ ] Graphics - [ ] Graphics
- [x] Scene rendering - [x] Scene rendering
- [x] Some kind of UI
- [ ] UI - [ ] UI
- [ ] Menu ? - [ ] Menu ?
- [ ] Gameloop - [ ] Gameloop
- [ ] Transition from starting to running state and vice-versa - [x] Transition from starting to running state and vice-versa
- [ ] Entity behaviour evolution - [x] Entity behaviour evolution
- [x] Rule application - [x] Rule application
- [ ] Saving state through serialization ? - [ ] Win condition
- [ ] UML - [ ] ~~Saving state through serialization ?~~
- [x] UML
- [ ] Unit tests - [ ] Unit tests

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Before After
Before After

12
game/CMakeLists.txt Normal file
View file

@ -0,0 +1,12 @@
cmake_minimum_required(VERSION 3.14)
project(project_maat)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake_modules" ${CMAKE_MODULE_PATH})
add_executable(game main.cpp)
target_include_directories(game PRIVATE ${PROJECT_SOURCE_DIR}/../src)
target_link_libraries(game
engine)

15
game/main.cpp Normal file
View file

@ -0,0 +1,15 @@
//
// Created by trotfunky on 11/06/19.
//
#include "Game.h"
int main()
{
std::vector<std::string> textures = {"Head_Boy.png","Head_Significant_Boy.png","Building.png"};
std::vector<std::string> levels = {"test_level.xml"};
Game game(levels,textures);
game.loadLevel(0);
game.runGame();
}

5
resources/level1.xml Normal file
View file

@ -0,0 +1,5 @@
<?xml version = "1.0"?>
<Level w="20" h="20" textureId="0">
<Entity x="5" y="5" type="Citizen"/>
<Entity x="10" y="5" type="Noble" textureId="1"/>
</Level>

33
resources/level2.xml Normal file
View file

@ -0,0 +1,33 @@
<?xml version = "1.0"?>
<Level w="20" h="20" textureId="0">
<Entity x="2" y="2" type="Citizen"/>
<Entity x="2" y="3" type="Citizen"/>
<Entity x="2" y="4" type="Citizen"/>
<Entity x="2" y="5" type="Citizen"/>
<Entity x="2" y="6" type="Citizen"/>
<Entity x="2" y="7" type="Citizen"/>
<Entity x="7" y="2" type="Citizen"/>
<Entity x="7" y="3" type="Citizen"/>
<Entity x="7" y="4" type="Citizen"/>
<Entity x="7" y="5" type="Citizen"/>
<Entity x="7" y="6" type="Citizen"/>
<Entity x="7" y="7" type="Citizen"/>
<Entity y="2" x="2" type="Citizen"/>
<Entity y="2" x="3" type="Citizen"/>
<Entity y="2" x="4" type="Citizen"/>
<Entity y="2" x="5" type="Citizen"/>
<Entity y="2" x="6" type="Citizen"/>
<Entity y="2" x="7" type="Citizen"/>
<Entity y="7" x="2" type="Citizen"/>
<Entity y="7" x="3" type="Citizen"/>
<Entity y="7" x="4" type="Citizen"/>
<Entity y="7" x="5" type="Citizen"/>
<Entity y="7" x="6" type="Citizen"/>
<Entity y="7" x="7" type="Citizen"/>
<Entity x="10" y="10" type="Noble" textureId="1"/>
<Entity x="3" y="3" w="4" h="4" type="House" textureId="2"/>
</Level>

View file

@ -1,7 +1,7 @@
<?xml version = "1.0"?> <?xml version = "1.0"?>
<Level w="20" h="20" textureId="0"> <Level w="20" h="20" textureId="0">
<Entity x="0" y="0" type="Citizen"/> <Entity x="0" y="0" type="Citizen"/>
<Entity x="0" y="1" type="Citizen"/> <Entity x="0" y="10" type="Citizen"/>
<Entity x="10" y="10" type="Significant" textureId="1"/> <Entity x="10" y="10" type="Noble" textureId="1"/>
<Entity x="1" y="1" w="4" h="4" type="House" textureId="2"/> <Entity x="2" y="2" w="4" h="4" type="House" textureId="2"/>
</Level> </Level>

View file

@ -10,7 +10,8 @@ target_link_libraries(engine
sfml-window sfml-window
sfml-graphics sfml-graphics
sfml-system sfml-system
pugixml) pugixml
tgui)
if (CMAKE_COMPILER_IS_GNUCXX) if (CMAKE_COMPILER_IS_GNUCXX)
target_compile_options(engine PRIVATE -Wall -Wpedantic -Wextra) target_compile_options(engine PRIVATE -Wall -Wpedantic -Wextra)

View file

@ -6,10 +6,14 @@
const std::map<std::string,EntityType> Entity::entityTypeLookup = { const std::map<std::string,EntityType> Entity::entityTypeLookup = {
{"Citizen",EntityType::Citizen}, {"Citizen",EntityType::Citizen},
{"Significant",EntityType::Significant}, {"Noble",EntityType::Noble},
{"House",EntityType::House}, {"House",EntityType::House},
{"Car",EntityType::Car}}; {"Car",EntityType::Car}};
const std::map<std::string,State> Entity::stateLookup = {
{"Seek",State::Moving},
{"Flee",State::Fleeing}};
Entity::Entity(pro_maat::GridUnit x, pro_maat::GridUnit y, EntityType type, sf::Texture* texture, int width, int height) : type(type) Entity::Entity(pro_maat::GridUnit x, pro_maat::GridUnit y, EntityType type, sf::Texture* texture, int width, int height) : type(type)
{ {
shape = sf::RectangleShape(sf::Vector2f(width*pro_maat::pixelsPerUnit,height*pro_maat::pixelsPerUnit)); shape = sf::RectangleShape(sf::Vector2f(width*pro_maat::pixelsPerUnit,height*pro_maat::pixelsPerUnit));

View file

@ -15,7 +15,7 @@
enum class EntityType enum class EntityType
{ {
Citizen, Citizen,
Significant, Noble,
House, House,
Car, Car,
}; };
@ -62,14 +62,15 @@ public:
virtual const std::vector<pro_maat::GridPos> getOccupiedSquares() const; virtual const std::vector<pro_maat::GridPos> getOccupiedSquares() const;
static const std::map<std::string,EntityType> entityTypeLookup;
static const std::map<std::string,State> stateLookup;
protected: protected:
/// Empty constructor for derived class instanciation /// Empty constructor for derived class instanciation
Entity(); Entity();
private: private:
static const std::map<std::string,EntityType> entityTypeLookup;
EntityType type; EntityType type;
// As it contains position, size and orientation, we do not need anything more // As it contains position, size and orientation, we do not need anything more

View file

@ -8,22 +8,25 @@
Game::Game(std::vector<std::string>& levels, std::vector<std::string>& textures) : levelFiles(std::move(levels)), Game::Game(std::vector<std::string>& levels, std::vector<std::string>& textures) : levelFiles(std::move(levels)),
textureFiles(std::move(textures)) textureFiles(std::move(textures)), running(false), ruleCount(0)
{ {
loadTextures(); loadTextures();
} }
void Game::loadLevel(int levelId) void Game::loadLevel(int levelID)
{ {
// TODO : Reset rules when reseting level
running = false;
pugi::xml_document document; pugi::xml_document document;
pugi::xml_parse_result result = document.load_file((pro_maat::levelFolder+levelFiles.at(levelId)).c_str()); pugi::xml_parse_result result = document.load_file((pro_maat::levelFolder+levelFiles.at(levelID)).c_str());
if(!result) if(!result)
{ {
pro_maat::errorWindow(result.description()); pro_maat::errorWindow(result.description());
} }
currentLevel = std::make_unique<Level>(document,textures); currentLevel = std::make_unique<Level>(document,textures,levelID);
} }
void Game::loadTextures() void Game::loadTextures()
@ -42,6 +45,9 @@ void Game::runGame()
sf::Clock clock; sf::Clock clock;
tgui::Gui gui(window);
addWidgets(gui);
while (window.isOpen()) while (window.isOpen())
{ {
sf::Event event; sf::Event event;
@ -51,8 +57,10 @@ void Game::runGame()
{ {
window.close(); window.close();
} }
gui.handleEvent(event);
} }
if (clock.getElapsedTime().asMilliseconds() >= 200) if (running && clock.getElapsedTime().asMilliseconds() >= 200)
{ {
currentLevel->runStep(); currentLevel->runStep();
clock.restart(); clock.restart();
@ -60,11 +68,135 @@ void Game::runGame()
window.clear(sf::Color::White); window.clear(sf::Color::White);
// TODO : Consider drawing in a sf::RenderTexture to allow positioning the level at a fixed position ? // TODO : Consider drawing in a sf::RenderTexture to allow positioning the level at a fixed position ?
currentLevel->render(window); currentLevel->render(window);
gui.draw();
window.display(); window.display();
} }
} }
void Game::addWidgets(tgui::Gui& gui)
{
tgui::Button::Ptr button = tgui::Button::create("Start");
button->setSize("10%","5%");
button->setPosition("89%","94%");
button->connect("pressed",&Game::setRunning,this,true,std::ref(gui));
gui.add(button,"start");
// TODO : Use clone/copy ?
button = tgui::Button::create("Stop");
button->setSize("10%","5%");
button->setPosition("79%","94%");
button->connect("pressed",&Game::setRunning,this,false,std::ref(gui));
gui.add(button,"stop");
button = tgui::Button::create("Reset");
button->setSize("10%","5%");
button->setPosition("69%","94%");
button->connect("pressed",&Game::loadLevel,this,currentLevel->getLevelID());
gui.add(button,"reset");
button = tgui::Button::create("Add a new rule");
button->setSize("10%","5%");
button->setPosition("89%","6%");
button->connect("pressed",&Game::addRule,this,std::ref(gui));
gui.add(button,"add");
}
Game::operator bool() const Game::operator bool() const
{ {
return (currentLevel ? true : false); return (currentLevel ? true : false);
} }
//
// Widget callbacks
//
void Game::setRunning(bool newState, tgui::Gui& gui)
{
running = newState;
// TODO : Iterate over all rules
auto affectedTypeCombo = gui.get<tgui::ComboBox>("AffectedType0");
if(!affectedTypeCombo) return;
auto targetTypeCombo = gui.get<tgui::ComboBox>("TargetType0");
if(!targetTypeCombo) return;
auto actionCombo = gui.get<tgui::ComboBox>("Action0");
if(!actionCombo) return;
// If the rule is complete only
if(!affectedTypeCombo->getSelectedItem().isEmpty() &&
!targetTypeCombo->getSelectedItem().isEmpty() &&
!actionCombo->getSelectedItem().isEmpty())
{
currentLevel->addRule(Entity::entityTypeLookup.at(affectedTypeCombo->getSelectedItem()),
Entity::stateLookup.at(actionCombo->getSelectedItem()),
Entity::entityTypeLookup.at(targetTypeCombo->getSelectedItem()));
}
}
void Game::addRule(tgui::Gui& gui)
{
if(ruleCount >= pro_maat::maxRules) return;
// To update names with indexes
std::stringstream string;
tgui::ComboBox::Ptr combo = tgui::ComboBox::create();
combo->addItem("Citizen");
combo->addItem("Noble");
combo->setPosition("85%","5%");
// TODO : Move height according to number of rules
// Keep the proportions while moving
// sf::Vector2f tempPos = combo->getPosition();
// tempPos.y += tempPos.y*2*ruleCount;
// combo->setPosition(tempPos);
combo->setSize("5%","2%");
string << "AffectedType" << ruleCount;
gui.add(combo,string.str());
string.str("");
// TODO : Use clone/copy ?
combo = tgui::ComboBox::create();
combo->addItem("Citizen");
combo->addItem("Noble");
combo->addItem("House");
combo->setSize("5%","2%");
combo->setPosition("95%","5%");
// Keep the proportions while moving
// tempPos = combo->getPosition();
// tempPos.y += tempPos.y*2*ruleCount;
// combo->setPosition(tempPos);
string << "TargetType" << ruleCount;
gui.add(combo,string.str());
string.str("");
combo = tgui::ComboBox::create();
combo->addItem("Seek");
combo->addItem("Flee");
combo->setSize("4%","2%");
combo->setPosition("90.5%","5%");
// Keep the proportions while moving
// tempPos = combo->getPosition();
// tempPos.y += tempPos.y*2*ruleCount;
// combo->setPosition(tempPos);
string << "Action" << ruleCount;
gui.add(combo,string.str());
string.str("");
// TODO : Move the button down with each rule then hide it
// TODO : Hide button when max rules hit
// FIXME : Cannot hide ?
gui.get<tgui::Button>("add")->setSize("0%","0%");
gui.get<tgui::Button>("add")->setTextSize(0);
gui.get<tgui::Button>("add")->setPosition("-10%","-10%");
ruleCount++;
}

View file

@ -6,21 +6,22 @@
#define SRC_GAME_H #define SRC_GAME_H
#include <SFML/Graphics.hpp> #include <SFML/Graphics.hpp>
#include <TGUI/TGUI.hpp>
#include <vector> #include <vector>
#include <iostream> #include <iostream>
#include <sstream>
#include "Level.h" #include "Level.h"
#include "Entity.h" #include "Entity.h"
#include "Utils.h" #include "Utils.h"
class Game { class Game {
public: public:
Game(std::vector<std::string>& levels, std::vector<std::string>& textures); Game(std::vector<std::string>& levels, std::vector<std::string>& textures);
/// Loads the level of corresponding ID from Game::levelFiles /// Loads the level of corresponding ID from Game::levelFiles
/// Closes the program if there is a fatal error (Missing file, bad file...) /// Closes the program if there is a fatal error (Missing file, bad file...)
void loadLevel(int levelId); void loadLevel(int levelID);
std::unique_ptr<Level> currentLevel; std::unique_ptr<Level> currentLevel;
@ -37,7 +38,18 @@ private:
/// Stores pointers to textures for future use /// Stores pointers to textures for future use
pro_maat::TextureStore textures; pro_maat::TextureStore textures;
bool running;
unsigned int ruleCount;
void loadTextures(); void loadTextures();
void addWidgets(tgui::Gui& gui);
//
// Widget callbacks
//
void setRunning(bool newState,tgui::Gui& gui);
void addRule(tgui::Gui& gui);
}; };

View file

@ -5,8 +5,8 @@
#include "Level.h" #include "Level.h"
Level::Level(const pugi::xml_document& xmlDoc, const pro_maat::TextureStore& textureStore) Level::Level(const pugi::xml_document& xmlDoc, const pro_maat::TextureStore& textureStore, int id)
: size(xmlDoc.child("Level").attribute("w").as_int(),xmlDoc.child("Level").attribute("h").as_int()), : levelID(id), size(xmlDoc.child("Level").attribute("w").as_int(),xmlDoc.child("Level").attribute("h").as_int()),
textures(textureStore) textures(textureStore)
{ {
pugi::xml_node levelNode = xmlDoc.child("Level"); pugi::xml_node levelNode = xmlDoc.child("Level");
@ -14,7 +14,7 @@ Level::Level(const pugi::xml_document& xmlDoc, const pro_maat::TextureStore& tex
{ {
if(!strncmp(child.name(),"Entity",6)) if(!strncmp(child.name(),"Entity",6))
{ {
entities.emplace_back(std::make_unique<Entity>(child,textures.at(child.attribute("textureId").as_int(0)).get())); 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 // Initialize the occupied squares vector with the new entity's squares
std::vector<pro_maat::GridPos> entitySquares = entities.rbegin()->get()->getOccupiedSquares(); std::vector<pro_maat::GridPos> entitySquares = entities.rbegin()->get()->getOccupiedSquares();
@ -23,7 +23,8 @@ Level::Level(const pugi::xml_document& xmlDoc, const pro_maat::TextureStore& tex
} }
// FIXME : For testing purposes // FIXME : For testing purposes
addRule(EntityType::Significant,State::Moving,EntityType::Citizen); // addRule(EntityType::Noble,State::Moving,EntityType::House);
// addRule(EntityType::Citizen,State::Moving,EntityType::Citizen);
} }
void Level::addRule(EntityType affectedEntities, const State targetState, EntityType targetEntities) void Level::addRule(EntityType affectedEntities, const State targetState, EntityType targetEntities)
@ -32,7 +33,7 @@ void Level::addRule(EntityType affectedEntities, const State targetState, Entity
{ {
if(entity->getType() == affectedEntities) if(entity->getType() == affectedEntities)
{ {
entity = std::make_unique<Rule>(entity.release(),targetState,targetEntities,entities,occupiedSquares,size); entity = std::make_shared<Rule>(entity,targetState,targetEntities,entities,occupiedSquares,size);
} }
} }
} }
@ -127,6 +128,7 @@ Orientation Level::findPath(pro_maat::GridPos start, pro_maat::GridPos goal, int
// Expand from the open node with the smallest estimated cost // Expand from the open node with the smallest estimated cost
pro_maat::GridPos currentNode = std::min_element(estimatedCosts.begin(),estimatedCosts.end(),compWithOpen)->first; 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 == goal)
{ {
if(currentNode == start) if(currentNode == start)
@ -189,7 +191,7 @@ Orientation Level::findPath(pro_maat::GridPos start, pro_maat::GridPos goal, int
} }
pathCosts.insert_or_assign(neighbour,newPathCost); pathCosts.insert_or_assign(neighbour,newPathCost);
estimatedCosts.insert_or_assign(neighbour,newPathCost + pro_maat::manhattanDistance(neighbour,goal)); estimatedCosts.insert_or_assign(neighbour,newPathCost + pro_maat::manhattanDistance(neighbour,goal)*sign);
paths.insert_or_assign(neighbour,currentNode); paths.insert_or_assign(neighbour,currentNode);
} }
} }
@ -197,3 +199,8 @@ Orientation Level::findPath(pro_maat::GridPos start, pro_maat::GridPos goal, int
// If we did not find a path, do not move // If we did not find a path, do not move
return Orientation::None; return Orientation::None;
} }
int Level::getLevelID()
{
return levelID;
}

View file

@ -11,6 +11,7 @@
#include <cstring> #include <cstring>
#include <set> #include <set>
#include <map> #include <map>
#include <memory>
#include "Utils.h" #include "Utils.h"
#include "Entity.h" #include "Entity.h"
@ -19,7 +20,7 @@
class Level { class Level {
public: public:
Level(const pugi::xml_document& xmlDoc, const pro_maat::TextureStore& textureStore); Level(const pugi::xml_document& xmlDoc, const pro_maat::TextureStore& textureStore, int id = 0);
/// Add a new rule on top of existing ones, thus with a lower priority /// Add a new rule on top of existing ones, thus with a lower priority
/// ///
@ -31,10 +32,14 @@ public:
void render(sf::RenderWindow& renderWindow) const; void render(sf::RenderWindow& renderWindow) const;
void runStep(); void runStep();
int getLevelID();
private: private:
int levelID;
const pro_maat::GridPos size; const pro_maat::GridPos size;
std::vector<std::unique_ptr<Entity>> entities; std::vector<std::shared_ptr<Entity>> entities;
const pro_maat::TextureStore& textures; const pro_maat::TextureStore& textures;

View file

@ -5,10 +5,10 @@
#include "Rule.h" #include "Rule.h"
Rule::Rule(Entity* entity, State targetState, EntityType targetType, Rule::Rule(std::shared_ptr<Entity> entity, State targetState, EntityType targetType,
std::vector<std::unique_ptr<Entity>>& entities, const std::vector<std::shared_ptr<Entity>>& entities,
const std::vector<pro_maat::GridPos>& occupiedSquares, const pro_maat::GridPos& mapSize) const std::vector<pro_maat::GridPos>& occupiedSquares, const pro_maat::GridPos& mapSize)
: entity(entity), : entity(std::move(entity)),
targetState(targetState), targetState(targetState),
targetType(targetType), targetType(targetType),
entities(entities), entities(entities),
@ -20,15 +20,17 @@ void Rule::update()
{ {
if (targetState == State::Moving || targetState == State::Fleeing) if (targetState == State::Moving || targetState == State::Fleeing)
{ {
entity->nextTarget = findTarget(); pro_maat::GridPos target = findTarget();
if(entity->nextTarget == entity->getPosition()) if(target != entity->getPosition())
{ {
entity->nextState = State::Idle; entity->nextTarget = target;
entity->nextState = targetState;
} }
else else
{ {
entity->nextState = targetState; entity->nextState = State::Idle;
} }
entity->update(); entity->update();
} }
else if (targetState == State::Waiting) else if (targetState == State::Waiting)
@ -47,12 +49,11 @@ void Rule::update()
pro_maat::GridPos Rule::findTarget() pro_maat::GridPos Rule::findTarget()
{ {
// TODO : Sorting in place, consider using shared_ptr ? std::vector<std::shared_ptr<Entity>> sortedEntities{};
// std::vector<Entity> sortedEntities{}; sortedEntities.insert(sortedEntities.end(),entities.begin(),entities.end());
// sortedEntities.insert(sortedEntities.end(),entities.begin(),entities.end());
// Compares entities via their distance to the current entity // Compares entities via their distance to the current entity
auto distanceSortEntities = [this](const std::unique_ptr<Entity>& leftHandSide, const std::unique_ptr<Entity>& rightHandSide){ auto distanceSortEntities = [this](const std::shared_ptr<Entity>& leftHandSide, const std::shared_ptr<Entity>& rightHandSide){
return (pro_maat::manhattanDistance(entity->getPosition(),leftHandSide->getPosition()) < return (pro_maat::manhattanDistance(entity->getPosition(),leftHandSide->getPosition()) <
pro_maat::manhattanDistance(entity->getPosition(),rightHandSide->getPosition())); pro_maat::manhattanDistance(entity->getPosition(),rightHandSide->getPosition()));
}; };
@ -66,28 +67,35 @@ pro_maat::GridPos Rule::findTarget()
// Get the smallest, non-occupied, inside the map square // Get the smallest, non-occupied, inside the map square
auto bestTarget = [this](const pro_maat::GridPos& leftHandSide, const pro_maat::GridPos& rightHandSide){ 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 the left hand side operand is not in the map or occupied, it is not valid
if(!pro_maat::isInMap(leftHandSide,mapSize) || if(!pro_maat::isInMap(leftHandSide,mapSize))
std::find(occupiedSquares.begin(),occupiedSquares.end(),leftHandSide) != occupiedSquares.end())
{ {
return(false); return(false);
} }
else if(!pro_maat::isInMap(rightHandSide,mapSize) || else if(std::find(occupiedSquares.begin(),occupiedSquares.end(),leftHandSide) != occupiedSquares.end())
std::find(occupiedSquares.begin(),occupiedSquares.end(),rightHandSide) != occupiedSquares.end()) {
return(false);
}
else if(!pro_maat::isInMap(rightHandSide,mapSize))
{
return(true);
}
else if(std::find(occupiedSquares.begin(),occupiedSquares.end(),rightHandSide) != occupiedSquares.end())
{ {
return(true); return(true);
} }
else else
{ {
return(leftHandSide < rightHandSide); return(pro_maat::manhattanDistance(entity->getPosition(),leftHandSide) <
pro_maat::manhattanDistance(entity->getPosition(),rightHandSide));
}}; }};
// Sort in order to minimize entities to process // Sort in order to minimize entities to process
std::sort(entities.begin(),entities.end(),distanceSortEntities); std::sort(sortedEntities.begin(),sortedEntities.end(),distanceSortEntities);
for(const auto& processingEntity : entities) for(const auto& processingEntity : sortedEntities)
{ {
if(processingEntity->getType() != targetType) continue; if(processingEntity->getType() != targetType || processingEntity->getPosition() == entity->getPosition()) continue;
std::vector<pro_maat::GridPos> potentialTargets{}; std::vector<pro_maat::GridPos> potentialTargets{};
@ -96,27 +104,34 @@ pro_maat::GridPos Rule::findTarget()
potentialTargets.reserve((entityWidth+2)*2+entityHeight*2); potentialTargets.reserve((entityWidth+2)*2+entityHeight*2);
// Computes the top left corner of the entity // Computes the top left corner just outside of the entity
pro_maat::GridPos topLeftCorner = processingEntity->getPosition(); pro_maat::GridPos topLeftCorner = processingEntity->getPosition();
topLeftCorner.first -= entityWidth*0.5 - 1; topLeftCorner.first -= std::floor(entityWidth*0.5) + 1;
topLeftCorner.second -= entityHeight*0.5 - 1; topLeftCorner.second -= std::floor(entityHeight*0.5) + 1;
// Get all the top and bottom adjacent squares // Get all the top and bottom adjacent squares
for(int i = 0;i<entityWidth+2;i++) for(int i = 0;i<entityWidth+2;i++)
{ {
potentialTargets.emplace_back(topLeftCorner.first+i,topLeftCorner.second); potentialTargets.emplace_back(topLeftCorner.first+i,topLeftCorner.second);
potentialTargets.emplace_back(topLeftCorner.first+i,topLeftCorner.second+entityHeight+2); potentialTargets.emplace_back(topLeftCorner.first+i,topLeftCorner.second+entityHeight+1);
} }
// Get the missing adjacent squares from the sides // Get the missing adjacent squares from the sides
for(int i = 1;i<=entityHeight;i++) for(int i = 1;i<=entityHeight;i++)
{ {
potentialTargets.emplace_back(topLeftCorner.first,topLeftCorner.second+i); potentialTargets.emplace_back(topLeftCorner.first,topLeftCorner.second+i);
potentialTargets.emplace_back(topLeftCorner.first+entityWidth+2,topLeftCorner.second+i); potentialTargets.emplace_back(topLeftCorner.first+entityWidth+1,topLeftCorner.second+i);
} }
std::sort(potentialTargets.begin(),potentialTargets.end(),distanceSortSquares); std::sort(potentialTargets.begin(),potentialTargets.end(),distanceSortSquares);
// If we are adjacent to a target, we do not need to move
if(std::find(potentialTargets.begin(),potentialTargets.end(),entity->getPosition()) != potentialTargets.end())
{
break;
}
auto target = std::min_element(potentialTargets.begin(),potentialTargets.end(),bestTarget); auto target = std::min_element(potentialTargets.begin(),potentialTargets.end(),bestTarget);
if(target != potentialTargets.end()) if(target != potentialTargets.end())

View file

@ -7,6 +7,8 @@
#include <vector> #include <vector>
#include <algorithm> #include <algorithm>
#include <math.h>
#include <memory>
#include "Entity.h" #include "Entity.h"
@ -16,7 +18,8 @@ class Rule : public Entity
{ {
public: public:
// The Rule object takes ownership of the Entity* // The Rule object takes ownership of the Entity*
Rule(Entity* entity, State targetState, EntityType targetType, std::vector<std::unique_ptr<Entity>>& entities, Rule(std::shared_ptr<Entity> entity, State targetState, EntityType targetType,
const std::vector<std::shared_ptr<Entity>>& entities,
const std::vector<pro_maat::GridPos>& occupiedSquares, const pro_maat::GridPos& mapSize); const std::vector<pro_maat::GridPos>& occupiedSquares, const pro_maat::GridPos& mapSize);
/// Update according to the targetState and targetType /// Update according to the targetState and targetType
@ -38,13 +41,12 @@ private:
/// \return Suitable target square or current position if none was found. /// \return Suitable target square or current position if none was found.
pro_maat::GridPos findTarget(); pro_maat::GridPos findTarget();
std::unique_ptr<Entity> entity; std::shared_ptr<Entity> entity;
State targetState; const State targetState;
EntityType targetType; const EntityType targetType;
// TOOD : dropped const-qualifier. Consider using shared_ptr ? const std::vector<std::shared_ptr<Entity>>& entities;
std::vector<std::unique_ptr<Entity>>& entities;
const std::vector<pro_maat::GridPos>& occupiedSquares; const std::vector<pro_maat::GridPos>& occupiedSquares;
const pro_maat::GridPos& mapSize; const pro_maat::GridPos& mapSize;

View file

@ -39,8 +39,8 @@ namespace pro_maat
bool isInMap(const GridPos& square, const GridPos& gridSize) bool isInMap(const GridPos& square, const GridPos& gridSize)
{ {
return (square.first < 0 || square.second < 0 || return !(square.first < 0 || square.second < 0 ||
square.first >= gridSize.first || square.second >= gridSize.second); square.first > gridSize.first || square.second > gridSize.second);
} }
float manhattanDistance(const GridPos& leftHandSide, const GridPos& rightHandSide) float manhattanDistance(const GridPos& leftHandSide, const GridPos& rightHandSide)

View file

@ -20,6 +20,8 @@ using GridUnit = int16_t;
using GridPos = std::pair<GridUnit,GridUnit>; using GridPos = std::pair<GridUnit,GridUnit>;
static constexpr uint8_t pixelsPerUnit = 30; static constexpr uint8_t pixelsPerUnit = 30;
static constexpr unsigned int maxRules = 5;
static constexpr char levelFolder[] = "resources/"; static constexpr char levelFolder[] = "resources/";
static constexpr char textureFolder[] = "resources/"; static constexpr char textureFolder[] = "resources/";
static constexpr char fontFolder[] = "resources/"; static constexpr char fontFolder[] = "resources/";

View file

@ -11,8 +11,4 @@ target_include_directories(gTests PRIVATE ${PROJECT_SOURCE_DIR}/../src)
target_link_libraries(gTests target_link_libraries(gTests
engine engine
gtest gtest
pthread pthread)
sfml-window
sfml-graphics
sfml-system
pugixml)