From 384c648e5432d4c85fa6062d3399f7f458717386 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Sun, 10 Oct 2021 16:10:24 +0100 Subject: [PATCH 01/20] starting work on A* search using griddly actions as edge nodes --- src/Griddly/Core/AStarPathFinder.cpp | 23 ++++++++++++++++++++++ src/Griddly/Core/AStarPathFinder.hpp | 29 ++++++++++++++++++++++++++++ src/Griddly/Core/AStarPathNode.hpp | 22 +++++++++++++++++++++ src/Griddly/Core/PathFinder.cpp | 10 ++++++++++ src/Griddly/Core/PathFinder.hpp | 26 +++++++++++++++++++++++++ src/Griddly/Core/Util/util.hpp | 2 +- 6 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 src/Griddly/Core/AStarPathFinder.cpp create mode 100644 src/Griddly/Core/AStarPathFinder.hpp create mode 100644 src/Griddly/Core/AStarPathNode.hpp create mode 100644 src/Griddly/Core/PathFinder.cpp create mode 100644 src/Griddly/Core/PathFinder.hpp diff --git a/src/Griddly/Core/AStarPathFinder.cpp b/src/Griddly/Core/AStarPathFinder.cpp new file mode 100644 index 000000000..1b85cfef2 --- /dev/null +++ b/src/Griddly/Core/AStarPathFinder.cpp @@ -0,0 +1,23 @@ +#pragma once + +#include "AStarPathFinder.hpp" + +#include + +#include "Grid.hpp" + +namespace griddly { + +AStarPathFinder::AStarPathFinder(std::shared_ptr grid, std::unordered_set passableObjects, std::unordered_set impassableObjects, std::string targetAction, ActionInputsDefinition actionInputs) + : PathFinder(grid, passableObjects, impassableObjects), targetAction_(targetAction), actionInputs_(actionInputs) { +} + + +SearchOutput search(glm::ivec2 startLocation, glm::ivec2 endLocation, uint32_t maxDepth) { + + VectorPriorityQueue nodes; + +} + + +} // namespace griddly \ No newline at end of file diff --git a/src/Griddly/Core/AStarPathFinder.hpp b/src/Griddly/Core/AStarPathFinder.hpp new file mode 100644 index 000000000..58c9bc4a1 --- /dev/null +++ b/src/Griddly/Core/AStarPathFinder.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "PathFinder.hpp" +#include "Grid.hpp" +#include "Util/util.hpp" +#include "AStarPathNode.hpp" + +namespace griddly { + +struct PathNode { + +} + +class AStarPathFinder : public PathFinder { + public: + AStarPathFinder(std::shared_ptr grid, std::unordered_set passableObjects, std::unordered_set impassableObjects, std::string targetAction, ActionInputsDefinition actionInputs); + + virtual SearchOutput search(glm::ivec2 startLocation, glm::ivec2 endLocation, uint32_t maxDepth) override; + + private: + const std::string targetAction_; + const ActionInputsDefinition actionInputs_; + + +}; + +} // namespace griddly diff --git a/src/Griddly/Core/AStarPathNode.hpp b/src/Griddly/Core/AStarPathNode.hpp new file mode 100644 index 000000000..60df4b2bc --- /dev/null +++ b/src/Griddly/Core/AStarPathNode.hpp @@ -0,0 +1,22 @@ + +namespace griddly { +class AStarPathNode { + public: + AStarPathNode(uint32_t score) + : playerId(playerId), priority(score), action(action) { + } + bool operator==(const DelayedActionQueueItem& other) const { + return priority == other.priority; + } + + bool operator>(const DelayedActionQueueItem& other) const { + return priority < other.priority; + } + + bool operator<(const DelayedActionQueueItem& other) const { + return priority > other.priority; + } + + const uint32_t score_; +}; +} // namespace griddly \ No newline at end of file diff --git a/src/Griddly/Core/PathFinder.cpp b/src/Griddly/Core/PathFinder.cpp new file mode 100644 index 000000000..54da053e0 --- /dev/null +++ b/src/Griddly/Core/PathFinder.cpp @@ -0,0 +1,10 @@ +#include + +namespace griddly { + +PathFinder::PathFinder(std::shared_ptr grid, std::unordered_set passableObjects, std::unordered_set impassableObjects) : + grid_(grid), passableObjects_(passableObjects), impassableObjects_(impassableObjects) +{ +} + +} // namespace griddly \ No newline at end of file diff --git a/src/Griddly/Core/PathFinder.hpp b/src/Griddly/Core/PathFinder.hpp new file mode 100644 index 000000000..10ac7e0f3 --- /dev/null +++ b/src/Griddly/Core/PathFinder.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include +#include + +namespace griddly { + +struct SearchOutput { + glm::ivec2 direction; +}; + +class PathFinder { + public: + PathFinder(std::shared_ptr grid, std::unordered_set passableObjects, std::unordered_set impassableObjects); + + virtual SearchOutput search(glm::ivec2 startLocation, glm::ivec2 endLocation, uint32_t maxDepth) = 0; + + protected: + const std::shared_ptr grid_; + std::unordered_set passableObjects_; + std::unordered_set impassableObjects_; +}; + +} // namespace griddly \ No newline at end of file diff --git a/src/Griddly/Core/Util/util.hpp b/src/Griddly/Core/Util/util.hpp index f68e4620a..43cf46c89 100644 --- a/src/Griddly/Core/Util/util.hpp +++ b/src/Griddly/Core/Util/util.hpp @@ -25,7 +25,7 @@ inline std::vector split(const std::string& s, char delim) { } template, class P = std::less > -struct VectorPriorityQueue :std::priority_queue { +struct VectorPriorityQueue : std::priority_queue { using std::priority_queue::priority_queue; typename C::iterator begin() { return std::priority_queue::c.begin(); } typename C::iterator end() { return std::priority_queue::c.end(); } From f3a1513f8fe0dd2964489768b749b4968950f3a8 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Sat, 16 Oct 2021 09:05:31 +0100 Subject: [PATCH 02/20] wip --- src/Griddly/Core/AStarPathNode.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Griddly/Core/AStarPathNode.hpp b/src/Griddly/Core/AStarPathNode.hpp index 60df4b2bc..b49fe26d5 100644 --- a/src/Griddly/Core/AStarPathNode.hpp +++ b/src/Griddly/Core/AStarPathNode.hpp @@ -1,4 +1,5 @@ + namespace griddly { class AStarPathNode { public: From 989e287647964e9cf8ff630e73eede6d0f81f66c Mon Sep 17 00:00:00 2001 From: Bam4d Date: Mon, 25 Oct 2021 18:02:10 +0100 Subject: [PATCH 03/20] added core of code for A* pathfinding --- src/Griddly/Core/AStarPathFinder.cpp | 74 +++++++++++++++++-- src/Griddly/Core/AStarPathFinder.hpp | 8 +- src/Griddly/Core/AStarPathNode.hpp | 29 +++++--- src/Griddly/Core/PathFinder.cpp | 6 +- src/Griddly/Core/PathFinder.hpp | 7 +- .../src/Griddly/Core/AStarPathFinderTest.cpp | 48 ++++++++++++ 6 files changed, 144 insertions(+), 28 deletions(-) create mode 100644 tests/src/Griddly/Core/AStarPathFinderTest.cpp diff --git a/src/Griddly/Core/AStarPathFinder.cpp b/src/Griddly/Core/AStarPathFinder.cpp index 1b85cfef2..003173f27 100644 --- a/src/Griddly/Core/AStarPathFinder.cpp +++ b/src/Griddly/Core/AStarPathFinder.cpp @@ -1,6 +1,6 @@ -#pragma once - #include "AStarPathFinder.hpp" +#include +#include #include @@ -8,14 +8,76 @@ namespace griddly { -AStarPathFinder::AStarPathFinder(std::shared_ptr grid, std::unordered_set passableObjects, std::unordered_set impassableObjects, std::string targetAction, ActionInputsDefinition actionInputs) - : PathFinder(grid, passableObjects, impassableObjects), targetAction_(targetAction), actionInputs_(actionInputs) { +AStarPathFinder::AStarPathFinder(std::shared_ptr grid, std::unordered_set impassableObjects, ActionInputsDefinition actionInputs) + : PathFinder(grid, impassableObjects), actionInputs_(actionInputs) { +} + +SearchOutput AStarPathFinder::reconstructPath(std::shared_ptr currentBestNode) { + if(currentBestNode->parent == nullptr) { + return {currentBestNode->actionId}; + } + return {0}; } +SearchOutput AStarPathFinder::search(glm::ivec2 startLocation, glm::ivec2 endLocation, uint32_t maxDepth) { + + VectorPriorityQueue> searchedNodes; + + auto startNode = std::make_shared(AStarPathNode(startLocation)); + + searchedNodes.push(startNode); + + std::unordered_map scoreToGoal; + + while(!searchedNodes.empty()) { + + auto currentBestNode = searchedNodes.top(); + + searchedNodes.pop(); + + if(currentBestNode->location == endLocation) { + return reconstructPath(currentBestNode); + } + + for(auto& inputMapping : actionInputs_.inputMappings) { + auto actionId = inputMapping.first; + auto mapping = inputMapping.second; + + // TODO: calculate this with respect to action/object orientation + auto vectorToDest = mapping.vectorToDest; + auto nextLocation = currentBestNode->location + vectorToDest; + + // If this location is passable + auto objectsAtNextLocation = grid_->getObjectsAt(nextLocation); + bool passable = true; + for (auto object : objectsAtNextLocation) { + if(impassableObjects_.find(object.second->getObjectName()) != impassableObjects_.end()) { + passable = false; + break; + } + } + + if(passable) { + float neighbourScoreToGoal = UINT_MAX; + + if(scoreToGoal.find(nextLocation) != scoreToGoal.end()) { + neighbourScoreToGoal = scoreToGoal.at(nextLocation); + } + + auto nextScoreToGoal = scoreToGoal[currentBestNode->location] + glm::length((glm::vec2)mapping.vectorToDest); + + if(nextScoreToGoal < neighbourScoreToGoal) { + // We have found a better path + scoreToGoal[nextLocation] = nextScoreToGoal; + auto nodeScore = nextScoreToGoal + glm::distance((glm::vec2)endLocation, (glm::vec2)nextLocation); + searchedNodes.push(std::make_shared(AStarPathNode(nodeScore, actionId, nextLocation, currentBestNode))); + } + } -SearchOutput search(glm::ivec2 startLocation, glm::ivec2 endLocation, uint32_t maxDepth) { + } + } - VectorPriorityQueue nodes; + return SearchOutput(); } diff --git a/src/Griddly/Core/AStarPathFinder.hpp b/src/Griddly/Core/AStarPathFinder.hpp index 58c9bc4a1..1256e198d 100644 --- a/src/Griddly/Core/AStarPathFinder.hpp +++ b/src/Griddly/Core/AStarPathFinder.hpp @@ -9,13 +9,11 @@ namespace griddly { -struct PathNode { - -} - class AStarPathFinder : public PathFinder { public: - AStarPathFinder(std::shared_ptr grid, std::unordered_set passableObjects, std::unordered_set impassableObjects, std::string targetAction, ActionInputsDefinition actionInputs); + AStarPathFinder(std::shared_ptr grid, std::unordered_set impassableObjects, ActionInputsDefinition actionInputs); + + SearchOutput reconstructPath(std::shared_ptr currentBestNode); virtual SearchOutput search(glm::ivec2 startLocation, glm::ivec2 endLocation, uint32_t maxDepth) override; diff --git a/src/Griddly/Core/AStarPathNode.hpp b/src/Griddly/Core/AStarPathNode.hpp index b49fe26d5..1f06c57dc 100644 --- a/src/Griddly/Core/AStarPathNode.hpp +++ b/src/Griddly/Core/AStarPathNode.hpp @@ -1,23 +1,32 @@ - +#include namespace griddly { class AStarPathNode { public: - AStarPathNode(uint32_t score) - : playerId(playerId), priority(score), action(action) { + + AStarPathNode(glm::ivec2 nodeLocation) + : score(UINT_MAX), actionId(0), location(nodeLocation), parent(nullptr) { } - bool operator==(const DelayedActionQueueItem& other) const { - return priority == other.priority; + + AStarPathNode(float nodeScore, uint32_t nodeActionId, glm::ivec2 nodeLocation, std::shared_ptr nodeParent) + : score(nodeScore), actionId(nodeActionId), location(nodeLocation), parent(nodeParent) { + } + + bool operator==(const AStarPathNode& other) const { + return score == other.score; } - bool operator>(const DelayedActionQueueItem& other) const { - return priority < other.priority; + bool operator>(const AStarPathNode& other) const { + return score < other.score; } - bool operator<(const DelayedActionQueueItem& other) const { - return priority > other.priority; + bool operator<(const AStarPathNode& other) const { + return score > other.score; } - const uint32_t score_; + const float score; + const uint32_t actionId; + const glm::ivec2 location; + const std::shared_ptr parent; }; } // namespace griddly \ No newline at end of file diff --git a/src/Griddly/Core/PathFinder.cpp b/src/Griddly/Core/PathFinder.cpp index 54da053e0..e78b87cc2 100644 --- a/src/Griddly/Core/PathFinder.cpp +++ b/src/Griddly/Core/PathFinder.cpp @@ -1,9 +1,9 @@ -#include +#include "PathFinder.hpp" namespace griddly { -PathFinder::PathFinder(std::shared_ptr grid, std::unordered_set passableObjects, std::unordered_set impassableObjects) : - grid_(grid), passableObjects_(passableObjects), impassableObjects_(impassableObjects) +PathFinder::PathFinder(std::shared_ptr grid, std::unordered_set impassableObjects) : + grid_(grid), impassableObjects_(impassableObjects) { } diff --git a/src/Griddly/Core/PathFinder.hpp b/src/Griddly/Core/PathFinder.hpp index 10ac7e0f3..01a737343 100644 --- a/src/Griddly/Core/PathFinder.hpp +++ b/src/Griddly/Core/PathFinder.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include "Grid.hpp" #include #include #include @@ -8,18 +8,17 @@ namespace griddly { struct SearchOutput { - glm::ivec2 direction; + uint32_t actionId = 0; }; class PathFinder { public: - PathFinder(std::shared_ptr grid, std::unordered_set passableObjects, std::unordered_set impassableObjects); + PathFinder(std::shared_ptr grid, std::unordered_set impassableObjects); virtual SearchOutput search(glm::ivec2 startLocation, glm::ivec2 endLocation, uint32_t maxDepth) = 0; protected: const std::shared_ptr grid_; - std::unordered_set passableObjects_; std::unordered_set impassableObjects_; }; diff --git a/tests/src/Griddly/Core/AStarPathFinderTest.cpp b/tests/src/Griddly/Core/AStarPathFinderTest.cpp new file mode 100644 index 000000000..7e50ff714 --- /dev/null +++ b/tests/src/Griddly/Core/AStarPathFinderTest.cpp @@ -0,0 +1,48 @@ +#include + +#include "Griddly/Core/AStarPathFinder.cpp" +#include "Mocks/Griddly/Core/GDY/Objects/MockObject.hpp" +#include "Mocks/Griddly/Core/MockGrid.hpp" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using ::testing::Return; +using ::testing::ReturnRef; + +namespace griddly { + +ActionInputsDefinition getUpDownLeftRightActions() { + ActionInputsDefinition definition; + definition.inputMappings = { + {1, {{0, 1}}}, {2, {{1, 0}}}, {3, {{0, -1}}}, {4, {{-1, 0}}}}; + definition.relative = false; + definition.internal = false; + definition.mapToGrid = false; + + return definition; +} + +TEST(AStarPathFinderTest, searchAllPassable) { + auto mockObjectPtr = std::shared_ptr(new MockObject()); + auto mockGridPtr = std::shared_ptr(new MockGrid()); + auto pathFinder = std::shared_ptr(new AStarPathFinder(mockGridPtr, {}, getUpDownLeftRightActions())); + + TileObjects objects = {{0, mockObjectPtr}}; + EXPECT_CALL(*mockGridPtr, getObjectsAt).WillRepeatedly(ReturnRef(objects)); + + auto up = pathFinder->search({0, 0}, {0, 5}, 100); + auto right = pathFinder->search({0, 0}, {5, 0}, 100); + auto down = pathFinder->search({0, 5}, {0, 0}, 100); + auto left = pathFinder->search({5, 0}, {0, 0}, 100); + + ASSERT_EQ(up.actionId, 1); + ASSERT_EQ(right.actionId, 2); + ASSERT_EQ(down.actionId, 3); + ASSERT_EQ(left.actionId, 4); +} + +TEST(AStarPathFinderTest, searchNoPassable) {} + +TEST(AStarPathFinderTest, searchSelfPassable) {} + +} // namespace griddly \ No newline at end of file From 67715968750e291b93d365141f23a3e013ca14c0 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Wed, 27 Oct 2021 08:39:30 +0100 Subject: [PATCH 04/20] trying to make priority queue work --- src/Griddly/Core/AStarPathFinder.cpp | 57 +++++++++++++------ src/Griddly/Core/AStarPathNode.hpp | 24 ++------ .../src/Griddly/Core/AStarPathFinderTest.cpp | 17 +++--- 3 files changed, 54 insertions(+), 44 deletions(-) diff --git a/src/Griddly/Core/AStarPathFinder.cpp b/src/Griddly/Core/AStarPathFinder.cpp index 003173f27..e18e1c8e5 100644 --- a/src/Griddly/Core/AStarPathFinder.cpp +++ b/src/Griddly/Core/AStarPathFinder.cpp @@ -1,7 +1,7 @@ #include "AStarPathFinder.hpp" #include #include - +#include #include #include "Grid.hpp" @@ -15,25 +15,29 @@ AStarPathFinder::AStarPathFinder(std::shared_ptr grid, std::unordered_set< SearchOutput AStarPathFinder::reconstructPath(std::shared_ptr currentBestNode) { if(currentBestNode->parent == nullptr) { return {currentBestNode->actionId}; + } else { + return reconstructPath(currentBestNode->parent); } return {0}; } SearchOutput AStarPathFinder::search(glm::ivec2 startLocation, glm::ivec2 endLocation, uint32_t maxDepth) { - VectorPriorityQueue> searchedNodes; + VectorPriorityQueue> orderedBestNodes; + std::unordered_map> nodes; auto startNode = std::make_shared(AStarPathNode(startLocation)); + startNode->scoreFromStart = glm::distance((glm::vec2)endLocation, (glm::vec2)startLocation); + startNode->scoreToGoal = 0; + orderedBestNodes.push(startNode); - searchedNodes.push(startNode); - - std::unordered_map scoreToGoal; - - while(!searchedNodes.empty()) { + while(!orderedBestNodes.empty()) { - auto currentBestNode = searchedNodes.top(); + auto currentBestNode = orderedBestNodes.top(); - searchedNodes.pop(); + orderedBestNodes.pop(); + + spdlog::debug("Current best node at location: [{0},{1}]. score: {2}", currentBestNode->location.x, currentBestNode->location.y, currentBestNode->scoreFromStart); if(currentBestNode->location == endLocation) { return reconstructPath(currentBestNode); @@ -47,6 +51,10 @@ SearchOutput AStarPathFinder::search(glm::ivec2 startLocation, glm::ivec2 endLoc auto vectorToDest = mapping.vectorToDest; auto nextLocation = currentBestNode->location + vectorToDest; + if(nextLocation.y < 0 || nextLocation.y >= grid_->getHeight() || nextLocation.x < 0 || nextLocation.x >= grid_->getWidth()) { + continue; + } + // If this location is passable auto objectsAtNextLocation = grid_->getObjectsAt(nextLocation); bool passable = true; @@ -58,19 +66,32 @@ SearchOutput AStarPathFinder::search(glm::ivec2 startLocation, glm::ivec2 endLoc } if(passable) { - float neighbourScoreToGoal = UINT_MAX; + std::shared_ptr neighbourNode; - if(scoreToGoal.find(nextLocation) != scoreToGoal.end()) { - neighbourScoreToGoal = scoreToGoal.at(nextLocation); - } + if(nodes.find(nextLocation) != nodes.end()) { + neighbourNode = nodes.at(nextLocation); + } else { + neighbourNode = std::make_shared(AStarPathNode(nextLocation)); + nodes[nextLocation] = neighbourNode; + } - auto nextScoreToGoal = scoreToGoal[currentBestNode->location] + glm::length((glm::vec2)mapping.vectorToDest); + auto nextScoreToGoal = currentBestNode->scoreToGoal + glm::length((glm::vec2)mapping.vectorToDest); - if(nextScoreToGoal < neighbourScoreToGoal) { + if(nextScoreToGoal < neighbourNode->scoreToGoal) { // We have found a better path - scoreToGoal[nextLocation] = nextScoreToGoal; - auto nodeScore = nextScoreToGoal + glm::distance((glm::vec2)endLocation, (glm::vec2)nextLocation); - searchedNodes.push(std::make_shared(AStarPathNode(nodeScore, actionId, nextLocation, currentBestNode))); + + + // Set the action from the current best node to this node, and the parent of the neighbour to this node + currentBestNode->actionId = actionId; + neighbourNode->parent = currentBestNode; + + // Calculate the scores + neighbourNode->scoreToGoal = nextScoreToGoal; + neighbourNode->scoreFromStart = nextScoreToGoal + glm::distance((glm::vec2)endLocation, (glm::vec2)nextLocation); + + spdlog::debug("New scores for location: [{0},{1}], scoreToGoal: {2}, scoreFromStart: {3}", nextLocation.x, nextLocation.y, neighbourNode->scoreToGoal, neighbourNode->scoreFromStart); + orderedBestNodes.push(neighbourNode); + } } diff --git a/src/Griddly/Core/AStarPathNode.hpp b/src/Griddly/Core/AStarPathNode.hpp index 1f06c57dc..03b698f43 100644 --- a/src/Griddly/Core/AStarPathNode.hpp +++ b/src/Griddly/Core/AStarPathNode.hpp @@ -5,28 +5,14 @@ class AStarPathNode { public: AStarPathNode(glm::ivec2 nodeLocation) - : score(UINT_MAX), actionId(0), location(nodeLocation), parent(nullptr) { + : location(nodeLocation) { } - AStarPathNode(float nodeScore, uint32_t nodeActionId, glm::ivec2 nodeLocation, std::shared_ptr nodeParent) - : score(nodeScore), actionId(nodeActionId), location(nodeLocation), parent(nodeParent) { - } - - bool operator==(const AStarPathNode& other) const { - return score == other.score; - } - - bool operator>(const AStarPathNode& other) const { - return score < other.score; - } - - bool operator<(const AStarPathNode& other) const { - return score > other.score; - } + float scoreFromStart = UINT_MAX; + float scoreToGoal = UINT_MAX; + uint32_t actionId = 0; + std::shared_ptr parent; - const float score; - const uint32_t actionId; const glm::ivec2 location; - const std::shared_ptr parent; }; } // namespace griddly \ No newline at end of file diff --git a/tests/src/Griddly/Core/AStarPathFinderTest.cpp b/tests/src/Griddly/Core/AStarPathFinderTest.cpp index 7e50ff714..46467eb72 100644 --- a/tests/src/Griddly/Core/AStarPathFinderTest.cpp +++ b/tests/src/Griddly/Core/AStarPathFinderTest.cpp @@ -27,18 +27,21 @@ TEST(AStarPathFinderTest, searchAllPassable) { auto mockGridPtr = std::shared_ptr(new MockGrid()); auto pathFinder = std::shared_ptr(new AStarPathFinder(mockGridPtr, {}, getUpDownLeftRightActions())); - TileObjects objects = {{0, mockObjectPtr}}; + TileObjects objects = {}; EXPECT_CALL(*mockGridPtr, getObjectsAt).WillRepeatedly(ReturnRef(objects)); - auto up = pathFinder->search({0, 0}, {0, 5}, 100); + EXPECT_CALL(*mockGridPtr, getHeight).WillRepeatedly(Return(6)); + EXPECT_CALL(*mockGridPtr, getWidth).WillRepeatedly(Return(6)); + + // auto up = pathFinder->search({0, 0}, {0, 5}, 100); auto right = pathFinder->search({0, 0}, {5, 0}, 100); - auto down = pathFinder->search({0, 5}, {0, 0}, 100); - auto left = pathFinder->search({5, 0}, {0, 0}, 100); + // auto down = pathFinder->search({0, 5}, {0, 0}, 100); + // auto left = pathFinder->search({5, 0}, {0, 0}, 100); - ASSERT_EQ(up.actionId, 1); + // ASSERT_EQ(up.actionId, 1); ASSERT_EQ(right.actionId, 2); - ASSERT_EQ(down.actionId, 3); - ASSERT_EQ(left.actionId, 4); + // ASSERT_EQ(down.actionId, 3); + // ASSERT_EQ(left.actionId, 4); } TEST(AStarPathFinderTest, searchNoPassable) {} From c4daf061684099369bd571dfceaf4593c8962255 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Wed, 27 Oct 2021 13:36:26 +0100 Subject: [PATCH 05/20] working tests and algos, need to add python tests --- src/Griddly/Core/AStarPathFinder.cpp | 37 +++--- src/Griddly/Core/AStarPathFinder.hpp | 10 +- src/Griddly/Core/AStarPathNode.hpp | 13 +- src/Griddly/Core/PathFinder.hpp | 2 +- .../src/Griddly/Core/AStarPathFinderTest.cpp | 120 ++++++++++++++++-- 5 files changed, 147 insertions(+), 35 deletions(-) diff --git a/src/Griddly/Core/AStarPathFinder.cpp b/src/Griddly/Core/AStarPathFinder.cpp index e18e1c8e5..2df4d5f0e 100644 --- a/src/Griddly/Core/AStarPathFinder.cpp +++ b/src/Griddly/Core/AStarPathFinder.cpp @@ -1,5 +1,4 @@ #include "AStarPathFinder.hpp" -#include #include #include #include @@ -13,20 +12,21 @@ AStarPathFinder::AStarPathFinder(std::shared_ptr grid, std::unordered_set< } SearchOutput AStarPathFinder::reconstructPath(std::shared_ptr currentBestNode) { - if(currentBestNode->parent == nullptr) { + if(currentBestNode->parent->parent == nullptr) { return {currentBestNode->actionId}; } else { + spdlog::debug("Reconstructing path: [{0},{1}]->[{2},{3}] actionId: {4}", currentBestNode->parent->location.x, currentBestNode->parent->location.y, currentBestNode->location.x, currentBestNode->location.y, currentBestNode->parent->actionId); return reconstructPath(currentBestNode->parent); } return {0}; } -SearchOutput AStarPathFinder::search(glm::ivec2 startLocation, glm::ivec2 endLocation, uint32_t maxDepth) { +SearchOutput AStarPathFinder::search(glm::ivec2 startLocation, glm::ivec2 endLocation, glm::ivec2 startOrientationVector, uint32_t maxDepth) { - VectorPriorityQueue> orderedBestNodes; - std::unordered_map> nodes; + std::priority_queue, std::vector>, SortAStarPathNodes> orderedBestNodes; + std::unordered_map> nodes; - auto startNode = std::make_shared(AStarPathNode(startLocation)); + auto startNode = std::make_shared(AStarPathNode(startLocation, startOrientationVector)); startNode->scoreFromStart = glm::distance((glm::vec2)endLocation, (glm::vec2)startLocation); startNode->scoreToGoal = 0; orderedBestNodes.push(startNode); @@ -37,19 +37,21 @@ SearchOutput AStarPathFinder::search(glm::ivec2 startLocation, glm::ivec2 endLoc orderedBestNodes.pop(); - spdlog::debug("Current best node at location: [{0},{1}]. score: {2}", currentBestNode->location.x, currentBestNode->location.y, currentBestNode->scoreFromStart); + spdlog::debug("Current best node at location: [{0},{1}]. score: {2}, action: {3}", currentBestNode->location.x, currentBestNode->location.y, currentBestNode->scoreFromStart, currentBestNode->actionId); if(currentBestNode->location == endLocation) { return reconstructPath(currentBestNode); } + auto rotationMatrix = DiscreteOrientation(currentBestNode->orientationVector).getRotationMatrix(); + for(auto& inputMapping : actionInputs_.inputMappings) { auto actionId = inputMapping.first; auto mapping = inputMapping.second; - // TODO: calculate this with respect to action/object orientation - auto vectorToDest = mapping.vectorToDest; + auto vectorToDest = actionInputs_.relative ? mapping.vectorToDest * rotationMatrix: mapping.vectorToDest; auto nextLocation = currentBestNode->location + vectorToDest; + auto nextOrientation = actionInputs_.relative ? mapping.orientationVector * rotationMatrix: mapping.orientationVector; if(nextLocation.y < 0 || nextLocation.y >= grid_->getHeight() || nextLocation.x < 0 || nextLocation.x >= grid_->getWidth()) { continue; @@ -68,28 +70,29 @@ SearchOutput AStarPathFinder::search(glm::ivec2 startLocation, glm::ivec2 endLoc if(passable) { std::shared_ptr neighbourNode; - if(nodes.find(nextLocation) != nodes.end()) { - neighbourNode = nodes.at(nextLocation); + auto nodeKey = glm::ivec4(nextLocation, nextOrientation); + + if(nodes.find(nodeKey) != nodes.end()) { + neighbourNode = nodes.at(nodeKey); } else { - neighbourNode = std::make_shared(AStarPathNode(nextLocation)); - nodes[nextLocation] = neighbourNode; + neighbourNode = std::make_shared(AStarPathNode(nextLocation, nextOrientation)); + nodes[nodeKey] = neighbourNode; } auto nextScoreToGoal = currentBestNode->scoreToGoal + glm::length((glm::vec2)mapping.vectorToDest); if(nextScoreToGoal < neighbourNode->scoreToGoal) { // We have found a better path - - // Set the action from the current best node to this node, and the parent of the neighbour to this node - currentBestNode->actionId = actionId; + // Set the action from the current best node to this node + neighbourNode->actionId = actionId; neighbourNode->parent = currentBestNode; // Calculate the scores neighbourNode->scoreToGoal = nextScoreToGoal; neighbourNode->scoreFromStart = nextScoreToGoal + glm::distance((glm::vec2)endLocation, (glm::vec2)nextLocation); - spdlog::debug("New scores for location: [{0},{1}], scoreToGoal: {2}, scoreFromStart: {3}", nextLocation.x, nextLocation.y, neighbourNode->scoreToGoal, neighbourNode->scoreFromStart); + spdlog::debug("New scores for location: [{0},{1}], scoreToGoal: {2}, scoreFromStart: {3}, action: {4}", nextLocation.x, nextLocation.y, neighbourNode->scoreToGoal, neighbourNode->scoreFromStart, actionId); orderedBestNodes.push(neighbourNode); } diff --git a/src/Griddly/Core/AStarPathFinder.hpp b/src/Griddly/Core/AStarPathFinder.hpp index 1256e198d..e130ef94f 100644 --- a/src/Griddly/Core/AStarPathFinder.hpp +++ b/src/Griddly/Core/AStarPathFinder.hpp @@ -9,19 +9,25 @@ namespace griddly { +struct SortAStarPathNodes { + bool operator()(std::shared_ptr a, std::shared_ptr b){ + return a->scoreFromStart > b->scoreFromStart; + }; +}; + + class AStarPathFinder : public PathFinder { public: AStarPathFinder(std::shared_ptr grid, std::unordered_set impassableObjects, ActionInputsDefinition actionInputs); SearchOutput reconstructPath(std::shared_ptr currentBestNode); - virtual SearchOutput search(glm::ivec2 startLocation, glm::ivec2 endLocation, uint32_t maxDepth) override; + virtual SearchOutput search(glm::ivec2 startLocation, glm::ivec2 endLocation, glm::ivec2 startOrientationVector, uint32_t maxDepth) override; private: const std::string targetAction_; const ActionInputsDefinition actionInputs_; - }; } // namespace griddly diff --git a/src/Griddly/Core/AStarPathNode.hpp b/src/Griddly/Core/AStarPathNode.hpp index 03b698f43..e6758471a 100644 --- a/src/Griddly/Core/AStarPathNode.hpp +++ b/src/Griddly/Core/AStarPathNode.hpp @@ -1,18 +1,23 @@ +#pragma once + #include +#include +#include namespace griddly { class AStarPathNode { public: - AStarPathNode(glm::ivec2 nodeLocation) - : location(nodeLocation) { + AStarPathNode(glm::ivec2 nodeLocation, glm::ivec2 nodeOrientationVector) + : location(nodeLocation), orientationVector(nodeOrientationVector) { } - float scoreFromStart = UINT_MAX; - float scoreToGoal = UINT_MAX; + float scoreFromStart = std::numeric_limits::max(); + float scoreToGoal = std::numeric_limits::max(); uint32_t actionId = 0; std::shared_ptr parent; const glm::ivec2 location; + const glm::ivec2 orientationVector; }; } // namespace griddly \ No newline at end of file diff --git a/src/Griddly/Core/PathFinder.hpp b/src/Griddly/Core/PathFinder.hpp index 01a737343..aadc5315d 100644 --- a/src/Griddly/Core/PathFinder.hpp +++ b/src/Griddly/Core/PathFinder.hpp @@ -15,7 +15,7 @@ class PathFinder { public: PathFinder(std::shared_ptr grid, std::unordered_set impassableObjects); - virtual SearchOutput search(glm::ivec2 startLocation, glm::ivec2 endLocation, uint32_t maxDepth) = 0; + virtual SearchOutput search(glm::ivec2 startLocation, glm::ivec2 endLocation, glm::ivec2 startOrientationVector, uint32_t maxDepth) = 0; protected: const std::shared_ptr grid_; diff --git a/tests/src/Griddly/Core/AStarPathFinderTest.cpp b/tests/src/Griddly/Core/AStarPathFinderTest.cpp index 46467eb72..816730dbe 100644 --- a/tests/src/Griddly/Core/AStarPathFinderTest.cpp +++ b/tests/src/Griddly/Core/AStarPathFinderTest.cpp @@ -22,10 +22,90 @@ ActionInputsDefinition getUpDownLeftRightActions() { return definition; } +ActionInputsDefinition getRotateAndForwardActions() { + // InputMapping: + // Inputs: + // 1: + // Description: Rotate left + // OrientationVector: [-1, 0] + // 2: + // Description: Move forwards + // OrientationVector: [0, -1] + // VectorToDest: [0, -1] + // 3: + // Description: Rotate right + // OrientationVector: [1, 0] + // Relative: true + + ActionInputsDefinition definition; + definition.inputMappings = {// Rotate left + {1, {{0, 0}, {-1, 0}}}, + + // Go forward + {2, {{0, -1}, {0, -1}}}, + + // Rotate right + {3, {{0, 0}, {1, 0}}}}; + definition.relative = true; + definition.internal = false; + definition.mapToGrid = false; + + return definition; +} + TEST(AStarPathFinderTest, searchAllPassable) { + auto mockGridPtr = std::shared_ptr(new MockGrid()); + auto pathFinder = std::shared_ptr( + new AStarPathFinder(mockGridPtr, {}, getUpDownLeftRightActions())); + + TileObjects objects = {}; + EXPECT_CALL(*mockGridPtr, getObjectsAt).WillRepeatedly(ReturnRef(objects)); + + EXPECT_CALL(*mockGridPtr, getHeight).WillRepeatedly(Return(6)); + EXPECT_CALL(*mockGridPtr, getWidth).WillRepeatedly(Return(6)); + + auto up = pathFinder->search({0, 0}, {0, 5}, {0, 0}, 100); + auto right = pathFinder->search({0, 0}, {5, 0}, {0, 0}, 100); + auto down = pathFinder->search({0, 5}, {0, 0}, {0, 0}, 100); + auto left = pathFinder->search({5, 0}, {0, 0}, {0, 0}, 100); + + ASSERT_EQ(up.actionId, 1); + ASSERT_EQ(right.actionId, 2); + ASSERT_EQ(down.actionId, 3); + ASSERT_EQ(left.actionId, 4); +} + +TEST(AStarPathFinderTest, searchNoPassable) { auto mockObjectPtr = std::shared_ptr(new MockObject()); auto mockGridPtr = std::shared_ptr(new MockGrid()); - auto pathFinder = std::shared_ptr(new AStarPathFinder(mockGridPtr, {}, getUpDownLeftRightActions())); + + std::string objectName = "impassable_object"; + EXPECT_CALL(*mockObjectPtr, getObjectName).WillRepeatedly(Return(objectName)); + + auto pathFinder = std::shared_ptr(new AStarPathFinder( + mockGridPtr, {objectName}, getUpDownLeftRightActions())); + + TileObjects objects = {{0, mockObjectPtr}}; + EXPECT_CALL(*mockGridPtr, getObjectsAt).WillRepeatedly(ReturnRef(objects)); + + EXPECT_CALL(*mockGridPtr, getHeight).WillRepeatedly(Return(6)); + EXPECT_CALL(*mockGridPtr, getWidth).WillRepeatedly(Return(6)); + + auto up = pathFinder->search({0, 0}, {0, 5}, {0, 0}, 100); + auto right = pathFinder->search({0, 0}, {5, 0}, {0, 0}, 100); + auto down = pathFinder->search({0, 5}, {0, 0}, {0, 0}, 100); + auto left = pathFinder->search({5, 0}, {0, 0}, {0, 0}, 100); + + ASSERT_EQ(up.actionId, 0); + ASSERT_EQ(right.actionId, 0); + ASSERT_EQ(down.actionId, 0); + ASSERT_EQ(left.actionId, 0); +} + +TEST(AStarPathFinderTest, searchRotationActionsFacingGoal) { + auto mockGridPtr = std::shared_ptr(new MockGrid()); + auto pathFinder = std::shared_ptr( + new AStarPathFinder(mockGridPtr, {}, getRotateAndForwardActions())); TileObjects objects = {}; EXPECT_CALL(*mockGridPtr, getObjectsAt).WillRepeatedly(ReturnRef(objects)); @@ -33,19 +113,37 @@ TEST(AStarPathFinderTest, searchAllPassable) { EXPECT_CALL(*mockGridPtr, getHeight).WillRepeatedly(Return(6)); EXPECT_CALL(*mockGridPtr, getWidth).WillRepeatedly(Return(6)); - // auto up = pathFinder->search({0, 0}, {0, 5}, 100); - auto right = pathFinder->search({0, 0}, {5, 0}, 100); - // auto down = pathFinder->search({0, 5}, {0, 0}, 100); - // auto left = pathFinder->search({5, 0}, {0, 0}, 100); + auto up = pathFinder->search({0, 0}, {0, 5}, {0, 1}, 100); + auto right = pathFinder->search({0, 0}, {5, 0}, {1, 0}, 100); + auto down = pathFinder->search({0, 5}, {0, 0}, {0, -1}, 100); + auto left = pathFinder->search({5, 0}, {0, 0}, {-1, 0}, 100); - // ASSERT_EQ(up.actionId, 1); + ASSERT_EQ(up.actionId, 2); ASSERT_EQ(right.actionId, 2); - // ASSERT_EQ(down.actionId, 3); - // ASSERT_EQ(left.actionId, 4); + ASSERT_EQ(down.actionId, 2); + ASSERT_EQ(left.actionId, 2); } -TEST(AStarPathFinderTest, searchNoPassable) {} +TEST(AStarPathFinderTest, searchRotationActions) { + auto mockGridPtr = std::shared_ptr(new MockGrid()); + auto pathFinder = std::shared_ptr( + new AStarPathFinder(mockGridPtr, {}, getRotateAndForwardActions())); + + TileObjects objects = {}; + EXPECT_CALL(*mockGridPtr, getObjectsAt).WillRepeatedly(ReturnRef(objects)); + + EXPECT_CALL(*mockGridPtr, getHeight).WillRepeatedly(Return(6)); + EXPECT_CALL(*mockGridPtr, getWidth).WillRepeatedly(Return(6)); -TEST(AStarPathFinderTest, searchSelfPassable) {} + auto up = pathFinder->search({0, 0}, {0, 5}, {0, 0}, 100); + auto right = pathFinder->search({0, 0}, {5, 0}, {0, 0}, 100); + auto down = pathFinder->search({0, 5}, {0, 0}, {0, 0}, 100); + auto left = pathFinder->search({5, 0}, {0, 0}, {0, 0}, 100); + + ASSERT_EQ(up.actionId, 3); + ASSERT_EQ(right.actionId, 3); + ASSERT_EQ(down.actionId, 2); + ASSERT_EQ(left.actionId, 1); +} -} // namespace griddly \ No newline at end of file +} // namespace griddly \ No newline at end of file From f440877c251a9c4d25b0c305249425461e928d95 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Wed, 27 Oct 2021 17:40:22 +0100 Subject: [PATCH 06/20] have to use collision detector to find nearest object to track for A* --- src/Griddly/Core/AStarPathFinder.cpp | 2 ++ src/Griddly/Core/AStarPathFinder.hpp | 2 ++ src/Griddly/Core/GDY/GDYFactory.cpp | 27 +---------------- src/Griddly/Core/GDY/GDYFactory.hpp | 3 -- src/Griddly/Core/GDY/Objects/Object.cpp | 28 ++++++++++++++++++ src/Griddly/Core/GDY/Objects/Object.hpp | 1 + src/Griddly/Core/GDY/YAMLUtils.hpp | 39 +++++++++++++++++++++++++ src/Griddly/Core/PathFinder.cpp | 1 + src/Griddly/Core/PathFinder.hpp | 3 +- 9 files changed, 76 insertions(+), 30 deletions(-) create mode 100644 src/Griddly/Core/GDY/YAMLUtils.hpp diff --git a/src/Griddly/Core/AStarPathFinder.cpp b/src/Griddly/Core/AStarPathFinder.cpp index 2df4d5f0e..9501709f7 100644 --- a/src/Griddly/Core/AStarPathFinder.cpp +++ b/src/Griddly/Core/AStarPathFinder.cpp @@ -4,6 +4,8 @@ #include #include "Grid.hpp" +#include "GDY/Objects/Object.hpp" +#include "GDY/Actions/Action.hpp" namespace griddly { diff --git a/src/Griddly/Core/AStarPathFinder.hpp b/src/Griddly/Core/AStarPathFinder.hpp index e130ef94f..ae5de43e5 100644 --- a/src/Griddly/Core/AStarPathFinder.hpp +++ b/src/Griddly/Core/AStarPathFinder.hpp @@ -6,9 +6,11 @@ #include "Grid.hpp" #include "Util/util.hpp" #include "AStarPathNode.hpp" +#include "GDY/Actions/Action.hpp" namespace griddly { + struct SortAStarPathNodes { bool operator()(std::shared_ptr a, std::shared_ptr b){ return a->scoreFromStart > b->scoreFromStart; diff --git a/src/Griddly/Core/GDY/GDYFactory.cpp b/src/Griddly/Core/GDY/GDYFactory.cpp index cff8caae2..b36c4e934 100644 --- a/src/Griddly/Core/GDY/GDYFactory.cpp +++ b/src/Griddly/Core/GDY/GDYFactory.cpp @@ -10,6 +10,7 @@ #include "../Grid.hpp" #include "../TurnBasedGameProcess.hpp" #include "GDYFactory.hpp" +#include "YAMLUtils.hpp" #define EMPTY_NODE YAML::Node() @@ -773,32 +774,6 @@ std::shared_ptr GDYFactory::createTerminationHandler(std::sh return terminationGenerator_->newInstance(grid, players); } -std::vector GDYFactory::singleOrListNodeToList(YAML::Node singleOrList) { - std::vector values; - if (singleOrList.IsScalar()) { - values.push_back(singleOrList.as()); - } else if (singleOrList.IsSequence()) { - for (std::size_t s = 0; s < singleOrList.size(); s++) { - values.push_back(singleOrList[s].as()); - } - } - - return values; -} - -BehaviourCommandArguments GDYFactory::singleOrListNodeToCommandArguments(YAML::Node singleOrList) { - BehaviourCommandArguments map; - if (singleOrList.IsScalar()) { - map["0"] = singleOrList; - } else if (singleOrList.IsSequence()) { - for (std::size_t s = 0; s < singleOrList.size(); s++) { - map[std::to_string(s)] = singleOrList[s]; - } - } - - return map; -} - std::unordered_map GDYFactory::defaultActionInputMappings() const { std::unordered_map defaultInputMappings{ {1, InputMapping{{-1, 0}, {-1, 0}, "Left"}}, diff --git a/src/Griddly/Core/GDY/GDYFactory.hpp b/src/Griddly/Core/GDY/GDYFactory.hpp index 028e60c68..7c4f01c5d 100644 --- a/src/Griddly/Core/GDY/GDYFactory.hpp +++ b/src/Griddly/Core/GDY/GDYFactory.hpp @@ -83,9 +83,6 @@ class GDYFactory { YAML::Node commandsNode, YAML::Node preconditionsNode); - std::vector singleOrListNodeToList(YAML::Node singleOrList); - BehaviourCommandArguments singleOrListNodeToCommandArguments(YAML::Node singleOrList); - void parseGlobalVariables(YAML::Node variablesNode); bool parseTerminationConditionV2(TerminationState state, YAML::Node conditionNode); diff --git a/src/Griddly/Core/GDY/Objects/Object.cpp b/src/Griddly/Core/GDY/Objects/Object.cpp index cf2c543d3..f3f2077b7 100644 --- a/src/Griddly/Core/GDY/Objects/Object.cpp +++ b/src/Griddly/Core/GDY/Objects/Object.cpp @@ -6,6 +6,7 @@ #include "../../Util/util.hpp" #include "../Actions/Action.hpp" #include "ObjectGenerator.hpp" +#include "../../AStarPathFinder.hpp" namespace griddly { @@ -442,6 +443,33 @@ BehaviourFunction Object::instantiateBehaviour(std::string commandName, Behaviou }; } + if (commandName == "search") { + auto actionName = commandArguments["Action"].as(); + auto maxSearchDepth = commandArguments["MaxDepth"].as(); + auto targetObjectName = commandArguments["TargetObjectName"].as(); + auto impassableObjectsList = singleOrListNodeToList(commandArguments["impassableObjects"]); + + std::unordered_set impassableObjectsSet(impassableObjectsList.begin(), impassableObjectsList.end()); + auto actionInputDefinitions = objectGenerator_->getActionInputDefinitions(); + auto actionInputDefinitionIt = actionInputDefinitions.find(actionName); + + if(actionInputDefinitionIt == actionInputDefinitions.end()) { + auto errorString = fmt::format("Cannot find action definition for action '{0}', invalid 'search' command.", actionName); + spdlog::error(errorString); + throw std::invalid_argument(errorString); + } + + //grid_->getCollisionDetectors() + + auto pathFinder = std::shared_ptr(new AStarPathFinder(grid_, impassableObjectsSet, actionInputDefinitionIt->second)); + return [this, pathFinder](std::shared_ptr action) -> BehaviourResult { + + //auto searchResult = pathFinder->search(getLocation(), , getObjectOrientation(), maxSearchDepth); + + return {}; + }; + } + throw std::invalid_argument(fmt::format("Unknown or badly defined command {0}.", commandName)); } diff --git a/src/Griddly/Core/GDY/Objects/Object.hpp b/src/Griddly/Core/GDY/Objects/Object.hpp index e27bb50f6..3eab2d67d 100644 --- a/src/Griddly/Core/GDY/Objects/Object.hpp +++ b/src/Griddly/Core/GDY/Objects/Object.hpp @@ -12,6 +12,7 @@ #include "../Actions/Direction.hpp" #include "ObjectVariable.hpp" +#include "../YAMLUtils.hpp" #define BehaviourCommandArguments std::unordered_map #define BehaviourFunction std::function)> diff --git a/src/Griddly/Core/GDY/YAMLUtils.hpp b/src/Griddly/Core/GDY/YAMLUtils.hpp new file mode 100644 index 000000000..2ca072657 --- /dev/null +++ b/src/Griddly/Core/GDY/YAMLUtils.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include + +namespace YAML { +class Node; +} + +#define BehaviourCommandArguments std::unordered_map + +namespace griddly { + +inline std::vector singleOrListNodeToList(YAML::Node singleOrList) { + std::vector values; + if (singleOrList.IsScalar()) { + values.push_back(singleOrList.as()); + } else if (singleOrList.IsSequence()) { + for (std::size_t s = 0; s < singleOrList.size(); s++) { + values.push_back(singleOrList[s].as()); + } + } + + return values; +} + +inline BehaviourCommandArguments singleOrListNodeToCommandArguments(YAML::Node singleOrList) { + BehaviourCommandArguments map; + if (singleOrList.IsScalar()) { + map["0"] = singleOrList; + } else if (singleOrList.IsSequence()) { + for (std::size_t s = 0; s < singleOrList.size(); s++) { + map[std::to_string(s)] = singleOrList[s]; + } + } + + return map; +} + +} \ No newline at end of file diff --git a/src/Griddly/Core/PathFinder.cpp b/src/Griddly/Core/PathFinder.cpp index e78b87cc2..8aa60e092 100644 --- a/src/Griddly/Core/PathFinder.cpp +++ b/src/Griddly/Core/PathFinder.cpp @@ -1,4 +1,5 @@ #include "PathFinder.hpp" +#include "Grid.hpp" namespace griddly { diff --git a/src/Griddly/Core/PathFinder.hpp b/src/Griddly/Core/PathFinder.hpp index aadc5315d..72c6a57f4 100644 --- a/src/Griddly/Core/PathFinder.hpp +++ b/src/Griddly/Core/PathFinder.hpp @@ -1,12 +1,13 @@ #pragma once -#include "Grid.hpp" #include #include #include namespace griddly { +class Grid; + struct SearchOutput { uint32_t actionId = 0; }; From 014d23ef660f428822fa548bc2447bb3287d33f7 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Sun, 31 Oct 2021 11:19:10 +0000 Subject: [PATCH 07/20] adding pathfinder code to exec actions, also adding collision detector tooling for pathfinding that should only happen in certain ranges --- src/Griddly/Core/CollisionDetector.hpp | 9 ++- src/Griddly/Core/GDY/Objects/Object.cpp | 71 +++++++++++-------- src/Griddly/Core/GDY/Objects/Object.hpp | 11 +++ src/Griddly/Core/GDY/YAMLUtils.hpp | 14 ++-- .../Core/SpatialHashCollisionDetector.cpp | 11 +-- .../Core/SpatialHashCollisionDetector.hpp | 2 +- tests/src/Griddly/Core/GameProcessTest.cpp | 2 +- 7 files changed, 76 insertions(+), 44 deletions(-) diff --git a/src/Griddly/Core/CollisionDetector.hpp b/src/Griddly/Core/CollisionDetector.hpp index e16221a7f..f957e5c60 100644 --- a/src/Griddly/Core/CollisionDetector.hpp +++ b/src/Griddly/Core/CollisionDetector.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -9,6 +10,12 @@ namespace griddly { class Object; + +struct SearchResult { + std::unordered_set> objectSet; + std::vector> closestObjects; +}; + class CollisionDetector { public: CollisionDetector(uint32_t gridWidth, uint32_t gridHeight, uint32_t range); @@ -17,7 +24,7 @@ class CollisionDetector { virtual bool remove(std::shared_ptr object) = 0; - virtual std::unordered_set> search(glm::ivec2 location) = 0; + virtual SearchResult search(glm::ivec2 location) = 0; protected: const uint32_t range_; diff --git a/src/Griddly/Core/GDY/Objects/Object.cpp b/src/Griddly/Core/GDY/Objects/Object.cpp index f3f2077b7..751bec069 100644 --- a/src/Griddly/Core/GDY/Objects/Object.cpp +++ b/src/Griddly/Core/GDY/Objects/Object.cpp @@ -374,15 +374,37 @@ BehaviourFunction Object::instantiateBehaviour(std::string commandName, Behaviou auto actionId = commandArguments["ActionId"].as(0); auto executor = commandArguments["Executor"].as("action"); + PathFinderConfig pathFinderConfig = getPathFinderConfig(commandArguments["Search"], actionName); + auto actionExecutor = getActionExecutorFromString(executor); // Resolve source object - return [this, actionName, delay, randomize, actionId, actionExecutor](std::shared_ptr action) -> BehaviourResult { + return [this, actionName, delay, randomize, actionId, actionExecutor, pathFinderConfig](std::shared_ptr action) -> BehaviourResult { InputMapping fallbackInputMapping; fallbackInputMapping.vectorToDest = action->getVectorToDest(); fallbackInputMapping.orientationVector = action->getOrientationVector(); - auto inputMapping = getInputMapping(actionName, actionId, randomize, fallbackInputMapping); + SingleInputMapping inputMapping; + if(pathFinderConfig.pathFinder != nullptr) { + spdlog::debug("Executing action based on PathFinder"); + auto endLocation = pathFinderConfig.endLocation; + if (pathFinderConfig.collisionDetector != nullptr) { + auto searchResult = pathFinderConfig.collisionDetector->search(getLocation()); + + if (searchResult.objectSet.empty()) { + return {}; + } + + endLocation = searchResult.closestObjects.at(0)->getLocation(); + } + + auto searchResult = pathFinderConfig.pathFinder->search(getLocation(), pathFinderConfig.endLocation, getObjectOrientation().getUnitVector(), pathFinderConfig.maxSearchDepth); + inputMapping = getInputMapping(actionName, searchResult.actionId, false, fallbackInputMapping); + } else { + inputMapping = getInputMapping(actionName, actionId, randomize, fallbackInputMapping); + } + + if (inputMapping.mappedToGrid) { inputMapping.vectorToDest = inputMapping.destinationLocation - getLocation(); @@ -443,33 +465,6 @@ BehaviourFunction Object::instantiateBehaviour(std::string commandName, Behaviou }; } - if (commandName == "search") { - auto actionName = commandArguments["Action"].as(); - auto maxSearchDepth = commandArguments["MaxDepth"].as(); - auto targetObjectName = commandArguments["TargetObjectName"].as(); - auto impassableObjectsList = singleOrListNodeToList(commandArguments["impassableObjects"]); - - std::unordered_set impassableObjectsSet(impassableObjectsList.begin(), impassableObjectsList.end()); - auto actionInputDefinitions = objectGenerator_->getActionInputDefinitions(); - auto actionInputDefinitionIt = actionInputDefinitions.find(actionName); - - if(actionInputDefinitionIt == actionInputDefinitions.end()) { - auto errorString = fmt::format("Cannot find action definition for action '{0}', invalid 'search' command.", actionName); - spdlog::error(errorString); - throw std::invalid_argument(errorString); - } - - //grid_->getCollisionDetectors() - - auto pathFinder = std::shared_ptr(new AStarPathFinder(grid_, impassableObjectsSet, actionInputDefinitionIt->second)); - return [this, pathFinder](std::shared_ptr action) -> BehaviourResult { - - //auto searchResult = pathFinder->search(getLocation(), , getObjectOrientation(), maxSearchDepth); - - return {}; - }; - } - throw std::invalid_argument(fmt::format("Unknown or badly defined command {0}.", commandName)); } @@ -665,6 +660,24 @@ std::vector> Object::getInitialActions(std::shared_ptr(); + auto targetEndLocation = singleOrListNodeToList(searchNode["TargetLocation"]); + auto impassableObjectsList = singleOrListNodeToList(searchNode["impassableObjects"]); + std::unordered_set impassableObjectsSet(impassableObjectsList.begin(), impassableObjectsList.end()); + auto actionInputDefinitions = objectGenerator_->getActionInputDefinitions(); + auto actionInputDefinitionIt = actionInputDefinitions.find(actionName); + + config.maxSearchDepth = searchNode["MaxDepth"].as(); + config.pathFinder = std::shared_ptr(new AStarPathFinder(grid_, impassableObjectsSet, actionInputDefinitionIt->second)); + } + + return config; +} + uint32_t Object::getPlayerId() const { return *playerId_; } diff --git a/src/Griddly/Core/GDY/Objects/Object.hpp b/src/Griddly/Core/GDY/Objects/Object.hpp index 3eab2d67d..643f1732b 100644 --- a/src/Griddly/Core/GDY/Objects/Object.hpp +++ b/src/Griddly/Core/GDY/Objects/Object.hpp @@ -14,6 +14,7 @@ #include "ObjectVariable.hpp" #include "../YAMLUtils.hpp" + #define BehaviourCommandArguments std::unordered_map #define BehaviourFunction std::function)> #define PreconditionFunction std::function)> @@ -25,6 +26,7 @@ class Grid; class Action; class ObjectGenerator; class InputMapping; +class PathFinder; struct InitialActionDefinition { std::string actionName; @@ -61,6 +63,13 @@ enum class ActionExecutor { OBJECT_PLAYER_ID, }; +struct PathFinderConfig { + std::shared_ptr pathFinder = nullptr; + std::shared_ptr collisionDetector = nullptr; + glm::ivec2 endLocation; + uint32_t maxSearchDepth = 100; +}; + class Object : public std::enable_shared_from_this { public: virtual glm::ivec2 getLocation() const; @@ -155,6 +164,8 @@ class Object : public std::enable_shared_from_this { SingleInputMapping getInputMapping(std::string actionName, uint32_t actionId, bool randomize, InputMapping fallback); + PathFinderConfig getPathFinderConfig(YAML::Node searchNode, std::string actionName); + std::unordered_map> resolveVariables(BehaviourCommandArguments variables); PreconditionFunction instantiatePrecondition(std::string commandName, BehaviourCommandArguments commandArguments); diff --git a/src/Griddly/Core/GDY/YAMLUtils.hpp b/src/Griddly/Core/GDY/YAMLUtils.hpp index 2ca072657..8e9879156 100644 --- a/src/Griddly/Core/GDY/YAMLUtils.hpp +++ b/src/Griddly/Core/GDY/YAMLUtils.hpp @@ -1,22 +1,20 @@ #pragma once +#include #include -namespace YAML { -class Node; -} - #define BehaviourCommandArguments std::unordered_map namespace griddly { -inline std::vector singleOrListNodeToList(YAML::Node singleOrList) { - std::vector values; +template +inline std::vector singleOrListNodeToList(YAML::Node singleOrList) { + std::vector values; if (singleOrList.IsScalar()) { - values.push_back(singleOrList.as()); + values.push_back(singleOrList.as()); } else if (singleOrList.IsSequence()) { for (std::size_t s = 0; s < singleOrList.size(); s++) { - values.push_back(singleOrList[s].as()); + values.push_back(singleOrList[s].as()); } } diff --git a/src/Griddly/Core/SpatialHashCollisionDetector.cpp b/src/Griddly/Core/SpatialHashCollisionDetector.cpp index 78ed31832..ff510c7d1 100644 --- a/src/Griddly/Core/SpatialHashCollisionDetector.cpp +++ b/src/Griddly/Core/SpatialHashCollisionDetector.cpp @@ -34,20 +34,20 @@ bool SpatialHashCollisionDetector::remove(std::shared_ptr object) { return bucketIt->second.erase(object) > 0; } -std::unordered_set> SpatialHashCollisionDetector::search(glm::ivec2 location) { +SearchResult SpatialHashCollisionDetector::search(glm::ivec2 location) { auto top = std::max(0, location.y - (int32_t)range_); auto bottom = std::min(gridHeight_, location.y + range_); auto left = std::max(0, location.x - (int32_t)range_); auto right = std::min(gridWidth_, location.x + range_); - - auto topLeft = glm::ivec2(left, top); auto bottomLeft = glm::ivec2(left, bottom); auto topRight = glm::ivec2(right, top); auto bottomRight = glm::ivec2(right, bottom); + // TODO: fix this for large ranges that span many cellSizes + const std::unordered_set hashes = { calculateHash(topLeft), calculateHash(bottomLeft), @@ -56,6 +56,7 @@ std::unordered_set> SpatialHashCollisionDetector::search }; std::unordered_set> collidedObjects; + std::vector> closestObjects; for (const auto& hash : hashes) { spdlog::debug("object location ({0},{1})", location.x, location.y); @@ -72,6 +73,7 @@ std::unordered_set> SpatialHashCollisionDetector::search } else if (std::abs(location.y - collisionLocation.y) == range_ && std::abs(location.x - collisionLocation.x) <= range_) { spdlog::debug("Range collided object at ({0},{1}), source object at ({2},{3})", collisionLocation.x, collisionLocation.y, location.x, location.y); collidedObjects.insert(object); + closestObjects.push_back(object); } } } @@ -82,6 +84,7 @@ std::unordered_set> SpatialHashCollisionDetector::search if (std::abs(location.y - collisionLocation.y) <= range_ && std::abs(location.x - collisionLocation.x) <= range_) { spdlog::debug("Area collided object at ({0},{1}), source object at ({2},{3})", collisionLocation.x, collisionLocation.y, location.x, location.y); collidedObjects.insert(object); + closestObjects.push_back(object); } } } @@ -89,7 +92,7 @@ std::unordered_set> SpatialHashCollisionDetector::search } } - return collidedObjects; + return {collidedObjects, closestObjects}; } glm::ivec2 SpatialHashCollisionDetector::calculateHash(glm::ivec2 location) { diff --git a/src/Griddly/Core/SpatialHashCollisionDetector.hpp b/src/Griddly/Core/SpatialHashCollisionDetector.hpp index 638d63d5a..643472f50 100644 --- a/src/Griddly/Core/SpatialHashCollisionDetector.hpp +++ b/src/Griddly/Core/SpatialHashCollisionDetector.hpp @@ -15,7 +15,7 @@ class SpatialHashCollisionDetector : public CollisionDetector { virtual bool remove(std::shared_ptr object) override; - virtual std::unordered_set> search(glm::ivec2 location) override; + virtual SearchResult search(glm::ivec2 location) override; private: glm::ivec2 calculateHash(glm::ivec2 location); diff --git a/tests/src/Griddly/Core/GameProcessTest.cpp b/tests/src/Griddly/Core/GameProcessTest.cpp index 869080561..6cebfecc9 100644 --- a/tests/src/Griddly/Core/GameProcessTest.cpp +++ b/tests/src/Griddly/Core/GameProcessTest.cpp @@ -1024,7 +1024,7 @@ TEST(GameProcessTest, clone) { auto gameProcessPtr = std::shared_ptr(new TurnBasedGameProcess(ObserverType::NONE, mockGDYFactoryPtr, mockGridPtr)); - for(int i = 0; i<100; i++){ + for(int i = 0; i<100; i++) { auto clonedPtr = gameProcessPtr->clone(); } } From 3451ab66c8188b29814685206086798e50fe21bc Mon Sep 17 00:00:00 2001 From: Bam4d Date: Sun, 31 Oct 2021 17:57:59 +0000 Subject: [PATCH 08/20] more impl, fixes etc --- src/Griddly/Core/GDY/Objects/Object.cpp | 56 +++++++++++--- src/Griddly/Core/GDY/Objects/Object.hpp | 6 +- src/Griddly/Core/Grid.cpp | 11 ++- src/Griddly/Core/Grid.hpp | 2 + .../Core/SpatialHashCollisionDetector.cpp | 27 +++---- tests/src/Griddly/Core/GridTest.cpp | 6 +- .../Core/SpatialHashCollisionDetectorTest.cpp | 74 ++++++++++++++++--- .../Griddly/Core/MockCollisionDetector.hpp | 2 +- 8 files changed, 139 insertions(+), 45 deletions(-) diff --git a/src/Griddly/Core/GDY/Objects/Object.cpp b/src/Griddly/Core/GDY/Objects/Object.cpp index 751bec069..91824c4b9 100644 --- a/src/Griddly/Core/GDY/Objects/Object.cpp +++ b/src/Griddly/Core/GDY/Objects/Object.cpp @@ -7,6 +7,7 @@ #include "../Actions/Action.hpp" #include "ObjectGenerator.hpp" #include "../../AStarPathFinder.hpp" +#include "../../SpatialHashCollisionDetector.hpp" namespace griddly { @@ -368,14 +369,16 @@ BehaviourFunction Object::instantiateBehaviour(std::string commandName, Behaviou } if (commandName == "exec") { - auto actionName = commandArguments["Action"].as(); - auto delay = commandArguments["Delay"].as(0); - auto randomize = commandArguments["Randomize"].as(false); - auto actionId = commandArguments["ActionId"].as(0); - auto executor = commandArguments["Executor"].as("action"); + auto actionName = getCommandArgument(commandArguments, "Action", ""); + auto delay = getCommandArgument(commandArguments, "Delay", 0); + auto randomize = getCommandArgument(commandArguments, "Randomize", false); + auto actionId = getCommandArgument(commandArguments, "ActionId", 0); + auto executor = getCommandArgument(commandArguments, "Executor", "action"); + + auto searchNode = getCommandArgument(commandArguments, "Search", YAML::Node(YAML::NodeType::Undefined)); + + PathFinderConfig pathFinderConfig = configurePathFinder(searchNode, actionName); - PathFinderConfig pathFinderConfig = getPathFinderConfig(commandArguments["Search"], actionName); - auto actionExecutor = getActionExecutorFromString(executor); // Resolve source object @@ -660,19 +663,50 @@ std::vector> Object::getInitialActions(std::shared_ptr +C Object::getCommandArgument(BehaviourCommandArguments commandArguments, std::string commandArgumentKey, C defaultValue) { + auto commandArgumentIt = commandArguments.find(commandArgumentKey); + if(commandArgumentIt == commandArguments.end()) { + return defaultValue; + } + + return commandArgumentIt->second.as(defaultValue); + +} + +PathFinderConfig Object::configurePathFinder(YAML::Node searchNode, std::string actionName) { PathFinderConfig config; if (searchNode.IsDefined()) { - auto targetObjectName = searchNode["TargetObjectName"].as(); - auto targetEndLocation = singleOrListNodeToList(searchNode["TargetLocation"]); + auto targetObjectNameNode = searchNode["TargetObjectName"]; + + if(targetObjectNameNode.IsDefined()) { + + auto targetObjectName = targetObjectNameNode.as(); + // Just make the range really large so we always look in all cells + auto range = std::max(grid_->getWidth(), grid_->getHeight()); + + config.collisionDetector = std::shared_ptr(new SpatialHashCollisionDetector(grid_->getWidth(), grid_->getHeight(), 10, range, TriggerType::RANGE_BOX_AREA)); + + if(config.collisionDetector != nullptr) { + grid_->addCollisionObjectName(targetObjectName, actionName); + } + } + auto impassableObjectsList = singleOrListNodeToList(searchNode["impassableObjects"]); + std::unordered_set impassableObjectsSet(impassableObjectsList.begin(), impassableObjectsList.end()); auto actionInputDefinitions = objectGenerator_->getActionInputDefinitions(); auto actionInputDefinitionIt = actionInputDefinitions.find(actionName); - config.maxSearchDepth = searchNode["MaxDepth"].as(); + config.maxSearchDepth = searchNode["MaxDepth"].as(100); config.pathFinder = std::shared_ptr(new AStarPathFinder(grid_, impassableObjectsSet, actionInputDefinitionIt->second)); + + if (searchNode["TargetLocation"].IsDefined()) { + auto targetEndLocation = singleOrListNodeToList(searchNode["TargetLocation"]); + config.endLocation = glm::ivec2(targetEndLocation[0], targetEndLocation[1]); + } + } return config; diff --git a/src/Griddly/Core/GDY/Objects/Object.hpp b/src/Griddly/Core/GDY/Objects/Object.hpp index 643f1732b..d8817dd06 100644 --- a/src/Griddly/Core/GDY/Objects/Object.hpp +++ b/src/Griddly/Core/GDY/Objects/Object.hpp @@ -27,6 +27,7 @@ class Action; class ObjectGenerator; class InputMapping; class PathFinder; +class CollisionDetector; struct InitialActionDefinition { std::string actionName; @@ -164,7 +165,10 @@ class Object : public std::enable_shared_from_this { SingleInputMapping getInputMapping(std::string actionName, uint32_t actionId, bool randomize, InputMapping fallback); - PathFinderConfig getPathFinderConfig(YAML::Node searchNode, std::string actionName); + PathFinderConfig configurePathFinder(YAML::Node searchNode, std::string actionName); + + template + static C getCommandArgument(BehaviourCommandArguments commandArguments, std::string commandArgumentKey, C defaultValue); std::unordered_map> resolveVariables(BehaviourCommandArguments variables); diff --git a/src/Griddly/Core/Grid.cpp b/src/Griddly/Core/Grid.cpp index 6f6e2d063..f476f71ca 100644 --- a/src/Griddly/Core/Grid.cpp +++ b/src/Griddly/Core/Grid.cpp @@ -347,9 +347,11 @@ std::unordered_map Grid::processCollisions() { for (const auto& actionName : collisionActionNames) { spdlog::debug("Collision detector under action {0} for object {1} being queried", actionName, objectName); auto collisionDetector = collisionDetectors_.at(actionName); - auto objectsInCollisionRange = collisionDetector->search(location); + auto searchResults = collisionDetector->search(location); auto& actionTriggerDefinition = actionTriggerDefinitions_.at(actionName); + auto objectsInCollisionRange = searchResults.objectSet; + for (auto collisionObject : objectsInCollisionRange) { if (collisionObject == object) continue; @@ -483,15 +485,20 @@ void Grid::addActionProbability(std::string actionName, float probability) { actionProbabilities_[actionName] = probability; } +void Grid::addCollisionObjectName(std::string objectName, std::string actionName) { + collisionObjectActionNames_[objectName].insert(actionName); +} + void Grid::addActionTrigger(std::string actionName, ActionTriggerDefinition actionTriggerDefinition) { std::shared_ptr collisionDetector = collisionDetectorFactory_->newCollisionDetector(width_, height_, actionTriggerDefinition); for (auto sourceObjectName : actionTriggerDefinition.sourceObjectNames) { - collisionObjectActionNames_[sourceObjectName].insert(actionName); + addCollisionObjectName(sourceObjectName, actionName); collisionSourceObjectActionNames_[sourceObjectName].insert(actionName); } for (auto destinationObjectName : actionTriggerDefinition.destinationObjectNames) { + addCollisionObjectName(destinationObjectName, actionName); collisionObjectActionNames_[destinationObjectName].insert(actionName); } diff --git a/src/Griddly/Core/Grid.hpp b/src/Griddly/Core/Grid.hpp index 2889f0c44..16bdaee7d 100644 --- a/src/Griddly/Core/Grid.hpp +++ b/src/Griddly/Core/Grid.hpp @@ -158,6 +158,8 @@ class Grid : public std::enable_shared_from_this { virtual const std::unordered_map>& getSourceObjectCollisionActionNames() const; virtual const std::unordered_map>& getObjectCollisionActionNames() const; + void addCollisionObjectName(std::string objectName, std::string actionName); + private: GridEvent buildGridEvent(std::shared_ptr action, uint32_t playerId, uint32_t tick); void recordGridEvent(GridEvent event, std::unordered_map rewards); diff --git a/src/Griddly/Core/SpatialHashCollisionDetector.cpp b/src/Griddly/Core/SpatialHashCollisionDetector.cpp index ff510c7d1..ac4eb28f9 100644 --- a/src/Griddly/Core/SpatialHashCollisionDetector.cpp +++ b/src/Griddly/Core/SpatialHashCollisionDetector.cpp @@ -36,24 +36,21 @@ bool SpatialHashCollisionDetector::remove(std::shared_ptr object) { SearchResult SpatialHashCollisionDetector::search(glm::ivec2 location) { - auto top = std::max(0, location.y - (int32_t)range_); - auto bottom = std::min(gridHeight_, location.y + range_); - auto left = std::max(0, location.x - (int32_t)range_); + auto top = std::min(gridHeight_, location.y + range_); + auto bottom = std::max(0, location.y - (int32_t)range_); + auto right = std::min(gridWidth_, location.x + range_); + auto left = std::max(0, location.x - (int32_t)range_); - auto topLeft = glm::ivec2(left, top); - auto bottomLeft = glm::ivec2(left, bottom); - auto topRight = glm::ivec2(right, top); - auto bottomRight = glm::ivec2(right, bottom); - - // TODO: fix this for large ranges that span many cellSizes + auto bottomLeft = calculateHash(glm::ivec2(left, bottom)); + auto topRight = calculateHash(glm::ivec2(right, top)); - const std::unordered_set hashes = { - calculateHash(topLeft), - calculateHash(bottomLeft), - calculateHash(topRight), - calculateHash(bottomRight), - }; + std::vector hashes; + for(uint32_t hashy = bottomLeft.y; hashy <= topRight.y; hashy++) { + for(uint32_t hashx = bottomLeft.x; hashx <= topRight.x; hashx++) { + hashes.push_back({hashx, hashy}); + } + } std::unordered_set> collidedObjects; std::vector> closestObjects; diff --git a/tests/src/Griddly/Core/GridTest.cpp b/tests/src/Griddly/Core/GridTest.cpp index 77c7ffcab..46baaa53a 100644 --- a/tests/src/Griddly/Core/GridTest.cpp +++ b/tests/src/Griddly/Core/GridTest.cpp @@ -916,13 +916,13 @@ TEST(GridTest, performActionTriggeredByCollision) { grid->addObject({3, 3}, mockObjectPtr3); EXPECT_CALL(*mockCollisionDetectorPtr1, search(Eq(glm::ivec2{1, 1}))) - .WillOnce(Return(std::unordered_set>{mockObjectPtr1, mockObjectPtr2, mockObjectPtr3})); + .WillOnce(Return(SearchResult{{mockObjectPtr1, mockObjectPtr2, mockObjectPtr3},{}})); EXPECT_CALL(*mockCollisionDetectorPtr1, search(Eq(glm::ivec2{2, 2}))) - .WillOnce(Return(std::unordered_set>{mockObjectPtr1, mockObjectPtr2, mockObjectPtr3})); + .WillOnce(Return(SearchResult{{mockObjectPtr1, mockObjectPtr2, mockObjectPtr3},{}})); EXPECT_CALL(*mockCollisionDetectorPtr1, search(Eq(glm::ivec2{3, 3}))) - .WillOnce(Return(std::unordered_set>{mockObjectPtr1, mockObjectPtr2, mockObjectPtr3})); + .WillOnce(Return(SearchResult{{mockObjectPtr1, mockObjectPtr2, mockObjectPtr3},{}})); auto rewards = grid->update(); diff --git a/tests/src/Griddly/Core/SpatialHashCollisionDetectorTest.cpp b/tests/src/Griddly/Core/SpatialHashCollisionDetectorTest.cpp index 8dce7c977..31b08304a 100644 --- a/tests/src/Griddly/Core/SpatialHashCollisionDetectorTest.cpp +++ b/tests/src/Griddly/Core/SpatialHashCollisionDetectorTest.cpp @@ -65,9 +65,13 @@ TEST(SpatialHashCollisionDetectorTest, test_search_area_single_hash) { ASSERT_TRUE(collisionDetector->upsert(mockObjectPtr7)); ASSERT_TRUE(collisionDetector->upsert(mockObjectPtr8)); - auto objects1 = collisionDetector->search({3, 3}); - auto objects2 = collisionDetector->search({2, 2}); - auto objects3 = collisionDetector->search({1, 1}); + auto searchResults1 = collisionDetector->search({3, 3}); + auto searchResults2 = collisionDetector->search({2, 2}); + auto searchResults3 = collisionDetector->search({1, 1}); + + auto objects1 = searchResults1.objectSet; + auto objects2 = searchResults2.objectSet; + auto objects3 = searchResults3.objectSet; ASSERT_THAT(objects1, UnorderedElementsAre(mockObjectPtr1, mockObjectPtr2, mockObjectPtr3, mockObjectPtr4, mockObjectPtr8)); ASSERT_THAT(objects2, UnorderedElementsAre(mockObjectPtr1, mockObjectPtr2, mockObjectPtr3, mockObjectPtr4, mockObjectPtr5, mockObjectPtr6, mockObjectPtr7, mockObjectPtr8)); @@ -95,9 +99,13 @@ TEST(SpatialHashCollisionDetectorTest, test_search_area_across_many_hash) { ASSERT_TRUE(collisionDetector->upsert(mockObjectPtr7)); ASSERT_TRUE(collisionDetector->upsert(mockObjectPtr8)); - auto objects1 = collisionDetector->search({3, 3}); - auto objects2 = collisionDetector->search({2, 2}); - auto objects3 = collisionDetector->search({1, 1}); + auto searchResults1 = collisionDetector->search({3, 3}); + auto searchResults2 = collisionDetector->search({2, 2}); + auto searchResults3 = collisionDetector->search({1, 1}); + + auto objects1 = searchResults1.objectSet; + auto objects2 = searchResults2.objectSet; + auto objects3 = searchResults3.objectSet; ASSERT_THAT(objects1, UnorderedElementsAre(mockObjectPtr1, mockObjectPtr2, mockObjectPtr3, mockObjectPtr4, mockObjectPtr8)); ASSERT_THAT(objects2, UnorderedElementsAre(mockObjectPtr1, mockObjectPtr2, mockObjectPtr3, mockObjectPtr4, mockObjectPtr5, mockObjectPtr6, mockObjectPtr7, mockObjectPtr8)); @@ -125,9 +133,13 @@ TEST(SpatialHashCollisionDetectorTest, test_search_boundary_single_hash) { ASSERT_TRUE(collisionDetector->upsert(mockObjectPtr7)); ASSERT_TRUE(collisionDetector->upsert(mockObjectPtr8)); - auto objects1 = collisionDetector->search({3, 3}); - auto objects2 = collisionDetector->search({2, 2}); - auto objects3 = collisionDetector->search({1, 1}); + auto searchResults1 = collisionDetector->search({3, 3}); + auto searchResults2 = collisionDetector->search({2, 2}); + auto searchResults3 = collisionDetector->search({1, 1}); + + auto objects1 = searchResults1.objectSet; + auto objects2 = searchResults2.objectSet; + auto objects3 = searchResults3.objectSet; ASSERT_THAT(objects1, UnorderedElementsAre(mockObjectPtr1, mockObjectPtr2, mockObjectPtr3)); ASSERT_THAT(objects2, UnorderedElementsAre(mockObjectPtr5, mockObjectPtr6, mockObjectPtr7, mockObjectPtr8)); @@ -155,9 +167,47 @@ TEST(SpatialHashCollisionDetectorTest, test_search_boundary_across_many_hash) { ASSERT_TRUE(collisionDetector->upsert(mockObjectPtr7)); ASSERT_TRUE(collisionDetector->upsert(mockObjectPtr8)); - auto objects1 = collisionDetector->search({3, 3}); - auto objects2 = collisionDetector->search({2, 2}); - auto objects3 = collisionDetector->search({1, 1}); + auto searchResults1 = collisionDetector->search({3, 3}); + auto searchResults2 = collisionDetector->search({2, 2}); + auto searchResults3 = collisionDetector->search({1, 1}); + + auto objects1 = searchResults1.objectSet; + auto objects2 = searchResults2.objectSet; + auto objects3 = searchResults3.objectSet; + + ASSERT_THAT(objects1, UnorderedElementsAre(mockObjectPtr1, mockObjectPtr2, mockObjectPtr3)); + ASSERT_THAT(objects2, UnorderedElementsAre(mockObjectPtr5, mockObjectPtr6, mockObjectPtr7, mockObjectPtr8)); + ASSERT_THAT(objects3, UnorderedElementsAre(mockObjectPtr2, mockObjectPtr3, mockObjectPtr4)); +} + +TEST(SpatialHashCollisionDetectorTest, test_search_range_larger_than_cell_size) { + auto collisionDetector = std::shared_ptr(new SpatialHashCollisionDetector(10, 10, 1, 2, TriggerType::RANGE_BOX_BOUNDARY)); + + auto mockObjectPtr1 = mockObject("object1", {1, 1}); + auto mockObjectPtr2 = mockObject("object2", {3, 1}); + auto mockObjectPtr3 = mockObject("object3", {1, 3}); + auto mockObjectPtr4 = mockObject("object4", {3, 3}); + auto mockObjectPtr5 = mockObject("object5", {0, 0}); + auto mockObjectPtr6 = mockObject("object6", {4, 0}); + auto mockObjectPtr7 = mockObject("object7", {0, 4}); + auto mockObjectPtr8 = mockObject("object8", {4, 4}); + + ASSERT_TRUE(collisionDetector->upsert(mockObjectPtr1)); + ASSERT_TRUE(collisionDetector->upsert(mockObjectPtr2)); + ASSERT_TRUE(collisionDetector->upsert(mockObjectPtr3)); + ASSERT_TRUE(collisionDetector->upsert(mockObjectPtr4)); + ASSERT_TRUE(collisionDetector->upsert(mockObjectPtr5)); + ASSERT_TRUE(collisionDetector->upsert(mockObjectPtr6)); + ASSERT_TRUE(collisionDetector->upsert(mockObjectPtr7)); + ASSERT_TRUE(collisionDetector->upsert(mockObjectPtr8)); + + auto searchResults1 = collisionDetector->search({3, 3}); + auto searchResults2 = collisionDetector->search({2, 2}); + auto searchResults3 = collisionDetector->search({1, 1}); + + auto objects1 = searchResults1.objectSet; + auto objects2 = searchResults2.objectSet; + auto objects3 = searchResults3.objectSet; ASSERT_THAT(objects1, UnorderedElementsAre(mockObjectPtr1, mockObjectPtr2, mockObjectPtr3)); ASSERT_THAT(objects2, UnorderedElementsAre(mockObjectPtr5, mockObjectPtr6, mockObjectPtr7, mockObjectPtr8)); diff --git a/tests/src/Mocks/Griddly/Core/MockCollisionDetector.hpp b/tests/src/Mocks/Griddly/Core/MockCollisionDetector.hpp index 0c046fd8c..92273248c 100644 --- a/tests/src/Mocks/Griddly/Core/MockCollisionDetector.hpp +++ b/tests/src/Mocks/Griddly/Core/MockCollisionDetector.hpp @@ -9,6 +9,6 @@ class MockCollisionDetector : public CollisionDetector { MOCK_METHOD(bool, upsert, (std::shared_ptr object), ()); MOCK_METHOD(bool, remove, (std::shared_ptr object), ()); - MOCK_METHOD(std::unordered_set>, search, (glm::ivec2 location), ()); + MOCK_METHOD(SearchResult, search, (glm::ivec2 location), ()); }; } // namespace griddly \ No newline at end of file From 709e0643db666e83852023275ee4b81a0a47c4fc Mon Sep 17 00:00:00 2001 From: Bam4d Date: Tue, 2 Nov 2021 08:08:11 +0000 Subject: [PATCH 09/20] more tests and fixes and examples --- python/examples/AStar search/astar.py | 36 ++++ .../astar_opponent_environment.yaml | 144 +++++++++++++++ src/Griddly/Core/GDY/GDYFactory.cpp | 2 +- src/Griddly/Core/GDY/Objects/Object.cpp | 34 ++-- src/Griddly/Core/GDY/Objects/Object.hpp | 10 +- .../Core/GDY/Objects/ObjectGenerator.cpp | 13 +- .../Core/GDY/Objects/ObjectGenerator.hpp | 6 +- src/Griddly/Core/Grid.cpp | 21 ++- src/Griddly/Core/Grid.hpp | 2 +- .../Core/LevelGenerators/MapGenerator.cpp | 4 +- src/Griddly/Core/TurnBasedGameProcess.cpp | 4 +- tests/src/Griddly/Core/GDY/GDYFactoryTest.cpp | 18 +- .../Griddly/Core/GDY/Objects/ObjectTest.cpp | 174 +++++++++++++----- .../Core/LevelGenerator/MapReaderTest.cpp | 42 ++--- .../Griddly/Core/GDY/Objects/MockObject.hpp | 4 +- .../Core/GDY/Objects/MockObjectGenerator.hpp | 4 +- tests/src/Mocks/Griddly/Core/MockGrid.hpp | 2 + 17 files changed, 400 insertions(+), 120 deletions(-) create mode 100644 python/examples/AStar search/astar.py create mode 100644 python/examples/AStar search/astar_opponent_environment.yaml diff --git a/python/examples/AStar search/astar.py b/python/examples/AStar search/astar.py new file mode 100644 index 000000000..b02acc29a --- /dev/null +++ b/python/examples/AStar search/astar.py @@ -0,0 +1,36 @@ +import os + +from griddly import GymWrapperFactory, gd, GymWrapper +from griddly.RenderTools import VideoRecorder + +if __name__ == '__main__': + wrapper = GymWrapperFactory() + + name = 'astar_opponent_environment' + + current_path = os.path.dirname(os.path.realpath(__file__)) + + env = GymWrapper('astar_opponent_environment.yaml', + player_observer_type=gd.ObserverType.VECTOR, + global_observer_type=gd.ObserverType.SPRITE_2D, + level=0) + + env.reset() + + global_recorder = VideoRecorder() + global_visualization = env.render(observer='global', mode='rgb_array') + global_recorder.start("global_video_test.mp4", global_visualization.shape) + + for i in range(1000): + + obs, reward, done, info = env.step(env.action_space.sample()) + + env.render(observer='global') + frame = env.render(observer='global', mode='rgb_array') + + global_recorder.add_frame(frame) + + if done: + env.reset() + + global_recorder.close() \ No newline at end of file diff --git a/python/examples/AStar search/astar_opponent_environment.yaml b/python/examples/AStar search/astar_opponent_environment.yaml new file mode 100644 index 000000000..56e5a25fd --- /dev/null +++ b/python/examples/AStar search/astar_opponent_environment.yaml @@ -0,0 +1,144 @@ +Version: "0.1" +Environment: + Name: Spider Throwing Fire + Description: The spider can now throw fireballs + Observers: + Block2D: + TileSize: 24 + Vector: + IncludePlayerId: true + Player: + AvatarObject: catcher + Levels: + - | + W W W W W W W W W W W W W W W + W . . . . . . . . . . . . . W + W . . . . . . W W W . W . . W + W . . . W W W W . . . W . . W + W . . W . . . . . . . W . . W + W . . W . . . W . . . W . . W + W . . W . W W W . . . W . . W + W . . W . . . W . . . W . . W + W . . W W . . W . . . W . . W + W g . W . . . W W W W W . . W + W W W W . W W . . . . W . . W + W . . . . . . . . . . W . . W + W . . . . . . . . . . W . . W + W c . . . . . . . . . W . s W + W W W W W W W W W W W W W W W + +Actions: + + - Name: chase + InputMapping: + Internal: true + Behaviours: + - Src: + Object: spider + Commands: + - mov: _dest + - exec: + Action: chase + Delay: 2 + Search: + ImpassableObjects: [wall] + TargetObjectName: catcher + Dst: + Object: _empty + + - Src: + Object: spider + Commands: + - exec: + Action: chase + Delay: 2 + Search: + ImpassableObjects: [ wall ] + TargetObjectName: catcher + Dst: + Object: spider + + + - Src: + Object: spider + Dst: + Object: catcher + Commands: + - reward: -1 + - remove: true + + - Name: move + Behaviours: + - Src: + Object: catcher + Commands: + - mov: _dest + Dst: + Object: _empty + + - Src: + Object: catcher + Commands: + - reward: -1 + - remove: true + Dst: + Object: spider + +Objects: + - Name: spider + InitialActions: + - Action: chase + Delay: 2 + ActionId: 0 + MapCharacter: s + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/avatars/spider1.png + Block2D: + - Shape: triangle + Color: [ 0.9, 0.2, 0.2 ] + Scale: 1.0 + + - Name: catcher + MapCharacter: c + Observers: + Sprite2D: + - Image: gvgai/newset/girl5.png + Block2D: + - Color: [ 0.0, 0.8, 0.0 ] + Shape: square + + - Name: goal + MapCharacter: g + Observers: + Sprite2D: + - Image: gvgai/newset/exit2.png + Block2D: + - Color: [ 0.0, 0.0, 0.8 ] + Shape: square + + - Name: wall + MapCharacter: W + Observers: + Sprite2D: + - TilingMode: WALL_16 + Image: + - oryx/oryx_fantasy/wall2-0.png + - oryx/oryx_fantasy/wall2-1.png + - oryx/oryx_fantasy/wall2-2.png + - oryx/oryx_fantasy/wall2-3.png + - oryx/oryx_fantasy/wall2-4.png + - oryx/oryx_fantasy/wall2-5.png + - oryx/oryx_fantasy/wall2-6.png + - oryx/oryx_fantasy/wall2-7.png + - oryx/oryx_fantasy/wall2-8.png + - oryx/oryx_fantasy/wall2-9.png + - oryx/oryx_fantasy/wall2-10.png + - oryx/oryx_fantasy/wall2-11.png + - oryx/oryx_fantasy/wall2-12.png + - oryx/oryx_fantasy/wall2-13.png + - oryx/oryx_fantasy/wall2-14.png + - oryx/oryx_fantasy/wall2-15.png + Block2D: + - Color: [ 0.5, 0.5, 0.5 ] + Shape: square \ No newline at end of file diff --git a/src/Griddly/Core/GDY/GDYFactory.cpp b/src/Griddly/Core/GDY/GDYFactory.cpp index b36c4e934..ccdb970bc 100644 --- a/src/Griddly/Core/GDY/GDYFactory.cpp +++ b/src/Griddly/Core/GDY/GDYFactory.cpp @@ -561,7 +561,7 @@ void GDYFactory::parseCommandNode( for (YAML::const_iterator execArgNode = commandNode.begin(); execArgNode != commandNode.end(); ++execArgNode) { auto execArgName = execArgNode->first.as(); - auto execArgValue = execArgNode->second.as(); + auto execArgValue = execArgNode->second; commandArgumentMap[execArgName] = execArgValue; } diff --git a/src/Griddly/Core/GDY/Objects/Object.cpp b/src/Griddly/Core/GDY/Objects/Object.cpp index 91824c4b9..3460a53dc 100644 --- a/src/Griddly/Core/GDY/Objects/Object.cpp +++ b/src/Griddly/Core/GDY/Objects/Object.cpp @@ -11,8 +11,8 @@ namespace griddly { -Object::Object(std::string objectName, char mapCharacter, uint32_t playerId, uint32_t zIdx, std::unordered_map> availableVariables, std::shared_ptr objectGenerator) - : objectName_(objectName), mapCharacter_(mapCharacter), zIdx_(zIdx), objectGenerator_(objectGenerator) { +Object::Object(std::string objectName, char mapCharacter, uint32_t playerId, uint32_t zIdx, std::unordered_map> availableVariables, std::shared_ptr objectGenerator, std::shared_ptr grid) + : objectName_(objectName), mapCharacter_(mapCharacter), zIdx_(zIdx), objectGenerator_(objectGenerator), grid_(grid) { availableVariables.insert({"_x", x_}); availableVariables.insert({"_y", y_}); @@ -25,17 +25,15 @@ Object::Object(std::string objectName, char mapCharacter, uint32_t playerId, uin Object::~Object() {} -void Object::init(glm::ivec2 location, std::shared_ptr grid) { - init(location, DiscreteOrientation(Direction::NONE), grid); +void Object::init(glm::ivec2 location) { + init(location, DiscreteOrientation(Direction::NONE)); } -void Object::init(glm::ivec2 location, DiscreteOrientation orientation, std::shared_ptr grid) { +void Object::init(glm::ivec2 location, DiscreteOrientation orientation) { *x_ = location.x; *y_ = location.y; orientation_ = orientation; - - grid_ = grid; } glm::ivec2 Object::getLocation() const { @@ -235,7 +233,7 @@ BehaviourFunction Object::instantiateBehaviour(std::string commandName, Behaviou spdlog::debug("Changing object={0} to {1}", getObjectName(), objectName); auto playerId = getPlayerId(); auto location = getLocation(); - auto newObject = objectGenerator_->newInstance(objectName, playerId, grid_->getGlobalVariables()); + auto newObject = objectGenerator_->newInstance(objectName, playerId, grid_); removeObject(); grid_->addObject(location, newObject, true, action); return {}; @@ -369,12 +367,12 @@ BehaviourFunction Object::instantiateBehaviour(std::string commandName, Behaviou } if (commandName == "exec") { + auto actionName = getCommandArgument(commandArguments, "Action", ""); auto delay = getCommandArgument(commandArguments, "Delay", 0); auto randomize = getCommandArgument(commandArguments, "Randomize", false); auto actionId = getCommandArgument(commandArguments, "ActionId", 0); auto executor = getCommandArgument(commandArguments, "Executor", "action"); - auto searchNode = getCommandArgument(commandArguments, "Search", YAML::Node(YAML::NodeType::Undefined)); PathFinderConfig pathFinderConfig = configurePathFinder(searchNode, actionName); @@ -401,14 +399,12 @@ BehaviourFunction Object::instantiateBehaviour(std::string commandName, Behaviou endLocation = searchResult.closestObjects.at(0)->getLocation(); } - auto searchResult = pathFinderConfig.pathFinder->search(getLocation(), pathFinderConfig.endLocation, getObjectOrientation().getUnitVector(), pathFinderConfig.maxSearchDepth); + auto searchResult = pathFinderConfig.pathFinder->search(getLocation(), endLocation, getObjectOrientation().getUnitVector(), pathFinderConfig.maxSearchDepth); inputMapping = getInputMapping(actionName, searchResult.actionId, false, fallbackInputMapping); } else { inputMapping = getInputMapping(actionName, actionId, randomize, fallbackInputMapping); } - - if (inputMapping.mappedToGrid) { inputMapping.vectorToDest = inputMapping.destinationLocation - getLocation(); } @@ -462,7 +458,7 @@ BehaviourFunction Object::instantiateBehaviour(std::string commandName, Behaviou spdlog::debug("Spawning object={0} in location [{1},{2}]", objectName, destinationLocation.x, destinationLocation.y); auto playerId = getPlayerId(); - auto newObject = objectGenerator_->newInstance(objectName, playerId, grid_->getGlobalVariables()); + auto newObject = objectGenerator_->newInstance(objectName, playerId, grid_); grid_->addObject(destinationLocation, newObject, true, action); return {}; }; @@ -671,29 +667,35 @@ C Object::getCommandArgument(BehaviourCommandArguments commandArguments, std::st } return commandArgumentIt->second.as(defaultValue); - } PathFinderConfig Object::configurePathFinder(YAML::Node searchNode, std::string actionName) { PathFinderConfig config; if (searchNode.IsDefined()) { + + spdlog::debug("Configuring path finder for action {0}", actionName); auto targetObjectNameNode = searchNode["TargetObjectName"]; if(targetObjectNameNode.IsDefined()) { auto targetObjectName = targetObjectNameNode.as(); + + spdlog::debug("Path finder target object: {0}", targetObjectName); + + spdlog::debug("Grid height: {0}", grid_->getHeight()); + // Just make the range really large so we always look in all cells auto range = std::max(grid_->getWidth(), grid_->getHeight()); config.collisionDetector = std::shared_ptr(new SpatialHashCollisionDetector(grid_->getWidth(), grid_->getHeight(), 10, range, TriggerType::RANGE_BOX_AREA)); if(config.collisionDetector != nullptr) { - grid_->addCollisionObjectName(targetObjectName, actionName); + grid_->addCollisionDetector({targetObjectName, objectName_}, actionName+"_search", config.collisionDetector); } } - auto impassableObjectsList = singleOrListNodeToList(searchNode["impassableObjects"]); + auto impassableObjectsList = singleOrListNodeToList(searchNode["ImpassableObjects"]); std::unordered_set impassableObjectsSet(impassableObjectsList.begin(), impassableObjectsList.end()); auto actionInputDefinitions = objectGenerator_->getActionInputDefinitions(); diff --git a/src/Griddly/Core/GDY/Objects/Object.hpp b/src/Griddly/Core/GDY/Objects/Object.hpp index d8817dd06..67a934684 100644 --- a/src/Griddly/Core/GDY/Objects/Object.hpp +++ b/src/Griddly/Core/GDY/Objects/Object.hpp @@ -67,7 +67,7 @@ enum class ActionExecutor { struct PathFinderConfig { std::shared_ptr pathFinder = nullptr; std::shared_ptr collisionDetector = nullptr; - glm::ivec2 endLocation; + glm::ivec2 endLocation{0,0}; uint32_t maxSearchDepth = 100; }; @@ -75,9 +75,9 @@ class Object : public std::enable_shared_from_this { public: virtual glm::ivec2 getLocation() const; - virtual void init(glm::ivec2 location, std::shared_ptr grid); + virtual void init(glm::ivec2 location); - virtual void init(glm::ivec2 location, DiscreteOrientation orientation, std::shared_ptr grid); + virtual void init(glm::ivec2 location, DiscreteOrientation orientation); virtual std::string getObjectName() const; @@ -119,7 +119,7 @@ class Object : public std::enable_shared_from_this { virtual std::vector> getInitialActions(std::shared_ptr originatingAction); virtual void setInitialActionDefinitions(std::vector actionDefinitions); - Object(std::string objectName, char mapCharacter, uint32_t playerId, uint32_t zIdx, std::unordered_map> availableVariables, std::shared_ptr objectGenerator); + Object(std::string objectName, char mapCharacter, uint32_t playerId, uint32_t zIdx, std::unordered_map> availableVariables, std::shared_ptr objectGenerator, std::shared_ptr grid); ~Object(); @@ -151,7 +151,7 @@ class Object : public std::enable_shared_from_this { // The variables that are available in the object for behaviour commands to interact with std::unordered_map> availableVariables_; - std::shared_ptr grid_; + const std::shared_ptr grid_; std::unordered_set availableActionNames_; diff --git a/src/Griddly/Core/GDY/Objects/ObjectGenerator.cpp b/src/Griddly/Core/GDY/Objects/ObjectGenerator.cpp index 1e0ebc50d..cf788e7de 100644 --- a/src/Griddly/Core/GDY/Objects/ObjectGenerator.cpp +++ b/src/Griddly/Core/GDY/Objects/ObjectGenerator.cpp @@ -5,6 +5,7 @@ #define SPDLOG_HEADER_ONLY #include +#include "../../Grid.hpp" #include "Object.hpp" namespace griddly { @@ -55,7 +56,7 @@ void ObjectGenerator::addInitialAction(std::string objectName, std::string actio objectDefinition->initialActionDefinitions.push_back({actionName, actionId, delay, randomize}); } -std::shared_ptr ObjectGenerator::cloneInstance(std::shared_ptr toClone, std::unordered_map>> globalVariables) { +std::shared_ptr ObjectGenerator::cloneInstance(std::shared_ptr toClone, std::shared_ptr grid) { auto objectName = toClone->getObjectName(); auto objectDefinition = getObjectDefinition(objectName); auto playerId = toClone->getPlayerId(); @@ -76,6 +77,8 @@ std::shared_ptr ObjectGenerator::cloneInstance(std::shared_ptr t availableVariables.insert({variableDefinitions.first, initializedVariable}); } + auto globalVariables = grid->getGlobalVariables(); + // Initialize global variables for (auto &globalVariable : globalVariables) { auto variableName = globalVariable.first; @@ -94,7 +97,7 @@ std::shared_ptr ObjectGenerator::cloneInstance(std::shared_ptr t auto objectZIdx = objectDefinition->zIdx; auto mapCharacter = objectDefinition->mapCharacter; - auto initializedObject = std::shared_ptr(new Object(objectName, mapCharacter, playerId, objectZIdx, availableVariables, shared_from_this())); + auto initializedObject = std::shared_ptr(new Object(objectName, mapCharacter, playerId, objectZIdx, availableVariables, shared_from_this(), grid)); if (objectName == avatarObject_) { initializedObject->markAsPlayerAvatar(); @@ -136,7 +139,7 @@ std::shared_ptr ObjectGenerator::cloneInstance(std::shared_ptr t return initializedObject; } -std::shared_ptr ObjectGenerator::newInstance(std::string objectName, uint32_t playerId, std::unordered_map>> globalVariables) { +std::shared_ptr ObjectGenerator::newInstance(std::string objectName, uint32_t playerId, std::shared_ptr grid) { spdlog::debug("Creating new object {0}.", objectName); auto objectDefinition = getObjectDefinition(objectName); @@ -157,6 +160,8 @@ std::shared_ptr ObjectGenerator::newInstance(std::string objectName, uin availableVariables.insert({variableDefinitions.first, initializedVariable}); } + auto globalVariables = grid->getGlobalVariables(); + // Initialize global variables for (auto &globalVariable : globalVariables) { auto variableName = globalVariable.first; @@ -174,7 +179,7 @@ std::shared_ptr ObjectGenerator::newInstance(std::string objectName, uin auto objectZIdx = objectDefinition->zIdx; auto mapCharacter = objectDefinition->mapCharacter; - auto initializedObject = std::shared_ptr(new Object(objectName, mapCharacter, playerId, objectZIdx, availableVariables, shared_from_this())); + auto initializedObject = std::shared_ptr(new Object(objectName, mapCharacter, playerId, objectZIdx, availableVariables, shared_from_this(), grid)); if (isAvatar) { initializedObject->markAsPlayerAvatar(); diff --git a/src/Griddly/Core/GDY/Objects/ObjectGenerator.hpp b/src/Griddly/Core/GDY/Objects/ObjectGenerator.hpp index f9cb2ec4b..988fd635b 100644 --- a/src/Griddly/Core/GDY/Objects/ObjectGenerator.hpp +++ b/src/Griddly/Core/GDY/Objects/ObjectGenerator.hpp @@ -8,6 +8,8 @@ namespace griddly { +class Grid; + enum class ActionBehaviourType { SOURCE, DESTINATION @@ -44,8 +46,8 @@ class ObjectGenerator : public std::enable_shared_from_this { virtual void defineActionBehaviour(std::string objectName, ActionBehaviourDefinition behaviourDefinition); virtual void addInitialAction(std::string objectName, std::string actionName, uint32_t actionId, uint32_t delay, bool randomize=false); - virtual std::shared_ptr newInstance(std::string objectName, uint32_t playerId, std::unordered_map>> globalVariables); - virtual std::shared_ptr cloneInstance(std::shared_ptr toClone, std::unordered_map>> globalVariables); + virtual std::shared_ptr newInstance(std::string objectName, uint32_t playerId, std::shared_ptr grid); + virtual std::shared_ptr cloneInstance(std::shared_ptr toClone, std::shared_ptr grid); virtual std::string& getObjectNameFromMapChar(char character); diff --git a/src/Griddly/Core/Grid.cpp b/src/Griddly/Core/Grid.cpp index f476f71ca..81b541969 100644 --- a/src/Griddly/Core/Grid.cpp +++ b/src/Griddly/Core/Grid.cpp @@ -485,31 +485,38 @@ void Grid::addActionProbability(std::string actionName, float probability) { actionProbabilities_[actionName] = probability; } -void Grid::addCollisionObjectName(std::string objectName, std::string actionName) { - collisionObjectActionNames_[objectName].insert(actionName); +void Grid::addCollisionDetector(std::vector objectNames, std::string actionName, std::shared_ptr collisionDetector) { + + for(auto objectName : objectNames) { + collisionObjectActionNames_[objectName].insert(actionName); + } + + collisionDetectors_.insert({actionName, collisionDetector}); } void Grid::addActionTrigger(std::string actionName, ActionTriggerDefinition actionTriggerDefinition) { std::shared_ptr collisionDetector = collisionDetectorFactory_->newCollisionDetector(width_, height_, actionTriggerDefinition); + std::vector objectNames; for (auto sourceObjectName : actionTriggerDefinition.sourceObjectNames) { - addCollisionObjectName(sourceObjectName, actionName); + objectNames.push_back(sourceObjectName); collisionSourceObjectActionNames_[sourceObjectName].insert(actionName); } for (auto destinationObjectName : actionTriggerDefinition.destinationObjectNames) { - addCollisionObjectName(destinationObjectName, actionName); + objectNames.push_back(destinationObjectName); collisionObjectActionNames_[destinationObjectName].insert(actionName); } actionTriggerDefinitions_.insert({actionName, actionTriggerDefinition}); - collisionDetectors_.insert({actionName, collisionDetector}); + + addCollisionDetector(objectNames, actionName, collisionDetector); } void Grid::addPlayerDefaultObject(std::shared_ptr object) { spdlog::debug("Adding default object for player {0}", object->getPlayerId()); - object->init({-1, -1}, shared_from_this()); + object->init({-1, -1}); defaultObject_[object->getPlayerId()] = object; } @@ -533,7 +540,7 @@ void Grid::addObject(glm::ivec2 location, std::shared_ptr object, bool a auto canAddObject = objects_.insert(object).second; if (canAddObject) { - object->init(location, shared_from_this()); + object->init(location); auto objectZIdx = object->getZIdx(); auto& objectsAtLocation = occupiedLocations_[location]; diff --git a/src/Griddly/Core/Grid.hpp b/src/Griddly/Core/Grid.hpp index 16bdaee7d..b9c4c6ae7 100644 --- a/src/Griddly/Core/Grid.hpp +++ b/src/Griddly/Core/Grid.hpp @@ -158,7 +158,7 @@ class Grid : public std::enable_shared_from_this { virtual const std::unordered_map>& getSourceObjectCollisionActionNames() const; virtual const std::unordered_map>& getObjectCollisionActionNames() const; - void addCollisionObjectName(std::string objectName, std::string actionName); + virtual void addCollisionDetector(std::vector objectNames, std::string actionName, std::shared_ptr collisionDetector); private: GridEvent buildGridEvent(std::shared_ptr action, uint32_t playerId, uint32_t tick); diff --git a/src/Griddly/Core/LevelGenerators/MapGenerator.cpp b/src/Griddly/Core/LevelGenerators/MapGenerator.cpp index d537e5ab3..f3dd10b77 100644 --- a/src/Griddly/Core/LevelGenerators/MapGenerator.cpp +++ b/src/Griddly/Core/LevelGenerators/MapGenerator.cpp @@ -35,7 +35,7 @@ void MapGenerator::reset(std::shared_ptr grid) { } for (auto playerId = 0; playerId < playerCount_ + 1; playerId++) { - auto defaultObject = objectGenerator_->newInstance("_empty", playerId, grid->getGlobalVariables()); + auto defaultObject = objectGenerator_->newInstance("_empty", playerId, grid); grid->addPlayerDefaultObject(defaultObject); } @@ -57,7 +57,7 @@ void MapGenerator::reset(std::shared_ptr grid) { auto playerId = objectData.playerId; spdlog::debug("Adding object {0} to environment at location ({1},{2})", objectName, location[0], location[1]); - auto object = objectGenerator_->newInstance(objectName, playerId, grid->getGlobalVariables()); + auto object = objectGenerator_->newInstance(objectName, playerId, grid); grid->addObject(location, object); } } diff --git a/src/Griddly/Core/TurnBasedGameProcess.cpp b/src/Griddly/Core/TurnBasedGameProcess.cpp index e47cb29f0..c19c1b5c2 100644 --- a/src/Griddly/Core/TurnBasedGameProcess.cpp +++ b/src/Griddly/Core/TurnBasedGameProcess.cpp @@ -118,7 +118,7 @@ std::shared_ptr TurnBasedGameProcess::clone() { // Adding player default objects for (auto playerId = 0; playerId < players_.size() + 1; playerId++) { - auto defaultObject = objectGenerator->newInstance("_empty", playerId, clonedGrid->getGlobalVariables()); + auto defaultObject = objectGenerator->newInstance("_empty", playerId, clonedGrid); clonedGrid->addPlayerDefaultObject(defaultObject); } @@ -127,7 +127,7 @@ std::shared_ptr TurnBasedGameProcess::clone() { auto& objectsToCopy = grid_->getObjects(); std::unordered_map, std::shared_ptr> clonedObjectMapping; for (const auto& toCopy : objectsToCopy) { - auto clonedObject = objectGenerator->cloneInstance(toCopy, clonedGrid->getGlobalVariables()); + auto clonedObject = objectGenerator->cloneInstance(toCopy, clonedGrid); clonedGrid->addObject(toCopy->getLocation(), clonedObject, false); // We need to know which objects are equivalent in the grid so we can diff --git a/tests/src/Griddly/Core/GDY/GDYFactoryTest.cpp b/tests/src/Griddly/Core/GDY/GDYFactoryTest.cpp index 60ce42a5a..8ec84dd0b 100644 --- a/tests/src/Griddly/Core/GDY/GDYFactoryTest.cpp +++ b/tests/src/Griddly/Core/GDY/GDYFactoryTest.cpp @@ -883,16 +883,16 @@ TEST(GDYFactoryTest, wallTest) { EXPECT_CALL(*mockObjectGeneratorPtr, getObjectNameFromMapChar(Eq('W'))) .WillRepeatedly(ReturnRef(wall16String)); - EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq("_empty"), Eq(1), _)) + EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq("_empty"), Eq(1), Eq(grid))) .WillRepeatedly(Return(mockDefaultObject)); - EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq("_empty"), Eq(0), _)) + EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq("_empty"), Eq(0), Eq(grid))) .WillRepeatedly(Return(mockDefaultObject)); - EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq(wall2String), Eq(0), _)) + EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq(wall2String), Eq(0), Eq(grid))) .WillRepeatedly(Return(mockWall2Object)); - EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq(wall16String), Eq(0), _)) + EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq(wall16String), Eq(0), Eq(grid))) .WillRepeatedly(Return(mockWall16Object)); gdyFactory->initializeFromFile("tests/resources/walls.yaml"); @@ -943,19 +943,19 @@ TEST(GDYFactoryTest, zIndexTest) { EXPECT_CALL(*mockObjectGeneratorPtr, getObjectNameFromMapChar(Eq('g'))) .WillRepeatedly(ReturnRef(floor)); - EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq("_empty"), Eq(1), _)) + EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq("_empty"), Eq(1), Eq(grid))) .WillRepeatedly(Return(mockDefaultObject)); - EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq("_empty"), Eq(0), _)) + EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq("_empty"), Eq(0), Eq(grid))) .WillRepeatedly(Return(mockDefaultObject)); - EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq(wall), Eq(0), _)) + EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq(wall), Eq(0), Eq(grid))) .WillRepeatedly(Return(mockWallObject)); - EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq(floor), Eq(0), _)) + EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq(floor), Eq(0), Eq(grid))) .WillRepeatedly(Return(mockFloorObject)); - EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq(ghost), Eq(0), _)) + EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq(ghost), Eq(0), Eq(grid))) .WillRepeatedly(Return(mockGhostObject)); gdyFactory->initializeFromFile("tests/resources/ztest.yaml"); diff --git a/tests/src/Griddly/Core/GDY/Objects/ObjectTest.cpp b/tests/src/Griddly/Core/GDY/Objects/ObjectTest.cpp index 056ea2dc3..68d7f7043 100644 --- a/tests/src/Griddly/Core/GDY/Objects/ObjectTest.cpp +++ b/tests/src/Griddly/Core/GDY/Objects/ObjectTest.cpp @@ -13,6 +13,7 @@ using ::testing::_; using ::testing::ElementsAre; +using ::testing::UnorderedElementsAre; using ::testing::Eq; using ::testing::Invoke; using ::testing::Mock; @@ -125,9 +126,9 @@ std::shared_ptr setupAction(std::string actionName, std::shared_ptr< TEST(ObjectTest, getLocation) { auto mockGridPtr = std::shared_ptr(new MockGrid()); - auto object = std::shared_ptr(new Object("object", 'o', 0, 0, {}, nullptr)); + auto object = std::shared_ptr(new Object("object", 'o', 0, 0, {}, nullptr, mockGridPtr)); - object->init({5, 5}, mockGridPtr); + object->init({5, 5}); ASSERT_EQ(object->getLocation(), glm::ivec2(5, 5)); @@ -136,7 +137,7 @@ TEST(ObjectTest, getLocation) { TEST(ObjectTest, getObjectName) { auto mockGridPtr = std::shared_ptr(new MockGrid()); - auto object = std::shared_ptr(new Object("object", 'o', 0, 0, {}, nullptr)); + auto object = std::shared_ptr(new Object("object", 'o', 0, 0, {}, nullptr, mockGridPtr)); ASSERT_EQ(object->getObjectName(), "object"); @@ -145,9 +146,9 @@ TEST(ObjectTest, getObjectName) { TEST(ObjectTest, getDescription) { auto mockGridPtr = std::shared_ptr(new MockGrid()); - auto object = std::shared_ptr(new Object("object", 'o', 0, 0, {}, nullptr)); + auto object = std::shared_ptr(new Object("object", 'o', 0, 0, {}, nullptr, mockGridPtr)); - object->init({9, 6}, mockGridPtr); + object->init({9, 6}); ASSERT_EQ(object->getDescription(), "object@[9, 6]"); @@ -156,9 +157,9 @@ TEST(ObjectTest, getDescription) { TEST(ObjectTest, getPlayerId) { auto mockGridPtr = std::shared_ptr(new MockGrid()); - auto object = std::shared_ptr(new Object("object", 'o', 2, 0, {}, nullptr)); + auto object = std::shared_ptr(new Object("object", 'o', 2, 0, {}, nullptr, mockGridPtr)); - object->init({5, 5}, mockGridPtr); + object->init({5, 5}); ASSERT_EQ(object->getPlayerId(), 2); @@ -167,11 +168,11 @@ TEST(ObjectTest, getPlayerId) { TEST(ObjectTest, getVariables) { auto mockGridPtr = std::shared_ptr(new MockGrid()); - auto object = std::shared_ptr(new Object("object", 'o', 2, 0, {{"test_param", _V(20)}}, nullptr)); + auto object = std::shared_ptr(new Object("object", 'o', 2, 0, {{"test_param", _V(20)}}, nullptr, mockGridPtr)); ASSERT_EQ(*object->getVariableValue("test_param"), 20); - object->init({5, 6}, mockGridPtr); + object->init({5, 6}); ASSERT_EQ(*object->getVariableValue("_x"), 5); ASSERT_EQ(*object->getVariableValue("_y"), 6); @@ -184,8 +185,8 @@ TEST(ObjectTest, getVariables) { TEST(ObjectTest, actionBoundToSrc) { auto srcObjectName = "srcObject"; auto dstObjectName = "dstObject"; - auto srcObject = std::shared_ptr(new Object(srcObjectName, 'S', 0, 0, {}, nullptr)); - auto dstObject = std::shared_ptr(new Object(dstObjectName, 'D', 0, 0, {}, nullptr)); + auto srcObject = std::shared_ptr(new Object(srcObjectName, 'S', 0, 0, {}, nullptr, nullptr)); + auto dstObject = std::shared_ptr(new Object(dstObjectName, 'D', 0, 0, {}, nullptr, nullptr)); auto mockActionPtr = setupAction("action", srcObject, dstObject); @@ -201,8 +202,8 @@ TEST(ObjectTest, actionBoundToSrc) { TEST(ObjectTest, actionBoundToDst) { auto srcObjectName = "srcObject"; auto dstObjectName = "dstObject"; - auto srcObject = std::shared_ptr(new Object(srcObjectName, 'S', 0, 0, {}, nullptr)); - auto dstObject = std::shared_ptr(new Object(dstObjectName, 'D', 0, 0, {}, nullptr)); + auto srcObject = std::shared_ptr(new Object(srcObjectName, 'S', 0, 0, {}, nullptr, nullptr)); + auto dstObject = std::shared_ptr(new Object(dstObjectName, 'D', 0, 0, {}, nullptr, nullptr)); auto mockActionPtr = setupAction("action", srcObject, dstObject); @@ -220,7 +221,7 @@ TEST(ObjectTest, actionBoundToDst) { TEST(ObjectTest, actionDestinationObjectDifferentFromOriginalObject) { auto srcObjectName = "srcObject"; auto dstObjectName = "dstObject"; - auto srcObject = std::shared_ptr(new Object(srcObjectName, 'S', 0, 0, {}, nullptr)); + auto srcObject = std::shared_ptr(new Object(srcObjectName, 'S', 0, 0, {}, nullptr, nullptr)); auto mockActionPtr = setupAction("action", srcObject, glm::ivec2{1, 1}); @@ -237,8 +238,8 @@ TEST(ObjectTest, actionDestinationObjectDifferentFromOriginalObject) { TEST(ObjectTest, srcActionNoBehaviourForDstObject) { auto srcObjectName = "srcObject"; auto dstObjectName = "dstObject"; - auto srcObject = std::shared_ptr(new Object(srcObjectName, 'S', 0, 0, {}, nullptr)); - auto dstObject = std::shared_ptr(new Object(dstObjectName, 'D', 0, 0, {}, nullptr)); + auto srcObject = std::shared_ptr(new Object(srcObjectName, 'S', 0, 0, {}, nullptr, nullptr)); + auto dstObject = std::shared_ptr(new Object(dstObjectName, 'D', 0, 0, {}, nullptr, nullptr)); auto mockActionPtr = setupAction("action", srcObject, dstObject); @@ -255,8 +256,8 @@ TEST(ObjectTest, srcActionNoBehaviourForDstObject) { TEST(ObjectTest, srcActionNoBehaviourForAction) { auto srcObjectName = "srcObject"; auto dstObjectName = "dstObject"; - auto srcObject = std::shared_ptr(new Object(srcObjectName, 'S', 0, 0, {}, nullptr)); - auto dstObject = std::shared_ptr(new Object(dstObjectName, 'D', 0, 0, {}, nullptr)); + auto srcObject = std::shared_ptr(new Object(srcObjectName, 'S', 0, 0, {}, nullptr, nullptr)); + auto dstObject = std::shared_ptr(new Object(dstObjectName, 'D', 0, 0, {}, nullptr, nullptr)); auto mockActionPtr = setupAction("action", srcObject, dstObject); @@ -270,8 +271,8 @@ TEST(ObjectTest, srcActionNoBehaviourForAction) { TEST(ObjectTest, dstActionNoBehaviourForDstObject) { auto srcObjectName = "srcObject"; auto dstObjectName = "dstObject"; - auto srcObject = std::shared_ptr(new Object(srcObjectName, 'S', 0, 0, {}, nullptr)); - auto dstObject = std::shared_ptr(new Object(dstObjectName, 'D', 0, 0, {}, nullptr)); + auto srcObject = std::shared_ptr(new Object(srcObjectName, 'S', 0, 0, {}, nullptr, nullptr)); + auto dstObject = std::shared_ptr(new Object(dstObjectName, 'D', 0, 0, {}, nullptr, nullptr)); auto mockActionPtr = setupAction("action", srcObject, dstObject); @@ -288,8 +289,8 @@ TEST(ObjectTest, dstActionNoBehaviourForDstObject) { TEST(ObjectTest, dstActionNoBehaviourForAction) { auto srcObjectName = "srcObject"; auto dstObjectName = "dstObject"; - auto srcObject = std::shared_ptr(new Object(srcObjectName, 'S', 0, 0, {}, nullptr)); - auto dstObject = std::shared_ptr(new Object(dstObjectName, 'D', 0, 0, {}, nullptr)); + auto srcObject = std::shared_ptr(new Object(srcObjectName, 'S', 0, 0, {}, nullptr, nullptr)); + auto dstObject = std::shared_ptr(new Object(dstObjectName, 'D', 0, 0, {}, nullptr, nullptr)); auto mockActionPtr = setupAction("action", srcObject, dstObject); @@ -307,10 +308,10 @@ struct CommandTestResult { }; std::shared_ptr setupObject(uint32_t playerId, std::string objectname, glm::ivec2 location, DiscreteOrientation orientation, std::unordered_map> initialVariables, std::shared_ptr mockGridPtr, std::shared_ptr objectGenerator) { - auto object = std::shared_ptr(new Object(objectname, 'o', playerId, 0, initialVariables, objectGenerator)); + auto object = std::shared_ptr(new Object(objectname, 'o', playerId, 0, initialVariables, objectGenerator, mockGridPtr)); if (mockGridPtr != nullptr) { - object->init(location, orientation, mockGridPtr); + object->init(location, orientation); EXPECT_CALL(*mockGridPtr, getObject(Eq(location))) .WillRepeatedly(Return(object)); @@ -394,7 +395,7 @@ MATCHER_P2(ActionListMatcher, actionName, numActions, "") { MATCHER_P4(SingletonDelayedActionVectorMatcher, actionName, delay, sourceObjectPtr, vectorToDest, "") { auto action = arg[0]; return arg.size() == 1 && - action->getDelay() == 10 && + action->getDelay() == delay && action->getActionName() == actionName && action->getSourceObject().get() == sourceObjectPtr.get() && action->getVectorToDest() == vectorToDest; @@ -1011,6 +1012,91 @@ TEST(ObjectTest, command_exec_randomize) { verifyMocks(mockActionPtr, mockGridPtr); } +TEST(ObjectTest, command_exec_search) { + //* - Src: + //* Object: srcObject + //* Commands: + //* - exec: + //* Action: exec_action + //* Delay: 20 + //* Search: + //* MaxDepth: 100 + //* TargetObjectName: search_object + //* Dst: + //* Object: dstObject + //* Commands: + //* - exec: + //* Action: exec_action + //* Search: + //* TargetLocation: [6, 7] + //* + auto mockObjectGenerator = std::shared_ptr(new MockObjectGenerator()); + auto mockGridPtr = mockGrid(); + auto srcObjectPtr = setupObject(1, "srcObject", glm::ivec2(0, 0), DiscreteOrientation(), {}, mockGridPtr, mockObjectGenerator); + auto dstObjectPtr = setupObject(1, "dstObject", glm::ivec2(5, 6), DiscreteOrientation(), {}, mockGridPtr, mockObjectGenerator); + + EXPECT_CALL(*mockGridPtr, getHeight()).WillRepeatedly(Return(100)); + EXPECT_CALL(*mockGridPtr, getWidth()).WillRepeatedly(Return(100)); + + auto searchObjectPtr = setupObject(1, "search_object", glm::ivec2(5, 0), DiscreteOrientation(), {}, mockGridPtr, mockObjectGenerator); + std::map> noObjects = {}; + std::map> searchObjectList = {{0, searchObjectPtr}}; + + ON_CALL(*mockGridPtr, getObjectsAt(_)).WillByDefault(ReturnRef(noObjects)); + EXPECT_CALL(*mockGridPtr, getObjectsAt(_)).WillRepeatedly(ReturnRef(noObjects)); + EXPECT_CALL(*mockGridPtr, getObjectsAt(Eq(glm::ivec2(5, 0)))).WillRepeatedly(ReturnRef(searchObjectList)); + + auto mockActionPtr = setupAction("do_exec", srcObjectPtr, dstObjectPtr); + std::unordered_map mockInputDefinitions{ + {"exec_action", {{ + {1, {{1, 0}, {0, 0}, ""}}, + {2, {{-1, 0}, {0, 0}, ""}}, + {3, {{1, 1}, {0, 0}, ""}}, + {4, {{0, -1}, {0, 0}, ""}}, + }, + false, + false}}}; + + auto invokeCollisionDetector = [this, searchObjectPtr](std::vector objectNames, std::string actionName, std::shared_ptr collisionDetector) -> void { + + ASSERT_EQ(objectNames, std::vector({"search_object", "srcObject"})); + ASSERT_EQ(actionName, "exec_action_search"); + + collisionDetector->upsert(searchObjectPtr); + }; + + + EXPECT_CALL(*mockGridPtr, addCollisionDetector).WillOnce(Invoke(invokeCollisionDetector)); + + + EXPECT_CALL(*mockObjectGenerator, getActionInputDefinitions()) + .Times(4) + .WillRepeatedly(Return(mockInputDefinitions)); + + EXPECT_CALL(*mockGridPtr, performActions(Eq(0), SingletonDelayedActionVectorMatcher("exec_action", 10, srcObjectPtr, glm::ivec2(1, 0)))) + .Times(1) + .WillOnce(Return(std::unordered_map{{1, 3}})); + + EXPECT_CALL(*mockGridPtr, performActions(Eq(0), SingletonDelayedActionVectorMatcher("exec_action", 0, dstObjectPtr, glm::ivec2(1, 1)))) + .Times(1) + .WillOnce(Return(std::unordered_map{{1, 3}})); + + YAML::Node searchNodeTargetObjectName; + YAML::Node searchNodeTargetLocation; + + searchNodeTargetObjectName["TargetObjectName"] = "search_object"; + searchNodeTargetLocation["TargetLocation"].push_back(6); + searchNodeTargetLocation["TargetLocation"].push_back(7); + + auto srcResult = addCommandsAndExecute(ActionBehaviourType::SOURCE, mockActionPtr, "exec", {{"Action", _Y("exec_action")}, {"Delay", _Y(10)}, {"Search", searchNodeTargetObjectName}}, srcObjectPtr, dstObjectPtr); + auto dstResult = addCommandsAndExecute(ActionBehaviourType::DESTINATION, mockActionPtr, "exec", {{"Action", _Y("exec_action")}, {"Search", searchNodeTargetLocation}}, srcObjectPtr, dstObjectPtr); + + verifyCommandResult(srcResult, false, {{1, 3}}); + verifyCommandResult(dstResult, false, {{1, 3}}); + + verifyMocks(mockActionPtr, mockGridPtr); +} + TEST(ObjectTest, command_remove) { //* - Src: //* Object: srcObject @@ -1069,10 +1155,10 @@ TEST(ObjectTest, command_change_to) { EXPECT_CALL(*mockGridPtr, getGlobalVariables) .WillRepeatedly(ReturnRef(globalVariables)); - EXPECT_CALL(*mockObjectGenerator, newInstance(Eq("newObject"), Eq(1), Eq(globalVariables))) + EXPECT_CALL(*mockObjectGenerator, newInstance(Eq("newObject"), Eq(1), Eq(mockGridPtr))) .WillOnce(Return(newObjectPtr)); - EXPECT_CALL(*mockObjectGenerator, newInstance(Eq("newObject"), Eq(2), Eq(globalVariables))) + EXPECT_CALL(*mockObjectGenerator, newInstance(Eq("newObject"), Eq(2), Eq(mockGridPtr))) .WillOnce(Return(newObjectPtr)); EXPECT_CALL(*mockGridPtr, removeObject(Eq(srcObjectPtr))) @@ -1146,16 +1232,12 @@ TEST(ObjectTest, command_spawn) { auto mockActionPtr = setupAction("action", srcObjectPtr, glm::ivec2(1, 0)); - EXPECT_CALL(*mockObjectGenerator, newInstance(Eq("newObject"), Eq(1), _)) + EXPECT_CALL(*mockObjectGenerator, newInstance(Eq("newObject"), Eq(1), Eq(mockGridPtr))) .WillOnce(Return(newObjectPtr)); EXPECT_CALL(*mockGridPtr, addObject(Eq(glm::ivec2(1, 0)), Eq(newObjectPtr), Eq(true), Eq(mockActionPtr))) .Times(1); - std::unordered_map>> globalVariables{}; - EXPECT_CALL(*mockGridPtr, getGlobalVariables) - .WillOnce(ReturnRef(globalVariables)); - auto srcResult = addCommandsAndExecute(ActionBehaviourType::SOURCE, mockActionPtr, "spawn", {{"0", _Y("newObject")}}, srcObjectPtr, nullptr); verifyCommandResult(srcResult, false, {}); @@ -1377,8 +1459,8 @@ TEST(ObjectTest, isValidAction) { auto srcObjectName = "srcObject"; auto dstObjectName = "dstObject"; auto actionName = "action"; - auto srcObject = std::shared_ptr(new Object(srcObjectName, 'S', 0, 0, {{"counter", _V(5)}}, nullptr)); - auto dstObject = std::shared_ptr(new Object(dstObjectName, 'D', 0, 0, {}, nullptr)); + auto srcObject = std::shared_ptr(new Object(srcObjectName, 'S', 0, 0, {{"counter", _V(5)}}, nullptr, nullptr)); + auto dstObject = std::shared_ptr(new Object(dstObjectName, 'D', 0, 0, {}, nullptr, nullptr)); auto mockActionPtr = setupAction(actionName, srcObject, dstObject); // auto mockActionPtr = std::shared_ptr(new MockAction()); @@ -1400,8 +1482,8 @@ TEST(ObjectTest, isValidActionNotDefinedForAction) { auto srcObjectName = "srcObject"; auto dstObjectName = "dstObject"; auto actionName = "action"; - auto srcObject = std::shared_ptr(new Object(srcObjectName, 'S', 0, 0, {{"counter", _V(5)}}, nullptr)); - auto dstObject = std::shared_ptr(new Object(dstObjectName, 'D', 0, 0, {}, nullptr)); + auto srcObject = std::shared_ptr(new Object(srcObjectName, 'S', 0, 0, {{"counter", _V(5)}}, nullptr, nullptr)); + auto dstObject = std::shared_ptr(new Object(dstObjectName, 'D', 0, 0, {}, nullptr, nullptr)); auto mockActionPtr = setupAction(actionName, srcObject, dstObject); @@ -1420,8 +1502,8 @@ TEST(ObjectTest, isValidActionNotDefinedForDestination) { auto srcObjectName = "srcObject"; auto dstObjectName = "dstObject"; auto actionName = "action"; - auto srcObject = std::shared_ptr(new Object(srcObjectName, 'S', 0, 0, {{"counter", _V(5)}}, nullptr)); - auto dstObject = std::shared_ptr(new Object(dstObjectName, 'D', 0, 0, {}, nullptr)); + auto srcObject = std::shared_ptr(new Object(srcObjectName, 'S', 0, 0, {{"counter", _V(5)}}, nullptr, nullptr)); + auto dstObject = std::shared_ptr(new Object(dstObjectName, 'D', 0, 0, {}, nullptr, nullptr)); auto mockActionPtr = setupAction(actionName, srcObject, dstObject); @@ -1448,15 +1530,15 @@ TEST(ObjectTest, isValidActionDestinationLocationOutsideGrid) { EXPECT_CALL(*mockGridPtr, getWidth).WillRepeatedly(Return(10)); EXPECT_CALL(*mockGridPtr, getHeight).WillRepeatedly(Return(10)); - auto srcObject = std::shared_ptr(new Object(srcObjectName, 'S', 0, 0, {{"counter", _V(5)}}, nullptr)); + auto srcObject = std::shared_ptr(new Object(srcObjectName, 'S', 0, 0, {{"counter", _V(5)}}, nullptr, mockGridPtr)); - auto dstObjectOutside = std::shared_ptr(new Object(dstBoundaryObjectName, 'S', 0, 0, {}, nullptr)); - auto dstObjectInside = std::shared_ptr(new Object(dstEmptyObjectName, 'D', 0, 0, {}, nullptr)); + auto dstObjectOutside = std::shared_ptr(new Object(dstBoundaryObjectName, 'S', 0, 0, {}, nullptr, mockGridPtr)); + auto dstObjectInside = std::shared_ptr(new Object(dstEmptyObjectName, 'D', 0, 0, {}, nullptr, mockGridPtr)); - srcObject->init({5, 4}, DiscreteOrientation(), mockGridPtr); + srcObject->init({5, 4}, DiscreteOrientation()); - dstObjectOutside->init({-1, -1}, DiscreteOrientation(), mockGridPtr); - dstObjectInside->init({5, 5}, DiscreteOrientation(), mockGridPtr); + dstObjectOutside->init({-1, -1}, DiscreteOrientation()); + dstObjectInside->init({5, 5}, DiscreteOrientation()); auto mockActionPtrOutside = setupAction(actionName, srcObject, dstObjectOutside); auto mockActionPtrValid = setupAction(actionName, srcObject, dstObjectInside); @@ -1513,7 +1595,7 @@ TEST(ObjectTest, getInitialActions) { EXPECT_CALL(*mockObjectGenerator, getActionInputDefinitions()).WillRepeatedly(Return(mockActionInputDefinitions)); - auto object = std::shared_ptr(new Object(objectName, 'S', 0, 0, {}, mockObjectGenerator)); + auto object = std::shared_ptr(new Object(objectName, 'S', 0, 0, {}, mockObjectGenerator, nullptr)); object->setInitialActionDefinitions(initialActionDefinitions); @@ -1588,7 +1670,7 @@ TEST(ObjectTest, getInitialActionsWithOriginatingAction) { EXPECT_CALL(*mockObjectGenerator, getActionInputDefinitions()).WillRepeatedly(Return(mockActionInputDefinitions)); - auto object = std::shared_ptr(new Object(objectName, 'S', 0, 0, {}, mockObjectGenerator)); + auto object = std::shared_ptr(new Object(objectName, 'S', 0, 0, {}, mockObjectGenerator, nullptr)); object->setInitialActionDefinitions(initialActionDefinitions); diff --git a/tests/src/Griddly/Core/LevelGenerator/MapReaderTest.cpp b/tests/src/Griddly/Core/LevelGenerator/MapReaderTest.cpp index 48da1964f..3a02e9c0e 100644 --- a/tests/src/Griddly/Core/LevelGenerator/MapReaderTest.cpp +++ b/tests/src/Griddly/Core/LevelGenerator/MapReaderTest.cpp @@ -62,19 +62,19 @@ TEST(MapGeneratorTest, testLoadStringWithPlayerObjects) { .Times(1) .WillRepeatedly(ReturnRef(avatarObjectName)); - EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq("_empty"), Eq(0), _)) + EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq("_empty"), Eq(0), Eq(mockGridPtr))) .Times(1) .WillRepeatedly(Return(mockDefaultObject)); - EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq("_empty"), Eq(1), _)) + EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq("_empty"), Eq(1), Eq(mockGridPtr))) .Times(1) .WillRepeatedly(Return(mockDefaultObject)); - EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq(wallObjectName), Eq(0), _)) + EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq(wallObjectName), Eq(0), Eq(mockGridPtr))) .Times(8) .WillRepeatedly(Return(mockWallObject)); - EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq(avatarObjectName), Eq(1), _)) + EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq(avatarObjectName), Eq(1), Eq(mockGridPtr))) .Times(1) .WillRepeatedly(Return(mockAvatarObject)); @@ -131,19 +131,19 @@ TEST(MapGeneratorTest, testLoadStringWithPlayerObjectsRandomWhitespace) { .Times(1) .WillRepeatedly(ReturnRef(avatarObjectName)); - EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq("_empty"), Eq(0), _)) + EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq("_empty"), Eq(0), Eq(mockGridPtr))) .Times(1) .WillRepeatedly(Return(mockDefaultObject)); - EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq("_empty"), Eq(1), _)) + EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq("_empty"), Eq(1), Eq(mockGridPtr))) .Times(1) .WillRepeatedly(Return(mockDefaultObject)); - EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq(wallObjectName), Eq(0), _)) + EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq(wallObjectName), Eq(0), Eq(mockGridPtr))) .Times(8) .WillRepeatedly(Return(mockWallObject)); - EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq(avatarObjectName), Eq(1), _)) + EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq(avatarObjectName), Eq(1), Eq(mockGridPtr))) .Times(1) .WillRepeatedly(Return(mockAvatarObject)); @@ -200,19 +200,19 @@ TEST(MapGeneratorTest, testLoadStringNoSpaces) { .Times(1) .WillRepeatedly(ReturnRef(avatarObjectName)); - EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq("_empty"), Eq(0), _)) + EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq("_empty"), Eq(0), Eq(mockGridPtr))) .Times(1) .WillRepeatedly(Return(mockDefaultObject)); - EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq("_empty"), Eq(1), _)) + EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq("_empty"), Eq(1), Eq(mockGridPtr))) .Times(1) .WillRepeatedly(Return(mockDefaultObject)); - EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq(wallObjectName), Eq(0), _)) + EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq(wallObjectName), Eq(0), Eq(mockGridPtr))) .Times(8) .WillRepeatedly(Return(mockWallObject)); - EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq(avatarObjectName), Eq(1), _)) + EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq(avatarObjectName), Eq(1), Eq(mockGridPtr))) .Times(1) .WillRepeatedly(Return(mockAvatarObject)); @@ -268,19 +268,19 @@ TEST(MapGeneratorTest, testLoadStringNoSpacesWithDots) { .Times(1) .WillRepeatedly(ReturnRef(avatarObjectName)); - EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq("_empty"), Eq(0), _)) + EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq("_empty"), Eq(0), Eq(mockGridPtr))) .Times(1) .WillRepeatedly(Return(mockDefaultObject)); - EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq("_empty"), Eq(1), _)) + EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq("_empty"), Eq(1), Eq(mockGridPtr))) .Times(1) .WillRepeatedly(Return(mockDefaultObject)); - EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq(wallObjectName), Eq(0), _)) + EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq(wallObjectName), Eq(0), Eq(mockGridPtr))) .Times(12) .WillRepeatedly(Return(mockWallObject)); - EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq(avatarObjectName), Eq(1), _)) + EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq(avatarObjectName), Eq(1), Eq(mockGridPtr))) .Times(1) .WillRepeatedly(Return(mockAvatarObject)); @@ -348,23 +348,23 @@ TEST(MapGeneratorTest, testLoadStringMultipleOccupants) { .Times(1) .WillRepeatedly(ReturnRef(floorObjectName)); - EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq("_empty"), Eq(0), _)) + EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq("_empty"), Eq(0), Eq(mockGridPtr))) .Times(1) .WillRepeatedly(Return(mockDefaultObject)); - EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq("_empty"), Eq(1), _)) + EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq("_empty"), Eq(1), Eq(mockGridPtr))) .Times(1) .WillRepeatedly(Return(mockDefaultObject)); - EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq(wallObjectName), Eq(0), _)) + EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq(wallObjectName), Eq(0), Eq(mockGridPtr))) .Times(12) .WillRepeatedly(Return(mockWallObject)); - EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq(avatarObjectName), Eq(1), _)) + EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq(avatarObjectName), Eq(1), Eq(mockGridPtr))) .Times(1) .WillRepeatedly(Return(mockAvatarObject)); - EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq(floorObjectName), Eq(0), _)) + EXPECT_CALL(*mockObjectGeneratorPtr, newInstance(Eq(floorObjectName), Eq(0), Eq(mockGridPtr))) .Times(1) .WillRepeatedly(Return(mockFloorObject)); diff --git a/tests/src/Mocks/Griddly/Core/GDY/Objects/MockObject.hpp b/tests/src/Mocks/Griddly/Core/GDY/Objects/MockObject.hpp index 1cfdf6bbb..dac8f24ba 100644 --- a/tests/src/Mocks/Griddly/Core/GDY/Objects/MockObject.hpp +++ b/tests/src/Mocks/Griddly/Core/GDY/Objects/MockObject.hpp @@ -8,10 +8,10 @@ namespace griddly { class MockObject : public Object { public: MockObject() - : Object("mockObject", 'o', 0, 0, {}, nullptr) { + : Object("mockObject", 'o', 0, 0, {}, nullptr, nullptr) { } - MOCK_METHOD(void, init, (glm::ivec2 location, std::shared_ptr grid_), ()); + MOCK_METHOD(void, init, (glm::ivec2 location), ()); MOCK_METHOD(uint32_t, getZIdx, (), (const)); MOCK_METHOD(glm::ivec2, getLocation, (), (const)); diff --git a/tests/src/Mocks/Griddly/Core/GDY/Objects/MockObjectGenerator.hpp b/tests/src/Mocks/Griddly/Core/GDY/Objects/MockObjectGenerator.hpp index e3c46ca2a..c153ef770 100644 --- a/tests/src/Mocks/Griddly/Core/GDY/Objects/MockObjectGenerator.hpp +++ b/tests/src/Mocks/Griddly/Core/GDY/Objects/MockObjectGenerator.hpp @@ -15,8 +15,8 @@ class MockObjectGenerator : public ObjectGenerator { MOCK_METHOD((std::unordered_map), getActionInputDefinitions, (), (const)); - MOCK_METHOD(std::shared_ptr, newInstance, (std::string objectName, uint32_t playerId, (std::unordered_map>> globalVariables)), ()); - MOCK_METHOD(std::shared_ptr, cloneInstance, (std::shared_ptr, (std::unordered_map>> globalVariables)), ()); + MOCK_METHOD(std::shared_ptr, newInstance, (std::string objectName, uint32_t playerId, std::shared_ptr grid), ()); + MOCK_METHOD(std::shared_ptr, cloneInstance, (std::shared_ptr, std::shared_ptr grid), ()); MOCK_METHOD(std::string&, getObjectNameFromMapChar, (char character), ()); MOCK_METHOD((std::unordered_map>), getObjectDefinitions, (), (const)); diff --git a/tests/src/Mocks/Griddly/Core/MockGrid.hpp b/tests/src/Mocks/Griddly/Core/MockGrid.hpp index 92d4445cd..a32903383 100644 --- a/tests/src/Mocks/Griddly/Core/MockGrid.hpp +++ b/tests/src/Mocks/Griddly/Core/MockGrid.hpp @@ -46,6 +46,8 @@ class MockGrid : public Grid { MOCK_METHOD((std::unordered_map>), getPlayerAvatarObjects, (), (const)); MOCK_METHOD(void, setPlayerCount, (int32_t), ()); + MOCK_METHOD(void, addCollisionDetector, (std::vector objectNames, std::string actionName, std::shared_ptr collisionDetector), ()); + MOCK_METHOD(std::shared_ptr, getTickCount, (), (const)); }; } // namespace griddly \ No newline at end of file From 64b55b561f92968d275b273f3653d19e1a308bbb Mon Sep 17 00:00:00 2001 From: Bam4d Date: Tue, 2 Nov 2021 12:49:05 +0000 Subject: [PATCH 10/20] more tests and creating examples --- .../AStar search/astar_opponent_environment.yaml | 7 +++++-- src/Griddly/Core/GDY/Objects/Object.cpp | 5 ++++- src/Griddly/Core/Grid.cpp | 3 ++- src/Griddly/Core/SpatialHashCollisionDetector.cpp | 11 +++++++++-- src/Griddly/Core/Util/util.hpp | 15 +++++++++++++++ tests/src/Griddly/Core/GDY/Objects/ObjectTest.cpp | 6 +----- tests/src/Griddly/Core/GridTest.cpp | 4 ++-- 7 files changed, 38 insertions(+), 13 deletions(-) diff --git a/python/examples/AStar search/astar_opponent_environment.yaml b/python/examples/AStar search/astar_opponent_environment.yaml index 56e5a25fd..f94ac5e47 100644 --- a/python/examples/AStar search/astar_opponent_environment.yaml +++ b/python/examples/AStar search/astar_opponent_environment.yaml @@ -9,6 +9,9 @@ Environment: IncludePlayerId: true Player: AvatarObject: catcher + Termination: + End: + - eq: [chatcher:count 0] Levels: - | W W W W W W W W W W W W W W W @@ -39,7 +42,7 @@ Actions: - mov: _dest - exec: Action: chase - Delay: 2 + Delay: 10 Search: ImpassableObjects: [wall] TargetObjectName: catcher @@ -51,7 +54,7 @@ Actions: Commands: - exec: Action: chase - Delay: 2 + Delay: 10 Search: ImpassableObjects: [ wall ] TargetObjectName: catcher diff --git a/src/Griddly/Core/GDY/Objects/Object.cpp b/src/Griddly/Core/GDY/Objects/Object.cpp index 3460a53dc..1e863ae3c 100644 --- a/src/Griddly/Core/GDY/Objects/Object.cpp +++ b/src/Griddly/Core/GDY/Objects/Object.cpp @@ -393,12 +393,15 @@ BehaviourFunction Object::instantiateBehaviour(std::string commandName, Behaviou auto searchResult = pathFinderConfig.collisionDetector->search(getLocation()); if (searchResult.objectSet.empty()) { + spdlog::debug("Cannot find target object for pathfinding!"); return {}; } endLocation = searchResult.closestObjects.at(0)->getLocation(); } + spdlog::debug("Searching for path from [{0},{1}] to [{2},{3}] using action {4}", getLocation().x, getLocation().y, endLocation.x, endLocation.y, actionName); + auto searchResult = pathFinderConfig.pathFinder->search(getLocation(), endLocation, getObjectOrientation().getUnitVector(), pathFinderConfig.maxSearchDepth); inputMapping = getInputMapping(actionName, searchResult.actionId, false, fallbackInputMapping); } else { @@ -691,7 +694,7 @@ PathFinderConfig Object::configurePathFinder(YAML::Node searchNode, std::string config.collisionDetector = std::shared_ptr(new SpatialHashCollisionDetector(grid_->getWidth(), grid_->getHeight(), 10, range, TriggerType::RANGE_BOX_AREA)); if(config.collisionDetector != nullptr) { - grid_->addCollisionDetector({targetObjectName, objectName_}, actionName+"_search", config.collisionDetector); + grid_->addCollisionDetector({targetObjectName}, actionName + generateRandomString(5), config.collisionDetector); } } diff --git a/src/Griddly/Core/Grid.cpp b/src/Griddly/Core/Grid.cpp index 81b541969..40dee59ab 100644 --- a/src/Griddly/Core/Grid.cpp +++ b/src/Griddly/Core/Grid.cpp @@ -499,7 +499,8 @@ void Grid::addActionTrigger(std::string actionName, ActionTriggerDefinition acti std::vector objectNames; for (auto sourceObjectName : actionTriggerDefinition.sourceObjectNames) { - objectNames.push_back(sourceObjectName); + // TODO: I dont think we need to add source names to all object names? + // objectNames.push_back(sourceObjectName); collisionSourceObjectActionNames_[sourceObjectName].insert(actionName); } diff --git a/src/Griddly/Core/SpatialHashCollisionDetector.cpp b/src/Griddly/Core/SpatialHashCollisionDetector.cpp index ac4eb28f9..497bcff6e 100644 --- a/src/Griddly/Core/SpatialHashCollisionDetector.cpp +++ b/src/Griddly/Core/SpatialHashCollisionDetector.cpp @@ -1,3 +1,4 @@ +#include #include "SpatialHashCollisionDetector.hpp" namespace griddly { @@ -16,6 +17,8 @@ void SpatialHashCollisionDetector::insert(std::shared_ptr object) { auto location = object->getLocation(); auto hash = calculateHash(location); + spdlog::debug("object at location [{0},{1}] added to hash [{2},{3}].", location.x, location.y, hash.x, hash.y); + if (buckets_.find(hash) == buckets_.end()) { buckets_.insert({hash, {object}}); } else { @@ -27,10 +30,13 @@ bool SpatialHashCollisionDetector::remove(std::shared_ptr object) { auto location = object->getLocation(); auto hash = calculateHash(location); auto bucketIt = buckets_.find(hash); + if (bucketIt == buckets_.end()) { return false; } + spdlog::debug("object at location [{0},{1}] removed from hash [{2},{3}].", location.x, location.y, hash.x, hash.y); + return bucketIt->second.erase(object) > 0; } @@ -56,8 +62,6 @@ SearchResult SpatialHashCollisionDetector::search(glm::ivec2 location) { std::vector> closestObjects; for (const auto& hash : hashes) { - spdlog::debug("object location ({0},{1})", location.x, location.y); - spdlog::debug("hash: ({0},{1})", hash.x, hash.y); auto objectSet = buckets_[hash]; switch (triggerType_) { @@ -86,6 +90,9 @@ SearchResult SpatialHashCollisionDetector::search(glm::ivec2 location) { } } break; + case TriggerType::NONE: + throw std::invalid_argument("Misconfigured collision detector!, specify 'RANGE_BOX_BOUNDARY' or 'RANGE_BOX_AREA' in configuration"); + break; } } diff --git a/src/Griddly/Core/Util/util.hpp b/src/Griddly/Core/Util/util.hpp index 43cf46c89..a7489c461 100644 --- a/src/Griddly/Core/Util/util.hpp +++ b/src/Griddly/Core/Util/util.hpp @@ -35,4 +35,19 @@ inline void accumulateRewards(std::unordered_map& acc, std::u for(auto valueIt : values) { acc[valueIt.first] += valueIt.second; } +} + +inline std::string generateRandomString(const int len) { + static const char alphanum[] = + "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + std::string tmp_s; + tmp_s.reserve(len); + + for (int i = 0; i < len; ++i) { + tmp_s += alphanum[rand() % (sizeof(alphanum) - 1)]; + } + + return tmp_s; } \ No newline at end of file diff --git a/tests/src/Griddly/Core/GDY/Objects/ObjectTest.cpp b/tests/src/Griddly/Core/GDY/Objects/ObjectTest.cpp index 68d7f7043..d0af6f930 100644 --- a/tests/src/Griddly/Core/GDY/Objects/ObjectTest.cpp +++ b/tests/src/Griddly/Core/GDY/Objects/ObjectTest.cpp @@ -1059,15 +1059,11 @@ TEST(ObjectTest, command_exec_search) { auto invokeCollisionDetector = [this, searchObjectPtr](std::vector objectNames, std::string actionName, std::shared_ptr collisionDetector) -> void { - ASSERT_EQ(objectNames, std::vector({"search_object", "srcObject"})); - ASSERT_EQ(actionName, "exec_action_search"); - + ASSERT_EQ(objectNames, std::vector({"search_object"})); collisionDetector->upsert(searchObjectPtr); }; - EXPECT_CALL(*mockGridPtr, addCollisionDetector).WillOnce(Invoke(invokeCollisionDetector)); - EXPECT_CALL(*mockObjectGenerator, getActionInputDefinitions()) .Times(4) diff --git a/tests/src/Griddly/Core/GridTest.cpp b/tests/src/Griddly/Core/GridTest.cpp index 46baaa53a..b33186c55 100644 --- a/tests/src/Griddly/Core/GridTest.cpp +++ b/tests/src/Griddly/Core/GridTest.cpp @@ -768,8 +768,8 @@ TEST(GridTest, intializeObjectWithCollisionDetection) { ASSERT_THAT(sourceObjectCollisionActionNames["object_2"], UnorderedElementsAre(actionName3)); ASSERT_THAT(sourceObjectCollisionActionNames["object_3"], UnorderedElementsAre(actionName2)); - ASSERT_EQ(objectCollisionActionNames.size(), 3); - ASSERT_THAT(objectCollisionActionNames["object_1"], UnorderedElementsAre(actionName1, actionName3)); + ASSERT_EQ(objectCollisionActionNames.size(), 2); + // ASSERT_THAT(objectCollisionActionNames["object_1"], UnorderedElementsAre(actionName1, actionName3)); ASSERT_THAT(objectCollisionActionNames["object_2"], UnorderedElementsAre(actionName1, actionName3)); ASSERT_THAT(objectCollisionActionNames["object_3"], UnorderedElementsAre(actionName2, actionName3)); } From 3c147f297300d82a02df8cd56f4d05bb384f742a Mon Sep 17 00:00:00 2001 From: Bam4d Date: Tue, 2 Nov 2021 13:07:56 +0000 Subject: [PATCH 11/20] another environment example with pathfinding but the object rotates --- python/examples/AStar search/astar.py | 8 +- .../astar_opponent_environment.yaml | 11 +- ...opponent_rotation_actions_environment.yaml | 163 ++++++++++++++++++ 3 files changed, 177 insertions(+), 5 deletions(-) create mode 100644 python/examples/AStar search/astar_opponent_rotation_actions_environment.yaml diff --git a/python/examples/AStar search/astar.py b/python/examples/AStar search/astar.py index b02acc29a..b63965876 100644 --- a/python/examples/AStar search/astar.py +++ b/python/examples/AStar search/astar.py @@ -10,7 +10,13 @@ current_path = os.path.dirname(os.path.realpath(__file__)) - env = GymWrapper('astar_opponent_environment.yaml', + # Uncommment to see normal actions (not rotated) being used + # env = GymWrapper('astar_opponent_environment.yaml', + # player_observer_type=gd.ObserverType.VECTOR, + # global_observer_type=gd.ObserverType.SPRITE_2D, + # level=0) + + env = GymWrapper('astar_opponent_rotation_actions_environment.yaml', player_observer_type=gd.ObserverType.VECTOR, global_observer_type=gd.ObserverType.SPRITE_2D, level=0) diff --git a/python/examples/AStar search/astar_opponent_environment.yaml b/python/examples/AStar search/astar_opponent_environment.yaml index f94ac5e47..f9b876385 100644 --- a/python/examples/AStar search/astar_opponent_environment.yaml +++ b/python/examples/AStar search/astar_opponent_environment.yaml @@ -1,8 +1,11 @@ Version: "0.1" Environment: - Name: Spider Throwing Fire - Description: The spider can now throw fireballs + Name: AStar Rotating Spider + Description: A simple example of setting up a mob that uses pathfinding to reach (and attack) the agent. Observers: + Sprite2D: + TileSize: 24 + BackgroundTile: gvgai/oryx/backLBrown.png Block2D: TileSize: 24 Vector: @@ -10,8 +13,8 @@ Environment: Player: AvatarObject: catcher Termination: - End: - - eq: [chatcher:count 0] + Lose: + - eq: [catcher:count, 0] Levels: - | W W W W W W W W W W W W W W W diff --git a/python/examples/AStar search/astar_opponent_rotation_actions_environment.yaml b/python/examples/AStar search/astar_opponent_rotation_actions_environment.yaml new file mode 100644 index 000000000..5dd72f4d8 --- /dev/null +++ b/python/examples/AStar search/astar_opponent_rotation_actions_environment.yaml @@ -0,0 +1,163 @@ +Version: "0.1" +Environment: + Name: AStar Rotating Spider + Description: A simple example of setting up a mob that uses pathfinding to reach (and attack) the agent using non-default action input mappings. + Observers: + Sprite2D: + TileSize: 24 + BackgroundTile: gvgai/oryx/backLBrown.png + Block2D: + TileSize: 24 + Vector: + IncludePlayerId: true + Player: + AvatarObject: catcher + Termination: + Lose: + - eq: [catcher:count, 0] + Levels: + - | + W W W W W W W W W W W W W W W + W . . . . . . . . . . . . . W + W . . . . . . W W W . W . . W + W . . . W W W W . . . W . . W + W . . W . . . . . . . W . . W + W . . W . . . W . . . W . . W + W . . W . W W W . . . W . . W + W . . W . . . W . . . W . . W + W . . W W . . W . . . W . . W + W g . W . . . W W W W W . . W + W W W W . W W . . . . W . . W + W . . . . . . . . . . W . . W + W . . . . . . . . . . W . . W + W c . . . . . . . . . W . s W + W W W W W W W W W W W W W W W + +Actions: + + - Name: chase + InputMapping: + Inputs: + 1: + Description: Rotate left + OrientationVector: [ -1, 0 ] + 2: + Description: Move forwards + OrientationVector: [ 0, -1 ] + VectorToDest: [ 0, -1 ] + 3: + Description: Rotate right + OrientationVector: [ 1, 0 ] + Relative: true + Internal: true + Behaviours: + - Src: + Object: spider + Commands: + - mov: _dest + - exec: + Action: chase + Delay: 10 + Search: + ImpassableObjects: [wall] + TargetObjectName: catcher + Dst: + Object: _empty + + - Src: + Object: spider + Commands: + - rot: _dir + - exec: + Action: chase + Delay: 0 + Search: + ImpassableObjects: [ wall ] + TargetObjectName: catcher + Dst: + Object: spider + + + - Src: + Object: spider + Dst: + Object: catcher + Commands: + - reward: -1 + - remove: true + + - Name: move + Behaviours: + - Src: + Object: catcher + Commands: + - mov: _dest + Dst: + Object: _empty + + - Src: + Object: catcher + Commands: + - reward: -1 + - remove: true + Dst: + Object: spider + +Objects: + - Name: spider + InitialActions: + - Action: chase + Delay: 2 + ActionId: 0 + MapCharacter: s + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/avatars/spider1.png + Block2D: + - Shape: triangle + Color: [ 0.9, 0.2, 0.2 ] + Scale: 1.0 + + - Name: catcher + MapCharacter: c + Observers: + Sprite2D: + - Image: gvgai/newset/girl5.png + Block2D: + - Color: [ 0.0, 0.8, 0.0 ] + Shape: square + + - Name: goal + MapCharacter: g + Observers: + Sprite2D: + - Image: gvgai/newset/exit2.png + Block2D: + - Color: [ 0.0, 0.0, 0.8 ] + Shape: square + + - Name: wall + MapCharacter: W + Observers: + Sprite2D: + - TilingMode: WALL_16 + Image: + - oryx/oryx_fantasy/wall2-0.png + - oryx/oryx_fantasy/wall2-1.png + - oryx/oryx_fantasy/wall2-2.png + - oryx/oryx_fantasy/wall2-3.png + - oryx/oryx_fantasy/wall2-4.png + - oryx/oryx_fantasy/wall2-5.png + - oryx/oryx_fantasy/wall2-6.png + - oryx/oryx_fantasy/wall2-7.png + - oryx/oryx_fantasy/wall2-8.png + - oryx/oryx_fantasy/wall2-9.png + - oryx/oryx_fantasy/wall2-10.png + - oryx/oryx_fantasy/wall2-11.png + - oryx/oryx_fantasy/wall2-12.png + - oryx/oryx_fantasy/wall2-13.png + - oryx/oryx_fantasy/wall2-14.png + - oryx/oryx_fantasy/wall2-15.png + Block2D: + - Color: [ 0.5, 0.5, 0.5 ] + Shape: square \ No newline at end of file From cf8b03ae14b0eeb311c4c95258d4be4eaaf4864f Mon Sep 17 00:00:00 2001 From: Bam4d Date: Tue, 2 Nov 2021 16:10:30 +0000 Subject: [PATCH 12/20] trying to fix compilation on mac --- src/Griddly/Core/AStarPathFinder.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Griddly/Core/AStarPathFinder.cpp b/src/Griddly/Core/AStarPathFinder.cpp index 9501709f7..3c6bd3b31 100644 --- a/src/Griddly/Core/AStarPathFinder.cpp +++ b/src/Griddly/Core/AStarPathFinder.cpp @@ -48,12 +48,12 @@ SearchOutput AStarPathFinder::search(glm::ivec2 startLocation, glm::ivec2 endLoc auto rotationMatrix = DiscreteOrientation(currentBestNode->orientationVector).getRotationMatrix(); for(auto& inputMapping : actionInputs_.inputMappings) { - auto actionId = inputMapping.first; - auto mapping = inputMapping.second; + const auto actionId = inputMapping.first; + const auto mapping = inputMapping.second; - auto vectorToDest = actionInputs_.relative ? mapping.vectorToDest * rotationMatrix: mapping.vectorToDest; - auto nextLocation = currentBestNode->location + vectorToDest; - auto nextOrientation = actionInputs_.relative ? mapping.orientationVector * rotationMatrix: mapping.orientationVector; + const auto vectorToDest = actionInputs_.relative ? mapping.vectorToDest * rotationMatrix: mapping.vectorToDest; + const auto nextLocation = currentBestNode->location + vectorToDest; + const auto nextOrientation = actionInputs_.relative ? mapping.orientationVector * rotationMatrix: mapping.orientationVector; if(nextLocation.y < 0 || nextLocation.y >= grid_->getHeight() || nextLocation.x < 0 || nextLocation.x >= grid_->getWidth()) { continue; @@ -62,8 +62,9 @@ SearchOutput AStarPathFinder::search(glm::ivec2 startLocation, glm::ivec2 endLoc // If this location is passable auto objectsAtNextLocation = grid_->getObjectsAt(nextLocation); bool passable = true; - for (auto object : objectsAtNextLocation) { - if(impassableObjects_.find(object.second->getObjectName()) != impassableObjects_.end()) { + for (const auto& object : objectsAtNextLocation) { + auto objectName = object.second->getObjectName(); + if(impassableObjects_.find(objectName) != impassableObjects_.end()) { passable = false; break; } From b849fb0cde24ec38216b3388492dc17c6341d99e Mon Sep 17 00:00:00 2001 From: Chris Bamford Date: Tue, 2 Nov 2021 20:28:22 +0000 Subject: [PATCH 13/20] for some reason this now builds on osx --- src/Griddly/Core/AStarPathFinder.cpp | 2 +- src/Griddly/Core/AStarPathFinder.hpp | 4 +--- src/Griddly/Core/GDY/Objects/Object.cpp | 2 +- src/Griddly/Core/PathFinder.cpp | 2 +- src/Griddly/Core/PathFinder.hpp | 6 +++--- 5 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Griddly/Core/AStarPathFinder.cpp b/src/Griddly/Core/AStarPathFinder.cpp index 3c6bd3b31..491ce9085 100644 --- a/src/Griddly/Core/AStarPathFinder.cpp +++ b/src/Griddly/Core/AStarPathFinder.cpp @@ -9,7 +9,7 @@ namespace griddly { -AStarPathFinder::AStarPathFinder(std::shared_ptr grid, std::unordered_set impassableObjects, ActionInputsDefinition actionInputs) +AStarPathFinder::AStarPathFinder(std::shared_ptr grid, std::set impassableObjects, ActionInputsDefinition actionInputs) : PathFinder(grid, impassableObjects), actionInputs_(actionInputs) { } diff --git a/src/Griddly/Core/AStarPathFinder.hpp b/src/Griddly/Core/AStarPathFinder.hpp index ae5de43e5..09afe96b7 100644 --- a/src/Griddly/Core/AStarPathFinder.hpp +++ b/src/Griddly/Core/AStarPathFinder.hpp @@ -1,7 +1,5 @@ #pragma once -#include - #include "PathFinder.hpp" #include "Grid.hpp" #include "Util/util.hpp" @@ -20,7 +18,7 @@ struct SortAStarPathNodes { class AStarPathFinder : public PathFinder { public: - AStarPathFinder(std::shared_ptr grid, std::unordered_set impassableObjects, ActionInputsDefinition actionInputs); + AStarPathFinder(std::shared_ptr grid, std::set impassableObjects, ActionInputsDefinition actionInputs); SearchOutput reconstructPath(std::shared_ptr currentBestNode); diff --git a/src/Griddly/Core/GDY/Objects/Object.cpp b/src/Griddly/Core/GDY/Objects/Object.cpp index 1e863ae3c..6d7aa39cb 100644 --- a/src/Griddly/Core/GDY/Objects/Object.cpp +++ b/src/Griddly/Core/GDY/Objects/Object.cpp @@ -700,7 +700,7 @@ PathFinderConfig Object::configurePathFinder(YAML::Node searchNode, std::string auto impassableObjectsList = singleOrListNodeToList(searchNode["ImpassableObjects"]); - std::unordered_set impassableObjectsSet(impassableObjectsList.begin(), impassableObjectsList.end()); + std::set impassableObjectsSet(impassableObjectsList.begin(), impassableObjectsList.end()); auto actionInputDefinitions = objectGenerator_->getActionInputDefinitions(); auto actionInputDefinitionIt = actionInputDefinitions.find(actionName); diff --git a/src/Griddly/Core/PathFinder.cpp b/src/Griddly/Core/PathFinder.cpp index 8aa60e092..5751eec96 100644 --- a/src/Griddly/Core/PathFinder.cpp +++ b/src/Griddly/Core/PathFinder.cpp @@ -3,7 +3,7 @@ namespace griddly { -PathFinder::PathFinder(std::shared_ptr grid, std::unordered_set impassableObjects) : +PathFinder::PathFinder(std::shared_ptr grid, std::set impassableObjects) : grid_(grid), impassableObjects_(impassableObjects) { } diff --git a/src/Griddly/Core/PathFinder.hpp b/src/Griddly/Core/PathFinder.hpp index 72c6a57f4..a77c9a12f 100644 --- a/src/Griddly/Core/PathFinder.hpp +++ b/src/Griddly/Core/PathFinder.hpp @@ -2,7 +2,7 @@ #include #include -#include +#include namespace griddly { @@ -14,13 +14,13 @@ struct SearchOutput { class PathFinder { public: - PathFinder(std::shared_ptr grid, std::unordered_set impassableObjects); + PathFinder(std::shared_ptr grid, std::set impassableObjects); virtual SearchOutput search(glm::ivec2 startLocation, glm::ivec2 endLocation, glm::ivec2 startOrientationVector, uint32_t maxDepth) = 0; protected: const std::shared_ptr grid_; - std::unordered_set impassableObjects_; + std::set impassableObjects_; }; } // namespace griddly \ No newline at end of file From 45abf2e9b2bc61b92ed5b03f2e458fa54b8e99e8 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Tue, 2 Nov 2021 21:17:08 +0000 Subject: [PATCH 14/20] include string here so it can compile --- src/Griddly/Core/PathFinder.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Griddly/Core/PathFinder.hpp b/src/Griddly/Core/PathFinder.hpp index a77c9a12f..fc7310139 100644 --- a/src/Griddly/Core/PathFinder.hpp +++ b/src/Griddly/Core/PathFinder.hpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace griddly { From 73464abb15365a706d2543447006c957ae44a810 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Wed, 3 Nov 2021 16:04:02 +0000 Subject: [PATCH 15/20] try to fix flakey test --- tests/src/Griddly/Core/AStarPathFinderTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/Griddly/Core/AStarPathFinderTest.cpp b/tests/src/Griddly/Core/AStarPathFinderTest.cpp index 816730dbe..7c3da8453 100644 --- a/tests/src/Griddly/Core/AStarPathFinderTest.cpp +++ b/tests/src/Griddly/Core/AStarPathFinderTest.cpp @@ -135,7 +135,7 @@ TEST(AStarPathFinderTest, searchRotationActions) { EXPECT_CALL(*mockGridPtr, getHeight).WillRepeatedly(Return(6)); EXPECT_CALL(*mockGridPtr, getWidth).WillRepeatedly(Return(6)); - auto up = pathFinder->search({0, 0}, {0, 5}, {0, 0}, 100); + auto up = pathFinder->search({0, 0}, {0, 5}, {1, 0}, 100); // Agent can rotate either way here to reach the goal. So to stop flakey tests, we rotate the agent so shortest path is specific. auto right = pathFinder->search({0, 0}, {5, 0}, {0, 0}, 100); auto down = pathFinder->search({0, 5}, {0, 0}, {0, 0}, 100); auto left = pathFinder->search({5, 0}, {0, 0}, {0, 0}, 100); From bf1cbd7dfa2307562826fb92460c619daa6ad731 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Wed, 3 Nov 2021 17:04:35 +0000 Subject: [PATCH 16/20] pushed state hashing into c++ for speed and accuracy --- bindings/wrapper/GameWrapper.cpp | 1 + .../clone_environments/clone_environments.py | 6 +-- python/tests/cloned_env_test.py | 8 +-- src/Griddly/Core/GameProcess.cpp | 29 +++++++++++ src/Griddly/Core/GameProcess.hpp | 50 +++++++++++++------ 5 files changed, 68 insertions(+), 26 deletions(-) diff --git a/bindings/wrapper/GameWrapper.cpp b/bindings/wrapper/GameWrapper.cpp index 67aed49b6..94e5912e8 100644 --- a/bindings/wrapper/GameWrapper.cpp +++ b/bindings/wrapper/GameWrapper.cpp @@ -324,6 +324,7 @@ class Py_GameWrapper { auto state = gameProcess_->getState(); py_state["GameTicks"] = state.gameTicks; + py_state["Hash"] = state.hash; py::dict py_globalVariables; for (auto varIt : state.globalVariables) { diff --git a/python/examples/clone_environments/clone_environments.py b/python/examples/clone_environments/clone_environments.py index f22e1b6f6..38b5caab0 100644 --- a/python/examples/clone_environments/clone_environments.py +++ b/python/examples/clone_environments/clone_environments.py @@ -37,11 +37,7 @@ env_state = env.get_state() cloned_state = clone_env.get_state() - env_state_hasher = StateHasher(env_state) - cloned_state_hasher = StateHasher(cloned_state) - - env_state_hash = env_state_hasher.hash() - cloned_state_hash = cloned_state_hasher.hash() + assert env_state['Hash'] == cloned_state['Hash'] if done and c_done: env.reset() diff --git a/python/tests/cloned_env_test.py b/python/tests/cloned_env_test.py index 14f77f324..88786ba9c 100644 --- a/python/tests/cloned_env_test.py +++ b/python/tests/cloned_env_test.py @@ -58,13 +58,7 @@ def test_random_trajectory_states(test_name): env_state = env.get_state() cloned_state = clone_env.get_state() - env_state_hasher = StateHasher(env_state) - cloned_state_hasher = StateHasher(cloned_state) - - env_state_hash = env_state_hasher.hash() - cloned_state_hash = cloned_state_hasher.hash() - - assert env_state_hash == cloned_state_hash + assert env_state['Hash'] == cloned_state['Hash'] if done and c_done: env.reset() diff --git a/src/Griddly/Core/GameProcess.cpp b/src/Griddly/Core/GameProcess.cpp index 4cec1f183..caae433ea 100644 --- a/src/Griddly/Core/GameProcess.cpp +++ b/src/Griddly/Core/GameProcess.cpp @@ -293,6 +293,33 @@ std::vector GameProcess::getAvailableActionIdsAtLocation(glm::ivec2 lo return availableActionIds; } +void GameProcess::generateStateHash(StateInfo& stateInfo) const { + + // Hash global variables + for (auto variableIt : stateInfo.globalVariables) { + hash_combine(stateInfo.hash, variableIt.first); + for (auto playerVariableIt : variableIt.second) { + hash_combine(stateInfo.hash, playerVariableIt.second); + hash_combine(stateInfo.hash, playerVariableIt.first); + } + } + + // Hash ordered object list + std::sort(stateInfo.objectInfo.begin(), stateInfo.objectInfo.end(), SortObjectInfo()); + for(auto o : stateInfo.objectInfo) { + hash_combine(stateInfo.hash, o.name); + hash_combine(stateInfo.hash, o.location); + hash_combine(stateInfo.hash, o.orientation.getUnitVector()); + hash_combine(stateInfo.hash, o.playerId); + + // Hash the object variables + for(auto variableIt : o.variables) { + hash_combine(stateInfo.hash, variableIt.first); + hash_combine(stateInfo.hash, variableIt.second); + } + } +} + StateInfo GameProcess::getState() const { StateInfo stateInfo; @@ -325,6 +352,8 @@ StateInfo GameProcess::getState() const { stateInfo.objectInfo.push_back(objectInfo); } + generateStateHash(stateInfo); + return stateInfo; } diff --git a/src/Griddly/Core/GameProcess.hpp b/src/Griddly/Core/GameProcess.hpp index 9fda11877..79918016a 100644 --- a/src/Griddly/Core/GameProcess.hpp +++ b/src/Griddly/Core/GameProcess.hpp @@ -19,25 +19,45 @@ struct ActionResult { struct ObjectInfo { std::string name; - std::unordered_map variables; + std::map variables; glm::ivec2 location; DiscreteOrientation orientation; uint8_t playerId; + +}; + +struct SortObjectInfo { + inline bool operator()(const ObjectInfo& a, const ObjectInfo& b) { + bool lt = false; + auto loca = 10000*a.location.x + a.location.y; + auto locb = 10000*b.location.x + b.location.y; + + if(loca == locb) { + return a.name <= b.name; + } else { + return loca< locb; + } + } }; struct StateInfo { int gameTicks; - std::unordered_map> globalVariables; + size_t hash; + std::map> globalVariables; std::vector objectInfo; }; class GameProcess : public std::enable_shared_from_this { public: - GameProcess(ObserverType globalObserverType, std::shared_ptr gdyFactory, std::shared_ptr grid); + GameProcess(ObserverType globalObserverType, + std::shared_ptr gdyFactory, + std::shared_ptr grid); virtual uint8_t* observe() const; - virtual ActionResult performActions(uint32_t playerId, std::vector> actions, bool updateTicks=true) = 0; + virtual ActionResult performActions( + uint32_t playerId, std::vector> actions, + bool updateTicks = true) = 0; virtual void addPlayer(std::shared_ptr player); @@ -47,7 +67,7 @@ class GameProcess : public std::enable_shared_from_this { // Use a custom level string virtual void setLevel(std::string levelString); - virtual void init(bool isCloned=false); + virtual void init(bool isCloned = false); virtual void reset(); @@ -60,9 +80,11 @@ class GameProcess : public std::enable_shared_from_this { std::shared_ptr getGrid(); std::shared_ptr getObserver(); - virtual std::unordered_map> getAvailableActionNames(uint32_t playerId) const; + virtual std::unordered_map> + getAvailableActionNames(uint32_t playerId) const; - virtual std::vector getAvailableActionIdsAtLocation(glm::ivec2 location, std::string actionName) const; + virtual std::vector getAvailableActionIdsAtLocation( + glm::ivec2 location, std::string actionName) const; virtual StateInfo getState() const; @@ -73,9 +95,8 @@ class GameProcess : public std::enable_shared_from_this { virtual ~GameProcess() = 0; protected: - - - virtual void setLevelGenerator(std::shared_ptr levelGenerator); + virtual void setLevelGenerator( + std::shared_ptr levelGenerator); virtual std::shared_ptr getLevelGenerator() const; std::vector> players_; @@ -83,7 +104,8 @@ class GameProcess : public std::enable_shared_from_this { std::shared_ptr gdyFactory_; std::shared_ptr terminationHandler_; - // Game process can have its own observer so we can monitor the game regardless of the player + // Game process can have its own observer so we can monitor the game + // regardless of the player ObserverType globalObserverType_; std::shared_ptr observer_; @@ -95,16 +117,16 @@ class GameProcess : public std::enable_shared_from_this { // Should the game process reset itself or rely on external reset bool autoReset_ = false; - // track whether this environment has finished or not, if it requires a reset, we can reset it + // track whether this environment has finished or not, if it requires a reset, + // we can reset it bool requiresReset_ = true; // Tracks the rewards currently accumulated per player std::unordered_map accumulatedRewards_; private: + void generateStateHash(StateInfo& stateInfo) const; void resetObservers(); ObserverConfig getObserverConfig(ObserverType observerType) const; - - }; } // namespace griddly \ No newline at end of file From 8827329016b34a415dd969ed04d6647720b8d544 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Wed, 3 Nov 2021 17:06:06 +0000 Subject: [PATCH 17/20] removed old state hasher code --- .../clone_environments/clone_environments.py | 4 --- python/griddly/util/state_hash.py | 27 ------------------- python/tests/cloned_env_test.py | 3 +-- 3 files changed, 1 insertion(+), 33 deletions(-) delete mode 100644 python/griddly/util/state_hash.py diff --git a/python/examples/clone_environments/clone_environments.py b/python/examples/clone_environments/clone_environments.py index 38b5caab0..b1dd2d4a4 100644 --- a/python/examples/clone_environments/clone_environments.py +++ b/python/examples/clone_environments/clone_environments.py @@ -1,9 +1,5 @@ import gym -import griddly import numpy as np -import json - -from griddly.util.state_hash import StateHasher env = gym.make('GDY-Sokoban-v0') env.reset() diff --git a/python/griddly/util/state_hash.py b/python/griddly/util/state_hash.py deleted file mode 100644 index d2c195d27..000000000 --- a/python/griddly/util/state_hash.py +++ /dev/null @@ -1,27 +0,0 @@ -from collections import OrderedDict - - -class StateHasher(): - - def __init__(self, state): - self._state = state - self._hash = None - - def hash(self): - if self._hash is None: - self._hash = hash(self._state['GameTicks']) - self._hash ^= hash(frozenset(self._state['GlobalVariables'])) - objects = self._state['Objects'] - sorted_objects = sorted(objects, key=lambda o: (o['Location'][0] * 100 + o['Location'][1], o['Name'])) - - ordered_keys_objects = [] - for o in sorted_objects: - o['Variables'] = frozenset(o['Variables']) - ordered_keys_objects.append(frozenset(o)) - - self._hash ^= hash(frozenset(ordered_keys_objects)) - - return self._hash - - def get_state(self): - return self._state diff --git a/python/tests/cloned_env_test.py b/python/tests/cloned_env_test.py index 88786ba9c..7ec54c760 100644 --- a/python/tests/cloned_env_test.py +++ b/python/tests/cloned_env_test.py @@ -1,9 +1,8 @@ -import gym import numpy as np import pytest +import gym from griddly import gd -from griddly.util.state_hash import StateHasher @pytest.fixture From f8697e4a0971337b0b2803fe4242355d904d27b7 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Wed, 3 Nov 2021 17:29:37 +0000 Subject: [PATCH 18/20] trying to find out why tests fail --- python/tests/cloned_env_test.py | 4 ++-- src/Griddly/Core/GameProcess.hpp | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/python/tests/cloned_env_test.py b/python/tests/cloned_env_test.py index 7ec54c760..c0da08ccc 100644 --- a/python/tests/cloned_env_test.py +++ b/python/tests/cloned_env_test.py @@ -44,7 +44,7 @@ def test_random_trajectory_states(test_name): clone_env = env.clone() # Create a bunch of steps and test in both environments - actions = [env.action_space.sample() for _ in range(100)] + actions = [env.action_space.sample() for _ in range(1000)] for action in actions: obs, reward, done, info = env.step(action) @@ -57,7 +57,7 @@ def test_random_trajectory_states(test_name): env_state = env.get_state() cloned_state = clone_env.get_state() - assert env_state['Hash'] == cloned_state['Hash'] + assert env_state['Hash'] == cloned_state['Hash'], f'state: {env_state}, cloned: {cloned_state}' if done and c_done: env.reset() diff --git a/src/Griddly/Core/GameProcess.hpp b/src/Griddly/Core/GameProcess.hpp index 79918016a..671bc15e6 100644 --- a/src/Griddly/Core/GameProcess.hpp +++ b/src/Griddly/Core/GameProcess.hpp @@ -28,14 +28,13 @@ struct ObjectInfo { struct SortObjectInfo { inline bool operator()(const ObjectInfo& a, const ObjectInfo& b) { - bool lt = false; auto loca = 10000*a.location.x + a.location.y; auto locb = 10000*b.location.x + b.location.y; if(loca == locb) { - return a.name <= b.name; + return a.name < b.name; } else { - return loca< locb; + return loca < locb; } } }; From 4e4e0a3882ed22685199933f59b547dc62ac1993 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Thu, 4 Nov 2021 09:06:35 +0000 Subject: [PATCH 19/20] make sure the seed hash is initialized --- src/Griddly/Core/GameProcess.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Griddly/Core/GameProcess.hpp b/src/Griddly/Core/GameProcess.hpp index 671bc15e6..8a5b1579f 100644 --- a/src/Griddly/Core/GameProcess.hpp +++ b/src/Griddly/Core/GameProcess.hpp @@ -41,7 +41,7 @@ struct SortObjectInfo { struct StateInfo { int gameTicks; - size_t hash; + size_t hash = 0; std::map> globalVariables; std::vector objectInfo; }; From b63961e37452dfe4b991eed63d484666f1b9570a Mon Sep 17 00:00:00 2001 From: Bam4d Date: Thu, 4 Nov 2021 10:20:35 +0000 Subject: [PATCH 20/20] bumping version numbers --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- CMakeLists.txt | 2 +- bindings/python.cpp | 2 +- docs/conf.py | 2 +- python/setup.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 256aba9e1..82278d72f 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -24,7 +24,7 @@ If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. mac/linux/windows] - - Version [e.g. 1.2.11] + - Version [e.g. 1.2.12] **Additional context** Add any other context about the problem here. diff --git a/CMakeLists.txt b/CMakeLists.txt index c69a37486..f1e27290d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10.0) -project(Griddly VERSION 1.2.11) +project(Griddly VERSION 1.2.12) set(BINARY ${CMAKE_PROJECT_NAME}) diff --git a/bindings/python.cpp b/bindings/python.cpp index 504470234..92f98ecc9 100644 --- a/bindings/python.cpp +++ b/bindings/python.cpp @@ -12,7 +12,7 @@ namespace griddly { PYBIND11_MODULE(python_griddly, m) { m.doc() = "Griddly python bindings"; - m.attr("version") = "1.2.11"; + m.attr("version") = "1.2.12"; #ifndef NDEBUG spdlog::set_level(spdlog::level::debug); diff --git a/docs/conf.py b/docs/conf.py index 080cf174a..cfab91a0b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -22,7 +22,7 @@ author = 'Chris Bamford' # The full version, including alpha/beta/rc tags -release = '1.2.11' +release = '1.2.12' # -- General configuration --------------------------------------------------- diff --git a/python/setup.py b/python/setup.py index d151a9aa3..194cae74b 100644 --- a/python/setup.py +++ b/python/setup.py @@ -71,7 +71,7 @@ def griddly_package_data(config='Debug'): setup( name='griddly', - version="1.2.11", + version="1.2.12", author_email="chrisbam4d@gmail.com", description="Griddly Python Libraries", long_description=long_description,