summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorKelly Rauchenberger <fefferburbia@gmail.com>2018-05-03 14:41:01 -0400
committerKelly Rauchenberger <fefferburbia@gmail.com>2018-05-03 14:41:01 -0400
commit83f51a6892629921b4cc482b986656a0a5cc5f6a (patch)
tree50dcd4485a165b29591dafbec907c20dcf9284b1
parentb2311e1ba43a72a205b51a24376a1f363faa569e (diff)
downloadtherapy-83f51a6892629921b4cc482b986656a0a5cc5f6a.tar.gz
therapy-83f51a6892629921b4cc482b986656a0a5cc5f6a.tar.bz2
therapy-83f51a6892629921b4cc482b986656a0a5cc5f6a.zip
Added simple AI implementation
The new AutomatingSystem and AutomatableComponent are responsible for simple AI tasks. This currently is limited to moving entities at a certain speed for certain periods of time. These tasks are arranged as a set of behaviors, which are picked randomly when automation starts or when a behavior finishes executing. A behavior is a sequence of actions that run one after another.

Currently, if an automated entity is blocked from moving by a collision, it will be coerced out of its intended path. This is because the automation parameters are stored as a speed and a duration, rather than a starting location and an ending location. This may end up being changed, or made configurable, as this is an early implementation of this feature and will need to be more complex later.

Added an RNG object to the Game class, so that the AutomatingSystem can pick behaviors at random.
-rw-r--r--CMakeLists.txt1
-rw-r--r--src/components/automatable.h96
-rw-r--r--src/game.cpp4
-rw-r--r--src/game.h9
-rw-r--r--src/main.cpp6
-rw-r--r--src/systems/automating.cpp71
-rw-r--r--src/systems/automating.h19
-rw-r--r--src/systems/realizing.cpp174
8 files changed, 377 insertions, 3 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index cd652e2..04ca668 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt
@@ -67,6 +67,7 @@ add_executable(Aromatherapy
67 src/systems/playing.cpp 67 src/systems/playing.cpp
68 src/systems/scheduling.cpp 68 src/systems/scheduling.cpp
69 src/systems/realizing.cpp 69 src/systems/realizing.cpp
70 src/systems/automating.cpp
70 vendor/stb_image.cpp 71 vendor/stb_image.cpp
71) 72)
72 73
diff --git a/src/components/automatable.h b/src/components/automatable.h new file mode 100644 index 0000000..b37945f --- /dev/null +++ b/src/components/automatable.h
@@ -0,0 +1,96 @@
1#ifndef AUTOMATABLE_H_3D519131
2#define AUTOMATABLE_H_3D519131
3
4#include "component.h"
5#include <vector>
6#include <random>
7
8class AutomatableComponent : public Component {
9public:
10
11 /**
12 * Helper class that defines an automatable action.
13 */
14 class Action {
15 public:
16
17 /**
18 * The horizontal and vertical speed, in pixels/sec, that the entity should
19 * move at.
20 */
21 double speedX;
22 double speedY;
23
24 /**
25 * The duration of the action in seconds.
26 */
27 double dur;
28 };
29
30 /**
31 * Helper type that defines a behavior that an entity can exhibit, which is a
32 * list of actions that are stepped through in sequence.
33 */
34 using Behavior = std::vector<Action>;
35
36 /**
37 * A group of behaviors that the entity can exhibit, which are picked at
38 * random at the start of automation and whenever a behavior completes.
39 *
40 * @managed_by RealizingSystem
41 */
42 std::vector<Behavior> behaviors;
43
44 /**
45 * A random distribution over the above behaviors.
46 *
47 * @managed_by RealizingSystem
48 */
49 std::discrete_distribution<size_t> behaviorDist;
50
51 /**
52 * A flag indicating whether a behavior is currently executing.
53 *
54 * @managed_by AutomatingSystem
55 */
56 bool behaviorRunning = false;
57
58 /**
59 * A flag indicating whether an action is currently executing.
60 *
61 * @managed_by AutomatingSystem
62 */
63 bool actionRunning = false;
64
65 /**
66 * The index of the currently executing behavior, if there is one.
67 *
68 * @managed_by AutomatingSystem
69 */
70 size_t currentBehavior;
71
72 /**
73 * The index of the currently executing action, if there is one.
74 *
75 * @managed_by AutomatingSystem
76 */
77 size_t currentAction;
78
79 /**
80 * The amount of time remaining, in seconds, of the currently executing
81 * action.
82 *
83 * @managed_by AutomatingSystem
84 */
85 double remaining;
86
87 /**
88 * If this flag is disabled, the entity will be ignored by the automating
89 * system.
90 *
91 * @managed_by RealizingSystem
92 */
93 bool active = false;
94};
95
96#endif /* end of include guard: AUTOMATABLE_H_3D519131 */
diff --git a/src/game.cpp b/src/game.cpp index d10c52c..bf2b10b 100644 --- a/src/game.cpp +++ b/src/game.cpp
@@ -12,6 +12,7 @@
12#include "systems/playing.h" 12#include "systems/playing.h"
13#include "systems/scheduling.h" 13#include "systems/scheduling.h"
14#include "systems/realizing.h" 14#include "systems/realizing.h"
15#include "systems/automating.h"
15#include "animation.h" 16#include "animation.h"
16#include "consts.h" 17#include "consts.h"
17 18
@@ -29,12 +30,13 @@ void key_callback(GLFWwindow* window, int key, int, int action, int)
29 game.systemManager_.input(key, action); 30 game.systemManager_.input(key, action);
30} 31}
31 32
32Game::Game() 33Game::Game(std::mt19937& rng) : rng_(rng)
33{ 34{
34 systemManager_.emplaceSystem<RealizingSystem>(*this); 35 systemManager_.emplaceSystem<RealizingSystem>(*this);
35 systemManager_.emplaceSystem<PlayingSystem>(*this); 36 systemManager_.emplaceSystem<PlayingSystem>(*this);
36 systemManager_.emplaceSystem<SchedulingSystem>(*this); 37 systemManager_.emplaceSystem<SchedulingSystem>(*this);
37 systemManager_.emplaceSystem<ControllingSystem>(*this); 38 systemManager_.emplaceSystem<ControllingSystem>(*this);
39 systemManager_.emplaceSystem<AutomatingSystem>(*this);
38 systemManager_.emplaceSystem<OrientingSystem>(*this); 40 systemManager_.emplaceSystem<OrientingSystem>(*this);
39 systemManager_.emplaceSystem<PonderingSystem>(*this); 41 systemManager_.emplaceSystem<PonderingSystem>(*this);
40 systemManager_.emplaceSystem<MappingSystem>(*this); 42 systemManager_.emplaceSystem<MappingSystem>(*this);
diff --git a/src/game.h b/src/game.h index 92a67d9..dc256c6 100644 --- a/src/game.h +++ b/src/game.h
@@ -1,6 +1,7 @@
1#ifndef GAME_H_1014DDC9 1#ifndef GAME_H_1014DDC9
2#define GAME_H_1014DDC9 2#define GAME_H_1014DDC9
3 3
4#include <random>
4#include "entity_manager.h" 5#include "entity_manager.h"
5#include "system_manager.h" 6#include "system_manager.h"
6#include "renderer/renderer.h" 7#include "renderer/renderer.h"
@@ -8,10 +9,15 @@
8class Game { 9class Game {
9public: 10public:
10 11
11 Game(); 12 Game(std::mt19937& rng);
12 13
13 void execute(); 14 void execute();
14 15
16 inline std::mt19937& getRng()
17 {
18 return rng_;
19 }
20
15 inline Renderer& getRenderer() 21 inline Renderer& getRenderer()
16 { 22 {
17 return renderer_; 23 return renderer_;
@@ -36,6 +42,7 @@ public:
36 42
37private: 43private:
38 44
45 std::mt19937 rng_;
39 Renderer renderer_; 46 Renderer renderer_;
40 EntityManager entityManager_; 47 EntityManager entityManager_;
41 SystemManager systemManager_; 48 SystemManager systemManager_;
diff --git a/src/main.cpp b/src/main.cpp index ddbc15f..d59d0f9 100644 --- a/src/main.cpp +++ b/src/main.cpp
@@ -1,11 +1,15 @@
1#include <random>
1#include "muxer.h" 2#include "muxer.h"
2#include "game.h" 3#include "game.h"
3 4
4int main() 5int main()
5{ 6{
7 std::random_device randomDevice;
8 std::mt19937 rng(randomDevice());
9
6 initMuxer(); 10 initMuxer();
7 11
8 Game game; 12 Game game(rng);
9 game.execute(); 13 game.execute();
10 14
11 destroyMuxer(); 15 destroyMuxer();
diff --git a/src/systems/automating.cpp b/src/systems/automating.cpp new file mode 100644 index 0000000..0d85957 --- /dev/null +++ b/src/systems/automating.cpp
@@ -0,0 +1,71 @@
1#include "automating.h"
2#include "game.h"
3#include "components/automatable.h"
4#include "components/ponderable.h"
5#include "systems/pondering.h"
6
7void AutomatingSystem::tick(double dt)
8{
9 auto entities = game_.getEntityManager().getEntitiesWithComponents<
10 AutomatableComponent,
11 PonderableComponent>();
12
13 for (id_type entity : entities)
14 {
15 auto& automatable = game_.getEntityManager().
16 getComponent<AutomatableComponent>(entity);
17
18 if (!automatable.active)
19 {
20 continue;
21 }
22
23 if (automatable.behaviorRunning &&
24 (automatable.remaining <= 0.0))
25 {
26 automatable.currentAction++;
27 automatable.actionRunning = false;
28
29 if (automatable.currentAction ==
30 automatable.behaviors[automatable.currentBehavior].size())
31 {
32 automatable.behaviorRunning = false;
33 }
34 }
35
36 if (!automatable.behaviorRunning)
37 {
38 automatable.currentBehavior = automatable.behaviorDist(game_.getRng());
39 automatable.currentAction = 0;
40 automatable.behaviorRunning = true;
41 }
42
43 AutomatableComponent::Action& curAction =
44 automatable.behaviors
45 [automatable.currentBehavior]
46 [automatable.currentAction];
47
48 if (!automatable.actionRunning)
49 {
50 automatable.remaining = curAction.dur;
51 automatable.actionRunning = true;
52 }
53
54 auto& ponderable = game_.getEntityManager().
55 getComponent<PonderableComponent>(entity);
56
57 ponderable.velX = curAction.speedX;
58 ponderable.velY = curAction.speedY;
59
60 automatable.remaining -= dt;
61 }
62}
63
64void AutomatingSystem::initPrototype(id_type prototype)
65{
66 auto& automatable = game_.getEntityManager().
67 getComponent<AutomatableComponent>(prototype);
68
69 automatable.behaviorRunning = false;
70 automatable.actionRunning = false;
71}
diff --git a/src/systems/automating.h b/src/systems/automating.h new file mode 100644 index 0000000..c78b7cf --- /dev/null +++ b/src/systems/automating.h
@@ -0,0 +1,19 @@
1#ifndef AUTOMATING_H_E6E5D76E
2#define AUTOMATING_H_E6E5D76E
3
4#include "system.h"
5
6class AutomatingSystem : public System {
7public:
8
9 AutomatingSystem(Game& game) : System(game)
10 {
11 }
12
13 void tick(double dt);
14
15 void initPrototype(id_type prototype);
16
17};
18
19#endif /* end of include guard: AUTOMATING_H_E6E5D76E */
diff --git a/src/systems/realizing.cpp b/src/systems/realizing.cpp index 3656acb..8e670ac 100644 --- a/src/systems/realizing.cpp +++ b/src/systems/realizing.cpp
@@ -12,9 +12,11 @@
12#include "components/playable.h" 12#include "components/playable.h"
13#include "components/ponderable.h" 13#include "components/ponderable.h"
14#include "components/transformable.h" 14#include "components/transformable.h"
15#include "components/automatable.h"
15#include "systems/mapping.h" 16#include "systems/mapping.h"
16#include "systems/animating.h" 17#include "systems/animating.h"
17#include "systems/pondering.h" 18#include "systems/pondering.h"
19#include "systems/automating.h"
18 20
19inline xmlChar* getProp(xmlNodePtr node, const char* attr) 21inline xmlChar* getProp(xmlNodePtr node, const char* attr)
20{ 22{
@@ -27,6 +29,92 @@ inline xmlChar* getProp(xmlNodePtr node, const char* attr)
27 return key; 29 return key;
28} 30}
29 31
32void parseAI(
33 xmlNodePtr node,
34 std::vector<AutomatableComponent::Action>& behavior,
35 const std::map<std::string, int>& items)
36{
37 xmlChar* key = nullptr;
38
39 if (!xmlStrcmp(
40 node->name,
41 reinterpret_cast<const xmlChar*>("switch")))
42 {
43 key = getProp(node, "item");
44 std::string switchItem = reinterpret_cast<char*>(key);
45 xmlFree(key);
46
47 for (xmlNodePtr switchNode = node->xmlChildrenNode;
48 switchNode != nullptr;
49 switchNode = switchNode->next)
50 {
51 if (!xmlStrcmp(
52 switchNode->name,
53 reinterpret_cast<const xmlChar*>("case")))
54 {
55 key = getProp(switchNode, "value");
56 int caseValue = atoi(reinterpret_cast<char*>(key));
57 xmlFree(key);
58
59 if (items.at(switchItem) == caseValue)
60 {
61 for (xmlNodePtr caseNode = switchNode->xmlChildrenNode;
62 caseNode != nullptr;
63 caseNode = caseNode->next)
64 {
65 parseAI(
66 caseNode,
67 behavior,
68 items);
69 }
70 }
71 }
72 }
73 } else if (!xmlStrcmp(
74 node->name,
75 reinterpret_cast<const xmlChar*>("move")))
76 {
77 key = getProp(node, "direction");
78 std::string direction = reinterpret_cast<char*>(key);
79 xmlFree(key);
80
81 key = getProp(node, "length-var");
82 std::string lengthVar = reinterpret_cast<char*>(key);
83 xmlFree(key);
84
85 key = getProp(node, "speed-var");
86 std::string speedVar = reinterpret_cast<char*>(key);
87 xmlFree(key);
88
89 double length = items.at(lengthVar);
90 double speed = items.at(speedVar);
91
92 AutomatableComponent::Action action;
93
94 if (direction == "left")
95 {
96 action.speedX = -speed;
97 action.speedY = 0;
98 } else if (direction == "right")
99 {
100 action.speedX = speed;
101 action.speedY = 0;
102 } else if (direction == "up")
103 {
104 action.speedX = 0;
105 action.speedY = -speed;
106 } else if (direction == "down")
107 {
108 action.speedX = 0;
109 action.speedY = speed;
110 }
111
112 action.dur = length / speed;
113
114 behavior.push_back(std::move(action));
115 }
116}
117
30// TODO: neither the XML doc nor any of the emplaced entities are properly 118// TODO: neither the XML doc nor any of the emplaced entities are properly
31// destroyed if this method throws an exception. 119// destroyed if this method throws an exception.
32EntityManager::id_type RealizingSystem::initSingleton( 120EntityManager::id_type RealizingSystem::initSingleton(
@@ -211,6 +299,70 @@ EntityManager::id_type RealizingSystem::initSingleton(
211 game_.getSystemManager().getSystem<PonderingSystem>(). 299 game_.getSystemManager().getSystem<PonderingSystem>().
212 initializeBody(mapObject, PonderableComponent::Type::vacuumed); 300 initializeBody(mapObject, PonderableComponent::Type::vacuumed);
213 301
302 // Look for any object configuration.
303 std::map<std::string, int> items;
304
305 for (xmlNodePtr objectNode = mapNode->xmlChildrenNode;
306 objectNode != nullptr;
307 objectNode = objectNode->next)
308 {
309 if (!xmlStrcmp(
310 objectNode->name,
311 reinterpret_cast<const xmlChar*>("item")))
312 {
313 key = getProp(objectNode, "id");
314 std::string itemName = reinterpret_cast<char*>(key);
315 xmlFree(key);
316
317 key = xmlNodeGetContent(objectNode);
318 int itemVal = atoi(reinterpret_cast<char*>(key));
319 xmlFree(key);
320
321 items[itemName] = itemVal;
322 }
323 }
324
325 // Add any AI behaviors.
326 std::vector<double> behaviorWeights;
327
328 for (xmlNodePtr protoSubNode = prototypeNode->xmlChildrenNode;
329 protoSubNode != nullptr;
330 protoSubNode = protoSubNode->next)
331 {
332 if (!xmlStrcmp(
333 protoSubNode->name,
334 reinterpret_cast<const xmlChar*>("ai")))
335 {
336 if (!game_.getEntityManager().
337 hasComponent<AutomatableComponent>(mapObject))
338 {
339 game_.getEntityManager().
340 emplaceComponent<AutomatableComponent>(mapObject);
341 }
342
343 auto& automatable = game_.getEntityManager().
344 getComponent<AutomatableComponent>(mapObject);
345
346 key = getProp(protoSubNode, "chance");
347 behaviorWeights.push_back(atof(reinterpret_cast<char*>(key)));
348 xmlFree(key);
349
350 std::vector<AutomatableComponent::Action> behavior;
351
352 for (xmlNodePtr aiNode = protoSubNode->xmlChildrenNode;
353 aiNode != nullptr;
354 aiNode = aiNode->next)
355 {
356 parseAI(
357 aiNode,
358 behavior,
359 items);
360 }
361
362 automatable.behaviors.push_back(std::move(behavior));
363 }
364 }
365
214 mappable.objects.push_back(mapObject); 366 mappable.objects.push_back(mapObject);
215 } else if (!xmlStrcmp( 367 } else if (!xmlStrcmp(
216 mapNode->name, 368 mapNode->name,
@@ -304,6 +456,7 @@ void RealizingSystem::loadMap(id_type mapEntity)
304 456
305 auto& animating = game_.getSystemManager().getSystem<AnimatingSystem>(); 457 auto& animating = game_.getSystemManager().getSystem<AnimatingSystem>();
306 auto& pondering = game_.getSystemManager().getSystem<PonderingSystem>(); 458 auto& pondering = game_.getSystemManager().getSystem<PonderingSystem>();
459 auto& automating = game_.getSystemManager().getSystem<AutomatingSystem>();
307 460
308 std::set<id_type> players = 461 std::set<id_type> players =
309 game_.getEntityManager().getEntitiesWithComponents< 462 game_.getEntityManager().getEntitiesWithComponents<
@@ -366,6 +519,11 @@ void RealizingSystem::loadMap(id_type mapEntity)
366 pondering.initPrototype(prototype); 519 pondering.initPrototype(prototype);
367 } 520 }
368 521
522 if (game_.getEntityManager().hasComponent<AutomatableComponent>(prototype))
523 {
524 automating.initPrototype(prototype);
525 }
526
369 enterActiveMap(prototype); 527 enterActiveMap(prototype);
370 } 528 }
371 529
@@ -399,6 +557,14 @@ void RealizingSystem::enterActiveMap(id_type entity)
399 557
400 ponderable.active = true; 558 ponderable.active = true;
401 } 559 }
560
561 if (game_.getEntityManager().hasComponent<AutomatableComponent>(entity))
562 {
563 auto& automatable = game_.getEntityManager().
564 getComponent<AutomatableComponent>(entity);
565
566 automatable.active = true;
567 }
402} 568}
403 569
404void RealizingSystem::leaveActiveMap(id_type entity) 570void RealizingSystem::leaveActiveMap(id_type entity)
@@ -418,4 +584,12 @@ void RealizingSystem::leaveActiveMap(id_type entity)
418 584
419 ponderable.active = false; 585 ponderable.active = false;
420 } 586 }
587
588 if (game_.getEntityManager().hasComponent<AutomatableComponent>(entity))
589 {
590 auto& automatable = game_.getEntityManager().
591 getComponent<AutomatableComponent>(entity);
592
593 automatable.active = false;
594 }
421} 595}