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/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/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/examples/AStar search/astar.py b/python/examples/AStar search/astar.py new file mode 100644 index 000000000..b63965876 --- /dev/null +++ b/python/examples/AStar search/astar.py @@ -0,0 +1,42 @@ +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__)) + + # 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) + + 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..f9b876385 --- /dev/null +++ b/python/examples/AStar search/astar_opponent_environment.yaml @@ -0,0 +1,150 @@ +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. + 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: + 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: + - exec: + Action: chase + Delay: 10 + 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/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 diff --git a/python/examples/clone_environments/clone_environments.py b/python/examples/clone_environments/clone_environments.py index f22e1b6f6..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() @@ -37,11 +33,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/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/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, diff --git a/python/tests/cloned_env_test.py b/python/tests/cloned_env_test.py index 14f77f324..c0da08ccc 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 @@ -45,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) @@ -58,13 +57,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'], f'state: {env_state}, cloned: {cloned_state}' if done and c_done: env.reset() diff --git a/src/Griddly/Core/AStarPathFinder.cpp b/src/Griddly/Core/AStarPathFinder.cpp new file mode 100644 index 000000000..491ce9085 --- /dev/null +++ b/src/Griddly/Core/AStarPathFinder.cpp @@ -0,0 +1,112 @@ +#include "AStarPathFinder.hpp" +#include +#include +#include + +#include "Grid.hpp" +#include "GDY/Objects/Object.hpp" +#include "GDY/Actions/Action.hpp" + +namespace griddly { + +AStarPathFinder::AStarPathFinder(std::shared_ptr grid, std::set impassableObjects, ActionInputsDefinition actionInputs) + : PathFinder(grid, impassableObjects), actionInputs_(actionInputs) { +} + +SearchOutput AStarPathFinder::reconstructPath(std::shared_ptr currentBestNode) { + 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, glm::ivec2 startOrientationVector, uint32_t maxDepth) { + + std::priority_queue, std::vector>, SortAStarPathNodes> orderedBestNodes; + std::unordered_map> nodes; + + auto startNode = std::make_shared(AStarPathNode(startLocation, startOrientationVector)); + startNode->scoreFromStart = glm::distance((glm::vec2)endLocation, (glm::vec2)startLocation); + startNode->scoreToGoal = 0; + orderedBestNodes.push(startNode); + + while(!orderedBestNodes.empty()) { + + auto currentBestNode = orderedBestNodes.top(); + + orderedBestNodes.pop(); + + 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) { + const auto actionId = inputMapping.first; + const auto mapping = inputMapping.second; + + 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; + } + + // If this location is passable + auto objectsAtNextLocation = grid_->getObjectsAt(nextLocation); + bool passable = true; + for (const auto& object : objectsAtNextLocation) { + auto objectName = object.second->getObjectName(); + if(impassableObjects_.find(objectName) != impassableObjects_.end()) { + passable = false; + break; + } + } + + if(passable) { + std::shared_ptr neighbourNode; + + auto nodeKey = glm::ivec4(nextLocation, nextOrientation); + + if(nodes.find(nodeKey) != nodes.end()) { + neighbourNode = nodes.at(nodeKey); + } else { + 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 + 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}, action: {4}", nextLocation.x, nextLocation.y, neighbourNode->scoreToGoal, neighbourNode->scoreFromStart, actionId); + orderedBestNodes.push(neighbourNode); + + } + } + + } + } + + return SearchOutput(); + +} + + +} // 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..09afe96b7 --- /dev/null +++ b/src/Griddly/Core/AStarPathFinder.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include "PathFinder.hpp" +#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; + }; +}; + + +class AStarPathFinder : public PathFinder { + public: + AStarPathFinder(std::shared_ptr grid, std::set impassableObjects, ActionInputsDefinition actionInputs); + + SearchOutput reconstructPath(std::shared_ptr currentBestNode); + + 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 new file mode 100644 index 000000000..e6758471a --- /dev/null +++ b/src/Griddly/Core/AStarPathNode.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include + +namespace griddly { +class AStarPathNode { + public: + + AStarPathNode(glm::ivec2 nodeLocation, glm::ivec2 nodeOrientationVector) + : location(nodeLocation), orientationVector(nodeOrientationVector) { + } + + 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/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/GDYFactory.cpp b/src/Griddly/Core/GDY/GDYFactory.cpp index cff8caae2..ccdb970bc 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() @@ -560,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; } @@ -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..6d7aa39cb 100644 --- a/src/Griddly/Core/GDY/Objects/Object.cpp +++ b/src/Griddly/Core/GDY/Objects/Object.cpp @@ -6,11 +6,13 @@ #include "../../Util/util.hpp" #include "../Actions/Action.hpp" #include "ObjectGenerator.hpp" +#include "../../AStarPathFinder.hpp" +#include "../../SpatialHashCollisionDetector.hpp" 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_}); @@ -23,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 { @@ -233,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 {}; @@ -367,21 +367,46 @@ 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); 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()) { + 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 { + inputMapping = getInputMapping(actionName, actionId, randomize, fallbackInputMapping); + } if (inputMapping.mappedToGrid) { inputMapping.vectorToDest = inputMapping.destinationLocation - getLocation(); @@ -436,7 +461,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 {}; }; @@ -637,6 +662,61 @@ 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()) { + + 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_->addCollisionDetector({targetObjectName}, actionName + generateRandomString(5), config.collisionDetector); + } + } + + auto impassableObjectsList = singleOrListNodeToList(searchNode["ImpassableObjects"]); + + std::set impassableObjectsSet(impassableObjectsList.begin(), impassableObjectsList.end()); + auto actionInputDefinitions = objectGenerator_->getActionInputDefinitions(); + auto actionInputDefinitionIt = actionInputDefinitions.find(actionName); + + 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; +} + 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 e27bb50f6..67a934684 100644 --- a/src/Griddly/Core/GDY/Objects/Object.hpp +++ b/src/Griddly/Core/GDY/Objects/Object.hpp @@ -12,6 +12,8 @@ #include "../Actions/Direction.hpp" #include "ObjectVariable.hpp" +#include "../YAMLUtils.hpp" + #define BehaviourCommandArguments std::unordered_map #define BehaviourFunction std::function)> @@ -24,6 +26,8 @@ class Grid; class Action; class ObjectGenerator; class InputMapping; +class PathFinder; +class CollisionDetector; struct InitialActionDefinition { std::string actionName; @@ -60,13 +64,20 @@ enum class ActionExecutor { OBJECT_PLAYER_ID, }; +struct PathFinderConfig { + std::shared_ptr pathFinder = nullptr; + std::shared_ptr collisionDetector = nullptr; + glm::ivec2 endLocation{0,0}; + uint32_t maxSearchDepth = 100; +}; + 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; @@ -108,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(); @@ -140,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_; @@ -154,6 +165,11 @@ class Object : public std::enable_shared_from_this { SingleInputMapping getInputMapping(std::string actionName, uint32_t actionId, bool randomize, InputMapping fallback); + 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); PreconditionFunction instantiatePrecondition(std::string commandName, BehaviourCommandArguments commandArguments); 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/GDY/YAMLUtils.hpp b/src/Griddly/Core/GDY/YAMLUtils.hpp new file mode 100644 index 000000000..8e9879156 --- /dev/null +++ b/src/Griddly/Core/GDY/YAMLUtils.hpp @@ -0,0 +1,37 @@ +#pragma once +#include + +#include + +#define BehaviourCommandArguments std::unordered_map + +namespace griddly { + +template +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/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..8a5b1579f 100644 --- a/src/Griddly/Core/GameProcess.hpp +++ b/src/Griddly/Core/GameProcess.hpp @@ -19,25 +19,44 @@ 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) { + 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 = 0; + 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 +66,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 +79,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 +94,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 +103,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 +116,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 diff --git a/src/Griddly/Core/Grid.cpp b/src/Griddly/Core/Grid.cpp index 6f6e2d063..40dee59ab 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,26 +485,39 @@ void Grid::addActionProbability(std::string actionName, float probability) { actionProbabilities_[actionName] = probability; } +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) { - collisionObjectActionNames_[sourceObjectName].insert(actionName); + // TODO: I dont think we need to add source names to all object names? + // objectNames.push_back(sourceObjectName); collisionSourceObjectActionNames_[sourceObjectName].insert(actionName); } for (auto destinationObjectName : actionTriggerDefinition.destinationObjectNames) { + 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; } @@ -526,7 +541,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 2889f0c44..b9c4c6ae7 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; + 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); void recordGridEvent(GridEvent event, std::unordered_map rewards); 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/PathFinder.cpp b/src/Griddly/Core/PathFinder.cpp new file mode 100644 index 000000000..5751eec96 --- /dev/null +++ b/src/Griddly/Core/PathFinder.cpp @@ -0,0 +1,11 @@ +#include "PathFinder.hpp" +#include "Grid.hpp" + +namespace griddly { + +PathFinder::PathFinder(std::shared_ptr grid, std::set impassableObjects) : + grid_(grid), 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..fc7310139 --- /dev/null +++ b/src/Griddly/Core/PathFinder.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include +#include + +namespace griddly { + +class Grid; + +struct SearchOutput { + uint32_t actionId = 0; +}; + +class PathFinder { + public: + 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::set impassableObjects_; +}; + +} // namespace griddly \ No newline at end of file diff --git a/src/Griddly/Core/SpatialHashCollisionDetector.cpp b/src/Griddly/Core/SpatialHashCollisionDetector.cpp index 78ed31832..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,39 +30,38 @@ 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; } -std::unordered_set> SpatialHashCollisionDetector::search(glm::ivec2 location) { +SearchResult SpatialHashCollisionDetector::search(glm::ivec2 location) { + + auto top = std::min(gridHeight_, location.y + range_); + auto bottom = std::max(0, location.y - (int32_t)range_); - 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 left = std::max(0, location.x - (int32_t)range_); + auto bottomLeft = calculateHash(glm::ivec2(left, bottom)); + auto topRight = calculateHash(glm::ivec2(right, top)); - - auto topLeft = glm::ivec2(left, top); - auto bottomLeft = glm::ivec2(left, bottom); - auto topRight = glm::ivec2(right, top); - auto bottomRight = glm::ivec2(right, bottom); - - 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; 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_) { @@ -72,6 +74,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,14 +85,18 @@ 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); } } } break; + case TriggerType::NONE: + throw std::invalid_argument("Misconfigured collision detector!, specify 'RANGE_BOX_BOUNDARY' or 'RANGE_BOX_AREA' in configuration"); + break; } } - 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/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/src/Griddly/Core/Util/util.hpp b/src/Griddly/Core/Util/util.hpp index f68e4620a..a7489c461 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(); } @@ -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/AStarPathFinderTest.cpp b/tests/src/Griddly/Core/AStarPathFinderTest.cpp new file mode 100644 index 000000000..7c3da8453 --- /dev/null +++ b/tests/src/Griddly/Core/AStarPathFinderTest.cpp @@ -0,0 +1,149 @@ +#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; +} + +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()); + + 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)); + + EXPECT_CALL(*mockGridPtr, getHeight).WillRepeatedly(Return(6)); + EXPECT_CALL(*mockGridPtr, getWidth).WillRepeatedly(Return(6)); + + 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, 2); + ASSERT_EQ(right.actionId, 2); + ASSERT_EQ(down.actionId, 2); + ASSERT_EQ(left.actionId, 2); +} + +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)); + + 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); + + 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 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..d0af6f930 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,87 @@ 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"})); + 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 +1151,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 +1228,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 +1455,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 +1478,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 +1498,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 +1526,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 +1591,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 +1666,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/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(); } } diff --git a/tests/src/Griddly/Core/GridTest.cpp b/tests/src/Griddly/Core/GridTest.cpp index 77c7ffcab..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)); } @@ -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/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/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/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/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 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