Roguelike AI - Doing it the generic way
Jump to navigation
Jump to search
I'm just starting to code the AI for my own roguelike Portals, and I've been planning an AI system that would be generic and easily extendable. Here's some ideas I have come up with (this article assumes some knowledge of C++). The main idea behind this article is, that by coding a few basic AI routines, more complex AI routines can be constructed using combinations of these simple routines. First we'll need to define a simple Creature class. class Creature { friend class Controller; public: void SetAIModule(AI_Algorithm*); private: AI_Algorithm* m_ai; }; The Controller class handles all interaction between the Creature and its AI-module. class Controller { public: Controller(Creature&); // The MoveBy method is provided as the simplest example. Other methods // needed would include spellcasting, wielding of items etc. void MoveBy(int delta_x, int delta_y); private: Creature& m_creature; }; The AI_Algorithm class is the abstract base class for all AI routines. DoAIAction is the only common method between the modules. When it is the creatures turn to act, it will simply call m_ai->DoAIAction(); class AI_Algorithm { public: AI_Algorithm(Controller&); virtual void DoAIAction() = 0; // pure virtual method protected: Controller m_controller; }; Going to a certain map location is the first AI module that is used by most following modules. This module calculates a good path (e.g. the shortest or safest path) through the map from the creature's current location to the destination. I'm not going to go into details about pathfinding in this article, but there are some very good web resources available on this subject. class AI_GoToXY : public AI_Algorithm { public: AI_GotoXY(Controller&); AI_GotoXY(Controller&, int x, int y); void SetDestination(int x, int y); // The overriden method DoAIAction() calculates the best path (if not // already calculated) and then moves the creature one step at a time // along this path. virtual void DoAIAction(); protected: // The class that generates and stores the best path to the destination. // I'll cover the details of this class in a separate article. Path m_path; }; Another basic module is random wandering around the map. Instead of the usual neutral monster behaviour seen in roguelikes (i.e. move one step to a random direction every turn) a more interesting movement can be generated by choosing a random (x,y) location from the map and sending the creature there, choosing a new destination whenever the current one is reached. struct XYPoint { int x,y; }; class AI_WanderRandomly : public AI_Algorithm { public: AI_WanderRandomly(Controller&); // If the creature has no destination, the DoAIAction() method randomly // picks a (x,y) location, and feeds the destination to the GoToXY module // through m_gotoxy.SetDestination(x,y). virtual void DoAIAction(); protected: virtual XYPoint GetRandomLocation() const; private: // Instead of aggregation, private inheritance could also be used. AI_GoToXY m_gotoxy; int m_destination_x, m_destination_y; }: The next module moves the creature randomly within a certain area (this could be for example the creature's "nest"). The area could be defined in several different ways, but in this example, it is defined as a n-sided polygon. Other good alternatives would be a rectangle or a circle. typedef std::vector<XYPoint> Polygon; class AI_PatrolArea : public AI_WanderRandomly { public: AI_PatrolArea(Controller&, const Polygon&); // Alternatively: // AI_PatrolArea(Controller&, XYPoint top_left, XYPoint bottom_right); // AI_PatrolArea(Controller&, XYPoint center, int radius); protected: // The overriden method from AI_WanderRandomly selects a random (x,y) // location from within the patrol area. virtual XY GetRandomLocation() const; private: Polygon m_area; }; ChaseCreature is one of the most important AI modules for a roguelike and it is used by almost every creature. It uses the GoToXY module to find the best path to the target creature's current location. Recalculating the path every turn would be very expensive, however, so a new destination should be set only on rare intervals. As a good rule of thumb, the closer the target is, the more often the path should be recalculated. class AI_ChaseCreature : public AI_Algorithm { public: AI_ChaseCreature(Controller&); AI_ChaseCreature(Controller&, Creature&); void SetTarget(Creature&); // DoAIAction checks whether the path needs to updated (via the // NeedUpdate() method) and follows the created path. virtual void DoAIAction(); protected: // The NeedUpdate method returns 'true' if the path needs to be // recalculated. This method takes into account the distance to the // target creature as well as the distance the target has moved since // the last update. bool NeedUpdate() const; private: Creature* m_target; AI_GoToXY m_gotoxy; }; The EstimateRisk module is just a helper module for the other AI modules. It (somehow!) estimates the risk involved in attacking a target creature. class AI_EstimateRisk : public AI_Algorithm { public: AI_EstimateRisk(Controller&); AI_EstimateRisk(Controller&, Creature&); void SetTarget(Creature&); int GetRiskFactor() const; virtual void DoAIAction(); }; The GuardArea module works just like the PatrolArea module until an intruder is noticed within the patrol area. At this point the module estimates the risk (the aggressiveness of the creature can be adjusted through the SetMaxRisk() method) and possibly attacks the intruder using the ChaseCreature module. class AI_GuardArea : public AI_Algorithm { public: AI_GuardArea(Controller&, Polygon&); void SetMaxRisk(int); // Move along the path (f_patrol) until and each step check the // creture's line of sight for intruders. virtual void DoAIAction(); private: AI_PatrolArea f_patrol; AI_ChaseCreature f_attack; AI_EstimateRisk f_risk; }; By combining existing module like done above, any kind of module from AI_ScavengeEquipment to AI_KillEverything could be constructed with moderate ease. A complete AI module would combine more than a dozen sub-modules like WanderRandomly, SearchFood, DefendHome etc. to simulate the actions of a realistic, living creature. For my own roguelike, I hope to have different creatures use different kind of AI modules, so that each creature type has a distinct behaviour (from a cowardly stalker to an aggressive hunter). If you have any questions, ideas, comments etc. feel free to email me. Sami Hangaslammi (mailto:shang@st.jyu.fi) Developer of Portals (http://www.jyu.fi/~shang/portals)