From 4b4125e234cb727c70822e0a1fce0688c357741e Mon Sep 17 00:00:00 2001 From: Kelly Rauchenberger Date: Thu, 19 Mar 2015 16:15:47 -0400 Subject: Implemented a simple AI --- CMakeLists.txt | 1 + res/entities.xml | 31 ++++-- res/maps.xml | 2 +- src/components/ai.cpp | 142 +++++++++++++++++++++++ src/components/ai.h | 73 ++++++++++++ src/components/map_collision.cpp | 15 ++- src/components/physics_body.cpp | 8 +- src/components/player_physics.cpp | 8 +- src/entity.h | 5 +- src/entityfactory.cpp | 229 +++++++++++++++++++++++++------------- src/entityfactory.h | 3 +- src/map.cpp | 2 +- src/map.h | 1 + src/world.cpp | 18 ++- 14 files changed, 436 insertions(+), 102 deletions(-) create mode 100644 src/components/ai.cpp create mode 100644 src/components/ai.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c5e8f71..0af1fa2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,7 @@ add_executable(Aromatherapy src/muxer.cpp src/entityfactory.cpp src/world.cpp + src/components/ai.cpp src/components/map_collision.cpp src/components/map_render.cpp src/components/physics_body.cpp diff --git a/res/entities.xml b/res/entities.xml index c3dd3f3..8f71062 100644 --- a/res/entities.xml +++ b/res/entities.xml @@ -1,12 +1,8 @@ - - - - - + - + Left Right @@ -15,8 +11,25 @@ - - - + + + + + + + + + + + + + + + + + + + + diff --git a/res/maps.xml b/res/maps.xml index 1f19dfa..9d855a2 100644 --- a/res/maps.xml +++ b/res/maps.xml @@ -95,4 +95,4 @@ 2,2,2,2,2,2,26,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,12,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,27,1,3,4,2,1,3,2,1,1,2,4,1,1,1,3,1,1,4,2,1,3,4,1,25,19,0,0,0,0,0,0,0,0, 0,0,19,0,0,0,0,0,0,0,0,0,0,0,0,0,19,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -17035 +19030 diff --git a/src/components/ai.cpp b/src/components/ai.cpp new file mode 100644 index 0000000..9f8c764 --- /dev/null +++ b/src/components/ai.cpp @@ -0,0 +1,142 @@ +#include "ai.h" +#include +#include "entity.h" + +void AIActionContainer::addAction(std::shared_ptr action) +{ + actions.push_back(action); +} + +void AIActionContainer::start(Game& game, Entity& entity) +{ + currentAction = begin(actions); + + if (currentAction != end(actions)) + { + (*currentAction)->start(game, entity); + } +} + +void AIActionContainer::perform(Game& game, Entity& entity, double dt) +{ + if (!isDone()) + { + (*currentAction)->perform(game, entity, dt); + + if ((*currentAction)->isDone()) + { + currentAction++; + + if (!isDone()) + { + (*currentAction)->start(game, entity); + } + } + } +} + +bool AIActionContainer::isDone() const +{ + return currentAction == end(actions); +} + +AI::AI(int chance) +{ + this->chance = chance; +} + +int AI::getChance() const +{ + return chance; +} + +AI& AIComponent::emplaceAI(int chance) +{ + maxChance += chance; + ais.emplace_back(chance); + + return ais.back(); +} + +void AIComponent::tick(Game& game, Entity& entity, double dt) +{ + if (currentAI == nullptr) + { + int toChoose = rand() % maxChance; + for (auto& ai : ais) + { + if (toChoose < ai.getChance()) + { + currentAI = &ai; + break; + } else { + toChoose -= ai.getChance(); + } + } + + if (currentAI != nullptr) + { + currentAI->start(game, entity); + } + } + + if (currentAI != nullptr) + { + currentAI->perform(game, entity, dt); + + if (currentAI->isDone()) + { + currentAI = nullptr; + } + } +} + +MoveAIAction::MoveAIAction(Direction dir, int len, int speed) +{ + this->dir = dir; + this->len = len; + this->speed = speed; +} + +void MoveAIAction::start(Game& game, Entity& entity) +{ + remaining = len; +} + +void MoveAIAction::perform(Game&, Entity& entity, double dt) +{ + double dist = dt * speed; + remaining -= dist; + + switch (dir) + { + case Direction::Left: + { + entity.position.first -= dist; + break; + } + + case Direction::Right: + { + entity.position.first += dist; + break; + } + + case Direction::Up: + { + entity.position.second -= dist; + break; + } + + case Direction::Down: + { + entity.position.second += dist; + break; + } + } +} + +bool MoveAIAction::isDone() const +{ + return remaining <= 0.0; +} diff --git a/src/components/ai.h b/src/components/ai.h new file mode 100644 index 0000000..840283b --- /dev/null +++ b/src/components/ai.h @@ -0,0 +1,73 @@ +#ifndef AI_H +#define AI_H + +#include +#include +#include +#include + +#include "entity.h" + +class AIAction { + public: + virtual void start(Game& game, Entity& entity) = 0; + virtual void perform(Game& game, Entity& entity, double dt) = 0; + virtual bool isDone() const = 0; +}; + +class AIActionContainer { + public: + void addAction(std::shared_ptr action); + virtual void start(Game& game, Entity& entity); + virtual void perform(Game& game, Entity& entity, double dt); + virtual bool isDone() const; + + private: + std::list> actions; + std::list>::iterator currentAction {end(actions)}; +}; + +class AI : public AIActionContainer { + public: + AI(int chance); + + int getChance() const; + + private: + int chance; +}; + +class AIComponent : public Component { + public: + AI& emplaceAI(int chance); + void tick(Game& game, Entity& entity, double dt); + + private: + int maxChance = 0; + std::list ais; + AI* currentAI = nullptr; +}; + +class MoveAIAction : public AIAction { + public: + enum class Direction { + Left, + Right, + Up, + Down + }; + + MoveAIAction(Direction dir, int len, int speed); + + void start(Game& game, Entity& entity); + void perform(Game& game, Entity& entity, double dt); + bool isDone() const; + + private: + Direction dir; + int len; + int speed; + double remaining; +}; + +#endif /* end of include guard: AI_H */ diff --git a/src/components/map_collision.cpp b/src/components/map_collision.cpp index 83ad33d..432fea6 100644 --- a/src/components/map_collision.cpp +++ b/src/components/map_collision.cpp @@ -156,15 +156,24 @@ void MapCollisionComponent::processCollision(Game& game, Entity& collider, Colli if (dir == Direction::left) { collider.position.first = collision.axis; - collider.send(game, Message::Type::stopMovingHorizontally); + + Message msg(Message::Type::setHorizontalVelocity); + msg.velocity = 0.0; + collider.send(game, msg); } else if (dir == Direction::right) { collider.position.first = collision.axis - collider.size.first; - collider.send(game, Message::Type::stopMovingHorizontally); + + Message msg(Message::Type::setHorizontalVelocity); + msg.velocity = 0.0; + collider.send(game, msg); } else if (dir == Direction::up) { collider.position.second = collision.axis; - collider.send(game, Message::Type::stopMovingVertically); + + Message msg(Message::Type::setVerticalVelocity); + msg.velocity = 0.0; + collider.send(game, msg); } else if (dir == Direction::down) { collider.position.second = collision.axis - collider.size.second; diff --git a/src/components/physics_body.cpp b/src/components/physics_body.cpp index acbdc5d..97394d1 100644 --- a/src/components/physics_body.cpp +++ b/src/components/physics_body.cpp @@ -13,12 +13,12 @@ void PhysicsBodyComponent::receive(Game&, Entity&, const Message& msg) } else if (msg.type == Message::Type::stopWalking) { velocity.first = 0.0; - } else if (msg.type == Message::Type::stopMovingHorizontally) + } else if (msg.type == Message::Type::setHorizontalVelocity) { - velocity.first = 0.0; - } else if (msg.type == Message::Type::stopMovingVertically) + velocity.first = msg.velocity; + } else if (msg.type == Message::Type::setVerticalVelocity) { - velocity.second = 0.0; + velocity.second = msg.velocity; } } diff --git a/src/components/player_physics.cpp b/src/components/player_physics.cpp index 1d14f35..40e9948 100644 --- a/src/components/player_physics.cpp +++ b/src/components/player_physics.cpp @@ -29,12 +29,12 @@ void PlayerPhysicsComponent::receive(Game&, Entity& entity, const Message& msg) { velocity.first = 0.0; direction = 0; - } else if (msg.type == Message::Type::stopMovingHorizontally) + } else if (msg.type == Message::Type::setHorizontalVelocity) { - velocity.first = 0.0; - } else if (msg.type == Message::Type::stopMovingVertically) + velocity.first = msg.velocity; + } else if (msg.type == Message::Type::setVerticalVelocity) { - velocity.second = 0.0; + velocity.second = msg.velocity; } else if (msg.type == Message::Type::hitTheGround) { if (isFalling) diff --git a/src/entity.h b/src/entity.h index d09dbe5..7f09f2d 100644 --- a/src/entity.h +++ b/src/entity.h @@ -15,8 +15,8 @@ class Message { walkLeft, walkRight, stopWalking, - stopMovingHorizontally, - stopMovingVertically, + setHorizontalVelocity, + setVerticalVelocity, collision, jump, stopJump, @@ -33,6 +33,7 @@ class Message { Type type; Entity* collisionEntity; int dropAxis; + double velocity; }; class Entity { diff --git a/src/entityfactory.cpp b/src/entityfactory.cpp index 6fb86ca..b80fe99 100644 --- a/src/entityfactory.cpp +++ b/src/entityfactory.cpp @@ -3,103 +3,180 @@ #include "muxer.h" #include #include +#include #include "components/static_image.h" #include "components/simple_collider.h" #include "components/physics_body.h" +#include "components/ai.h" #include "game.h" -struct EntityData { - char* sprite; - char* action; - bool hasPhysics; - int width; - int height; -}; - -static std::map factories; - -std::shared_ptr EntityFactory::createNamedEntity(const std::string name) +void parseEntityAIData(AI& ai, xmlNodePtr node, const std::map& items) { - auto it = factories.find(name); - EntityData data = factories[name]; - if (it == factories.end()) - { - xmlDocPtr doc = xmlParseFile(("entities/" + name + ".xml").c_str()); - if (doc == nullptr) - { - fprintf(stderr, "Error reading entity %s\n", name.c_str()); - exit(-1); - } - - xmlNodePtr top = xmlDocGetRootElement(doc); - if (top == nullptr) - { - fprintf(stderr, "Empty entity %s\n", name.c_str()); - exit(-1); - } - - if (xmlStrcmp(top->name, (const xmlChar*) "entity-def")) - { - fprintf(stderr, "Invalid entity definition %s\n", name.c_str()); - exit(-1); - } + xmlChar* key; - for (xmlNodePtr node = top->xmlChildrenNode; node != NULL; node = node->next) + for (xmlNodePtr aiNode = node->xmlChildrenNode; aiNode != NULL; aiNode = aiNode->next) + { + if (!xmlStrcmp(aiNode->name, (xmlChar*) "move")) { - if (!xmlStrcmp(node->name, (const xmlChar*) "sprite")) + MoveAIAction::Direction dir; + int len; + int speed; + + key = xmlGetProp(aiNode, (xmlChar*) "direction"); + if (key == 0) exit(2); + if (!xmlStrcmp(key, (xmlChar*) "left")) { - xmlChar* key = xmlNodeListGetString(doc, node->xmlChildrenNode, 1); - data.sprite = (char*) calloc(xmlStrlen(key)+1, sizeof(char)); - strcpy(data.sprite, (char*) key); - xmlFree(key); - } else if (!xmlStrcmp(node->name, (const xmlChar*) "action")) + dir = MoveAIAction::Direction::Left; + } else if (!xmlStrcmp(key, (xmlChar*) "right")) { - xmlChar* key = xmlNodeListGetString(doc, node->xmlChildrenNode, 1); - data.action = (char*) calloc(xmlStrlen(key)+1, sizeof(char)); - strcpy(data.action, (char*) key); - xmlFree(key); - } else if (!xmlStrcmp(node->name, (const xmlChar*) "size")) + dir = MoveAIAction::Direction::Right; + } else if (!xmlStrcmp(key, (xmlChar*) "up")) { - xmlChar* key = xmlNodeListGetString(doc, node->xmlChildrenNode, 1); - data.hasPhysics = true; - sscanf((char*) key, "%d,%d", &data.width, &data.height); - xmlFree(key); + dir = MoveAIAction::Direction::Up; + } else if (!xmlStrcmp(key, (xmlChar*) "down")) + { + dir = MoveAIAction::Direction::Down; + } else { + exit(2); + } + xmlFree(key); + + key = xmlGetProp(aiNode, (xmlChar*) "length"); + if (key != 0) + { + len = atoi((char*) key); + } else { + key = xmlGetProp(aiNode, (xmlChar*) "length-var"); + if (key == 0) exit(2); + std::string varName = (char*) key; + len = items.at(varName); + } + xmlFree(key); + + key = xmlGetProp(aiNode, (xmlChar*) "speed"); + if (key != 0) + { + speed = atoi((char*) key); + } else { + key = xmlGetProp(aiNode, (xmlChar*) "speed-var"); + if (key == 0) exit(2); + std::string varName = (char*) key; + speed = items.at(varName); + } + xmlFree(key); + + ai.addAction(std::make_shared(dir, len, speed)); + } else if (!xmlStrcmp(aiNode->name, (xmlChar*) "switch")) + { + key = xmlGetProp(aiNode, (xmlChar*) "item"); + if (key == 0) exit(2); + std::string switchItem = (char*) key; + xmlFree(key); + + for (xmlNodePtr switchNode = aiNode->xmlChildrenNode; switchNode != NULL; switchNode = switchNode->next) + { + if (!xmlStrcmp(switchNode->name, (xmlChar*) "case")) + { + key = xmlGetProp(switchNode, (xmlChar*) "value"); + if (key == 0) exit(2); + int caseValue = atoi((char*) key); + xmlFree(key); + + if (items.at(switchItem) == caseValue) + { + parseEntityAIData(ai, switchNode, items); + } + } } } - - xmlFreeDoc(doc); - - factories[name] = data; } - - auto entity = std::make_shared(); - - if (data.sprite) +} + +std::shared_ptr EntityFactory::createNamedEntity(const std::string name, const std::map& items) +{ + xmlDocPtr doc = xmlParseFile("res/entities.xml"); + if (doc == nullptr) { - auto component = std::make_shared(data.sprite); - entity->addComponent(component); + fprintf(stderr, "Error reading entities\n"); + exit(-1); } - - if (data.action) + + xmlNodePtr top = xmlDocGetRootElement(doc); + if (top == nullptr) { - if (!strcmp(data.action, "save")) - { - auto component = std::make_shared([&] (Game& game, Entity&) { - playSound("res/Pickup_Coin23.wav", 0.25); - - game.saveGame(); - }); - entity->addComponent(component); - } + fprintf(stderr, "Empty entities file\n"); + exit(-1); + } + + if (xmlStrcmp(top->name, (const xmlChar*) "entities")) + { + fprintf(stderr, "Invalid entities definition\n"); + exit(-1); } - if (data.hasPhysics) + auto entity = std::make_shared(); + + xmlChar* key; + for (xmlNodePtr node = top->xmlChildrenNode; node != NULL; node = node->next) { - auto component = std::make_shared(); - entity->addComponent(component); - - entity->size = std::make_pair(data.width, data.height); + if (!xmlStrcmp(node->name, (xmlChar*) "entity")) + { + key = xmlGetProp(node, (xmlChar*) "id"); + if (key == 0) exit(-1); + std::string entityID = (char*) key; + xmlFree(key); + + if (entityID == name) + { + key = xmlGetProp(node, (xmlChar*) "sprite"); + if (key == 0) exit(-1); + auto spriteComponent = std::make_shared((char*) key); + entity->addComponent(spriteComponent); + xmlFree(key); + + auto physicsComponent = std::make_shared(); + entity->addComponent(physicsComponent); + + key = xmlGetProp(node, (xmlChar*) "width"); + if (key == 0) exit(-1); + entity->size.first = atoi((char*) key); + xmlFree(key); + + key = xmlGetProp(node, (xmlChar*) "height"); + if (key == 0) exit(-1); + entity->size.second = atoi((char*) key); + xmlFree(key); + + bool addAI = false; + auto aiComponent = std::make_shared(); + + for (xmlNodePtr entityNode = node->xmlChildrenNode; entityNode != NULL; entityNode = entityNode->next) + { + if (!xmlStrcmp(entityNode->name, (xmlChar*) "ai")) + { + addAI = true; + + xmlChar* chanceKey = xmlGetProp(entityNode, (xmlChar*) "chance"); + if (chanceKey == 0) exit(2); + int chance = atoi((char*) chanceKey); + xmlFree(chanceKey); + + AI& ai = aiComponent->emplaceAI(chance); + parseEntityAIData(ai, entityNode, items); + } + } + + if (addAI) + { + entity->addComponent(aiComponent); + } + + break; + } + } } + + xmlFreeDoc(doc); return entity; } diff --git a/src/entityfactory.h b/src/entityfactory.h index 870d6d5..56f7216 100644 --- a/src/entityfactory.h +++ b/src/entityfactory.h @@ -2,13 +2,14 @@ #define ENTITYFACTORY_H #include +#include class Entity; class Map; class EntityFactory { public: - static std::shared_ptr createNamedEntity(const std::string name); + static std::shared_ptr createNamedEntity(const std::string name, const std::map& items); }; #endif diff --git a/src/map.cpp b/src/map.cpp index c4f31d1..e3725b2 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -69,7 +69,7 @@ void Map::createEntities(std::list>& entities) const { for (auto data : this->entities) { - auto entity = EntityFactory::createNamedEntity(data.name); + auto entity = EntityFactory::createNamedEntity(data.name, data.items); entity->position = data.position; entities.push_back(entity); diff --git a/src/map.h b/src/map.h index 4e661ab..22333aa 100644 --- a/src/map.h +++ b/src/map.h @@ -34,6 +34,7 @@ class Map { struct EntityData { std::string name; std::pair position; + std::map items; }; struct Adjacent { diff --git a/src/world.cpp b/src/world.cpp index 17288fa..0c61c47 100644 --- a/src/world.cpp +++ b/src/world.cpp @@ -57,7 +57,7 @@ World::World(const char* filename) { if (!xmlStrcmp(mapNode->name, (const xmlChar*) "environment")) { - xmlChar* key = xmlNodeListGetString(doc, mapNode->xmlChildrenNode, 1); + xmlChar* key = xmlNodeGetContent(mapNode); int* mapdata = (int*) malloc(MAP_WIDTH*MAP_HEIGHT*sizeof(int)); mapdata[0] = atoi(strtok((char*) key, ",\n")); for (int i=1; i<(MAP_WIDTH*MAP_HEIGHT); i++) @@ -85,6 +85,22 @@ World::World(const char* filename) data.position.second = atoi((char*) yKey); xmlFree(yKey); + for (xmlNodePtr entityNode = mapNode->xmlChildrenNode; entityNode != NULL; entityNode = entityNode->next) + { + if (!xmlStrcmp(entityNode->name, (xmlChar*) "item")) + { + xmlChar* itemIdKey = xmlGetProp(entityNode, (const xmlChar*) "id"); + if (itemIdKey == 0) exit(2); + std::string itemId = (char*) itemIdKey; + xmlFree(itemIdKey); + + xmlChar* itemIdVal = xmlNodeGetContent(entityNode); + if (itemIdVal == 0) exit(2); + data.items[itemId] = atoi((char*) itemIdVal); + xmlFree(itemIdVal); + } + } + map.addEntity(data); } else if (!xmlStrcmp(mapNode->name, (const xmlChar*) "adjacent")) { -- cgit 1.4.1