diff --git a/CMakeLists.txt b/CMakeLists.txt index ad8b0b6..7afa9e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,7 @@ set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake_modules" ${CMAKE_MODULE_PATH}) add_subdirectory(src) add_subdirectory(tests) +add_subdirectory(game) # Detect and add SFML 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") 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 find_package(GTest REQUIRED) if(GTest_FOUND) diff --git a/README.md b/README.md index ede598f..43d10dd 100644 --- a/README.md +++ b/README.md @@ -33,18 +33,19 @@ The class diagram omits most constructors and getters. - [ ] Rules - [x] Creation - [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 - [ ] Other rules (Waiting...) - [ ] Graphics - [x] Scene rendering + - [x] Some kind of UI - [ ] UI - [ ] Menu ? - [ ] Gameloop - - [ ] Transition from starting to running state and vice-versa - - [ ] Entity behaviour evolution + - [x] Transition from starting to running state and vice-versa + - [x] Entity behaviour evolution - [x] Rule application - [ ] Win condition - [ ] ~~Saving state through serialization ?~~ - - [ ] UML + - [x] UML - [ ] Unit tests \ No newline at end of file diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt new file mode 100644 index 0000000..722fbf3 --- /dev/null +++ b/game/CMakeLists.txt @@ -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) \ No newline at end of file diff --git a/game/main.cpp b/game/main.cpp new file mode 100644 index 0000000..0a613e4 --- /dev/null +++ b/game/main.cpp @@ -0,0 +1,15 @@ +// +// Created by trotfunky on 11/06/19. +// + +#include "Game.h" + +int main() +{ + std::vector textures = {"Head_Boy.png","Head_Significant_Boy.png","Building.png"}; + std::vector levels = {"test_level.xml"}; + + Game game(levels,textures); + game.loadLevel(0); + game.runGame(); +} \ No newline at end of file diff --git a/resources/level1.xml b/resources/level1.xml new file mode 100644 index 0000000..8612c46 --- /dev/null +++ b/resources/level1.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/resources/level2.xml b/resources/level2.xml new file mode 100644 index 0000000..c5da26b --- /dev/null +++ b/resources/level2.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/test_level.xml b/resources/test_level.xml index 2a7f192..5e8b0e8 100644 --- a/resources/test_level.xml +++ b/resources/test_level.xml @@ -1,7 +1,7 @@ - - - + + + \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 322c065..f82b03c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -10,7 +10,8 @@ target_link_libraries(engine sfml-window sfml-graphics sfml-system - pugixml) + pugixml + tgui) if (CMAKE_COMPILER_IS_GNUCXX) target_compile_options(engine PRIVATE -Wall -Wpedantic -Wextra) diff --git a/src/Entity.cpp b/src/Entity.cpp index 11cc6c6..b7a35f3 100644 --- a/src/Entity.cpp +++ b/src/Entity.cpp @@ -6,10 +6,14 @@ const std::map Entity::entityTypeLookup = { {"Citizen",EntityType::Citizen}, - {"Significant",EntityType::Significant}, + {"Noble",EntityType::Noble}, {"House",EntityType::House}, {"Car",EntityType::Car}}; +const std::map 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) { shape = sf::RectangleShape(sf::Vector2f(width*pro_maat::pixelsPerUnit,height*pro_maat::pixelsPerUnit)); diff --git a/src/Entity.h b/src/Entity.h index 3765a09..d709673 100644 --- a/src/Entity.h +++ b/src/Entity.h @@ -15,7 +15,7 @@ enum class EntityType { Citizen, - Significant, + Noble, House, Car, }; @@ -62,14 +62,15 @@ public: virtual const std::vector getOccupiedSquares() const; + static const std::map entityTypeLookup; + static const std::map stateLookup; + protected: /// Empty constructor for derived class instanciation Entity(); private: - static const std::map entityTypeLookup; - EntityType type; // As it contains position, size and orientation, we do not need anything more diff --git a/src/Game.cpp b/src/Game.cpp index b1c7df0..0d39902 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -8,22 +8,25 @@ Game::Game(std::vector& levels, std::vector& textures) : levelFiles(std::move(levels)), - textureFiles(std::move(textures)) + textureFiles(std::move(textures)), running(false), ruleCount(0) { 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_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) { pro_maat::errorWindow(result.description()); } - currentLevel = std::make_unique(document,textures); + currentLevel = std::make_unique(document,textures,levelID); } void Game::loadTextures() @@ -42,6 +45,9 @@ void Game::runGame() sf::Clock clock; + tgui::Gui gui(window); + addWidgets(gui); + while (window.isOpen()) { sf::Event event; @@ -51,8 +57,10 @@ void Game::runGame() { window.close(); } + + gui.handleEvent(event); } - if (clock.getElapsedTime().asMilliseconds() >= 200) + if (running && clock.getElapsedTime().asMilliseconds() >= 200) { currentLevel->runStep(); clock.restart(); @@ -60,11 +68,135 @@ void Game::runGame() window.clear(sf::Color::White); // TODO : Consider drawing in a sf::RenderTexture to allow positioning the level at a fixed position ? currentLevel->render(window); + gui.draw(); 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 { 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("AffectedType0"); + if(!affectedTypeCombo) return; + auto targetTypeCombo = gui.get("TargetType0"); + if(!targetTypeCombo) return; + auto actionCombo = gui.get("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("add")->setSize("0%","0%"); + gui.get("add")->setTextSize(0); + gui.get("add")->setPosition("-10%","-10%"); + + ruleCount++; +} diff --git a/src/Game.h b/src/Game.h index f2ec76a..20b1732 100644 --- a/src/Game.h +++ b/src/Game.h @@ -6,21 +6,22 @@ #define SRC_GAME_H #include +#include #include #include +#include #include "Level.h" #include "Entity.h" #include "Utils.h" - class Game { public: Game(std::vector& levels, std::vector& textures); /// Loads the level of corresponding ID from Game::levelFiles /// Closes the program if there is a fatal error (Missing file, bad file...) - void loadLevel(int levelId); + void loadLevel(int levelID); std::unique_ptr currentLevel; @@ -37,7 +38,18 @@ private: /// Stores pointers to textures for future use pro_maat::TextureStore textures; + bool running; + unsigned int ruleCount; + void loadTextures(); + void addWidgets(tgui::Gui& gui); + + // + // Widget callbacks + // + + void setRunning(bool newState,tgui::Gui& gui); + void addRule(tgui::Gui& gui); }; diff --git a/src/Level.cpp b/src/Level.cpp index 046ac13..0d7aec7 100644 --- a/src/Level.cpp +++ b/src/Level.cpp @@ -5,8 +5,8 @@ #include "Level.h" -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()), +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"); @@ -23,8 +23,8 @@ Level::Level(const pugi::xml_document& xmlDoc, const pro_maat::TextureStore& tex } // FIXME : For testing purposes - addRule(EntityType::Significant,State::Fleeing,EntityType::Citizen); - addRule(EntityType::Citizen,State::Moving,EntityType::Significant); +// addRule(EntityType::Noble,State::Moving,EntityType::House); +// addRule(EntityType::Citizen,State::Moving,EntityType::Citizen); } void Level::addRule(EntityType affectedEntities, const State targetState, EntityType targetEntities) @@ -128,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 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) @@ -198,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 return Orientation::None; } + +int Level::getLevelID() +{ + return levelID; +} \ No newline at end of file diff --git a/src/Level.h b/src/Level.h index a19aede..b0c8d0f 100644 --- a/src/Level.h +++ b/src/Level.h @@ -20,7 +20,7 @@ class Level { 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 /// @@ -32,7 +32,11 @@ public: void render(sf::RenderWindow& renderWindow) const; void runStep(); + int getLevelID(); + private: + int levelID; + const pro_maat::GridPos size; std::vector> entities; diff --git a/src/Utils.h b/src/Utils.h index af3dfe3..7ecbfcb 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -20,6 +20,8 @@ using GridUnit = int16_t; using GridPos = std::pair; static constexpr uint8_t pixelsPerUnit = 30; +static constexpr unsigned int maxRules = 5; + static constexpr char levelFolder[] = "resources/"; static constexpr char textureFolder[] = "resources/"; static constexpr char fontFolder[] = "resources/"; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index bdde3da..75179ba 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -11,8 +11,4 @@ target_include_directories(gTests PRIVATE ${PROJECT_SOURCE_DIR}/../src) target_link_libraries(gTests engine gtest - pthread - sfml-window - sfml-graphics - sfml-system - pugixml) \ No newline at end of file + pthread) \ No newline at end of file