RC++: a rule-based language for game AI
Ian Wright James Marshall Sony Computer Entertainment Europe Waverley House, 7-12 Noel Street London W1V 4HH E-mail:
[email protected] [email protected] KEYWORDS Game AI, Rule-Based Languages, Tools, Agents
ABSTRACT "Game AI" is the high-level control code for computer entertainment applications. Games are diverse, and the nature of game AI reflects this diversity. However, game AI, in contrast to rendering code and other game code, is distinguished by employing a high density of predicates on rapidly changing game state, and a high density of operations that change game state. Traditional programming languages, such as C and C++, are not designed to facilitate the creation of such code, unlike high-level AI rule-based languages. RC++, a full-featured, rule-based extension to C++, designed for Sony Computer Entertainment’s PlayStation2 hardware, is described. RC++ is based on OPS5 (Forgy, 81) and Poprulebase1, a rule-based extension to the Pop-11 programming language. It combines optimised execution associated with OPS5 with the syntactical style and some of the useful features of Poprulebase. It is designed to facilitate the rapid creation of game AI by automating many of the common programming tasks associated with creating game rules, while maintaining the flexibility of C and C++ when required. The aim of the paper is to demonstrate the applicability of rule-based programming to game AI in general, and the usefulness of RC++ for game AI in particular.
THE RULES OF THE GAME Currently, most game AI code consists of the implementation of behavioural rules that encapsulate knowledge about a particular game domain. For example, rules to control racing cars, footballers, enemies, and so forth. There are many different types of ‘rule’. The equations of motion that control a bouncing ball are ‘behavioural rules’ in a general sense. Such rules normally involve analytical or 1
Poprulebase is part of the Poplog programming system, which contains the languages Pop-11, Prolog, Lisp, ML, and the SIM_AGENT toolkit. It is freely available from http: // www.cs.bham.ac.uk/ research/ poplog/ freepoplog.html.
numerical solutions to equations involving real numbers. In contrast, a rule to decide whether a footballer should pass or shoot involves conditions on world state, which may be represented not just by real numbers, but by symbolic representations of states-of-affairs, or combinations of such quantitative and qualitative information. These two types of behavioural rule require very different implementation strategies. Physical equations, in particular, require techniques from numerical mathematics, whereas ‘if-then’ rules require a different approach. This paper presents a language, called RC++, which is designed to facilitate the coding of ‘if-then’, as opposed to physics-based, rules. There are also many different types of game ‘agent’. A bouncing ball does not need to remember facts about its world, whereas a virtual footballer needs to remember where his team-mates are, what the current team strategy is, and so forth. Many games implement both kinds of agent: passive objects governed by approximations to physical laws and active agents constrained by those laws, but able to store information, make decisions, and perform actions that change the virtual environment. Examples are balls (passive, controlled by physical equations) and footballers (active, controlled by rules) in a football game, or cars and drivers in a racing game. The distinction between passive objects and active agents is reflected in the game industry’s distinction between physics modelling and game AI. Implementing behavioural rules for active agents is required by all games and is an everyday task for the game AI programmer. Rules are to AI as polygons are to graphics. The aim of RC++ is to provide methods and tools that allow easy scripting of high volumes of predetermined or logicbased events for game characters to perform, and ease the implementation difficulties of more sophisticated techniques required for correspondingly more intelligent behaviour.
PROCEDURAL LANGUAGES C and C++ are fundamentally procedural languages. They are not ideally suited to writing rule-based AI code. Game AI code, in contrast to other game code, tends to be condition-rich, requiring many tests on state and conditional branching based on the results of those tests. A procedural implementation, such as a C implementation, codes condition checking on state as a succession of ordered ‘if’ or ‘switch’ statements. There is nothing intrinsically wrong
with this approach. However, it does require the programmer to not only specify the condition and the actions to be executed if the condition holds, but also under what circumstances the test should be checked, where and how the predicated state should be stored, how the predicated state should be accessed, and when and how the state should be created and deleted. In addition, if the condition needs to be checked against multiple instances then the programmer must write looping code, including determining under what conditions the loop should terminate. Finally, the procedural programmer must incrementally abstract parts of conditions as separate function calls for reuse in other conditions: this takes time, and, if not performed, leads to a proliferation of 'same-but-slightly-different' conditional statements and code bloat. In summary, development times are increased by the need to perform a large number of peripheral programming tasks: it is never simply a matter of writing ‘rules’. Hence, scripting languages for game AI based on procedural languages, such as Java or subsets of C, are misguided: they are too similar to the main game language. The extra complexity introduced to the development process is not offset by any new language advantages. And any benefits obtained from the extra level of indirection between AI code and game engine code introduced by using a procedural scripting language can be also be obtained by simply implementing a good AI/game engine interface. In contrast, RC++ is specialised for writing game AI. It partially automates many of the peripheral tasks associated with creating game AI code. Programmers, and eventually designers, are able to concentrate on writing the rules themselves.
RULE-BASED LANGUAGES RC++ is a rule-based extension to C++. Rule-based languages are based on a simple model of computation that is particularly suited to the direct encoding of large collections of highly conditional knowledge that constitutes intelligence within a particular domain, and hence are ideally suited to controlling intelligent agent behaviour. For example, they form the basis of all expert systems (e.g., the CLIPS language (Giarratano, 93), and JESS (Friedman-Hill, 00), a rule language written in JAVA), a technology widely used, and also agent toolkits (not yet widely used), such as SIM_AGENT (Sloman & Poli, 95; Baxter et al., 97). Rule-based languages are executed by a production system, which consists of a working memory (WM) that stores information or ‘assertions’, a ruleset that defines under what circumstances assertions in WM should change, and a scheduler that repeatedly applies rules to assertions in WM. Every rule has a condition part, a boolean expression that can be checked against assertions in WM, and an action part that alters assertions in WM. For example, a production system might have the following assertions in WM: [Person ^Name Fred ^is 10]
[Person ^Name [Person ^Name // A ‘Person’ // and ‘^is’. // the second
Mary ^is 11] John ^is 17] data type has two attributes: ‘^Name’ The first attribute contains labels, attribute contains numbers.
and a ruleset consisting of a single rule: RULE Rule1 [Person ^Name ?name ^is ?years] // This condition has rule variables that // are consistently bound to values of // matching assertions in WM. WHERE [?years > 12 && ?years < 20] // A constraint on the values that the rule // variable ‘?years’ may take. -> ADD [Teenager ^Name ?name] // An action that adds an assertion to WM. ENDRULE
The scheduler runs and checks every rule in the ruleset against every possible matching combination of assertions in WM. In this example, ‘Rule1’ is checked against the three assertions in WM. Only the assertion [Person ^Name John ^is 17] causes the rule condition to evaluate to true and the action part to execute. After processing the new WM looks like this: [Person ^Name Fred ^is 10] [Person ^Name Mary ^is 11] [Person ^Name John ^is 17] [Teenager ^Name John]
Rule-based computation is modelled as the continual application of condition-action rules on a data set. Rulebased programming is computationally complete: anything that may be implemented procedurally can also be implemented as a rule-based program. There are many advantages to a rule-based approach compared to procedural programming: (i) Production systems automate rule checking and rule execution; therefore, the programmer only writes rules, not code to invoke rules, to check conditions, or to execute actions. (ii) There is no need to write looping code over multiple instances. (iii) There is no need to write code to maintain variables and state storage. (iv) The RC++ compiler automatically shares condition checks between rules to increase run-time efficiency and reduce code bloat. (v) Rule logic is separated out from all other code, making it easier to find that logic, read it, change it, test it and re-use it. (vi) Pattern-matching rules efficiently support certain types of search and matching over information items, useful for writing code that requires complex condition checking. (vii) Rule-based source code is more concise than the equivalent procedural source code. (viii) Reasoning about rules is easier and more natural than reasoning about algorithms with loops and branches. (ix) Individual rules can be added, altered or removed more easily than changing the logic within procedural code. (x) There is a more direct relationship between specification and implementation: programmers can more directly translate behavioural rules into code.
However, in general a rule-based program will consume more resources, both in time and space, compared to a procedural equivalent. But compared to Prolog or Prologlike languages, such as CML (Funge, 98), rule-based languages normally run faster. In general, there is a trade-off between expressive power and run-time performance. Much of the development work of RC++ has been concerned with developing efficient run-time algorithms. Most production systems employ a version of the RETE algorithm (Forgy, 82) to increase the run-time efficiency of rule processing. The details of the RETE algorithm are not important here but, briefly, the RETE algorithm maintains a network of dependencies between rules and data to allow run-time caching of partial results to reduce the amount of condition checking required. The RC++ rule executor is also based on the RETE algorithm. Our executor differs in certain important ways from RETE, and represents a compromise approach between RETE and the TREAT algorithm (Miranker, 90). This work will be reported elsewhere.
is compiled by
RC++ source file. Either rule code only, or a mixture of rule code and C/C++.
included by
RC++ compiler generates
C++ source file containing opcode rule program
RC++ object file containing opcode rule program
C++ source file
C/C++ game code
is compiled by
Static rule linking
C/C++ compiler
Dynamic rule linking
RC++ runtime execution library
Executable game code is loaded at run-time by
Given the relative inefficiency of rule-based languages and the fact that most game code is currently written in C or C++, there is a need to mix and match rule code with procedural code. RC++ allows programmers to 'drop-down' to procedural programming or machine code to perform functions that are more naturally expressed in imperative form, or benefit from the faster run-time execution of C or C++. RC++ extends C and C++ with rule-based constructs. Hence the name “Rules C++” or RC++. There are many commercial and shareware implementations of rule-based systems each with different design details. Most are used for business applications, such as expert systems, automatic ordering systems, large database management and so forth. The SOAR cognitive modelling language, also based on OPS5, the original rule-based programming language, has been used in games (Laird, 00). We developed RC++ because we wished to introduce certain language features designed specifically for building realtime game agents, and also enjoy full control over both compile-time and run-time optimisation for a specific target machine.
RC++ RC++ consists of a (i) rule language compiler, and (ii) a runtime executor, which runs on Sony’s PS2 hardware, available as a library that may be linked with C or C++ code. Ninety percent of the language features are implemented and we are about to use it in a commercial PS2 game. An RC++ program may be a mixed C++ and rule source file, or a rule source file only. Rule programs may be written within C++ files, if desired. C++ code can refer to rule code, and rule code can refer to C++ code. RC++ provides static or dynamic linking of rule programs. (see figure 1). Therefore, rule programs may be loaded at run- time, thus conserving memory by allowing AI code to be swapped in and out of RAM during game execution.
Figure 1: RC++ compiler operation
Rules can predicate on WM, or other game state stored within C or C++ data structures, including objects. Similarly, rules can act on WM, or other game state defined in native code. Unlike most scripting systems RC++ provides a great deal of flexibility for communication between the specialised AI language and other game code. Rule code can be embedded within C++ classes, and SENSE and ACTION rule classes may be defined to provide structured interactions between a rule program and its 'world', as defined by the surrounding C or C++ code. RC++ allows the creation of hierarchies of rule-based agents able to act in their virtual worlds via sensing and acting operations. A rule program, or a combination of rules and native code may perform internal agent computations.
An Example RC++ Agent To illustrate some aspects of RC++ in more detail we will describe a simple example. A full explication would require a much longer paper, and therefore many RC++ language features are not discussed. We wish to build a simple agent that moves towards its friends and tries to kiss them. Our agent is such an egotist that it maintains a record of who it has kissed and how many times. The agent is to be implemented as a C++ class, so we can easily instantiate multiple copies of our agent, and also define sub-agents classes that inherit from the super-agent classes. The agent's ‘thinking’ (of a very simple kind) is to be defined in terms of rules but the engine programmer has implemented the agent's virtual world in C++, so we need to be able to define communication paths between rules and C++ code. First, we define the agent's ‘thinking’ in terms of rules. A rule program can be encapsulated within a RULESET that
has an associated WM. A ruleset may contain three items: (i) a collection of assertion type declarations, which declare what kinds of data exist in WM that rules may operate upon; (ii) a collection of assertions that define the initial state of WM; and (iii) the rules themselves. Here is an RC++ ruleset with only assertion type declarations shown: RULESET Agent [ SENSE CLASS [Sense ^Name LABEL ^Location NUMBER] ACTION CLASS [Kiss ^Name LABEL] CLASS [Friend ^Name LABEL] CLASS [Kissed ^Name LABEL ^Times NUMBER] [ // Initial ruleset WM state here // … for example: [Friend ^Name Sally] ] // Rules within ruleset specified here … ]
The ruleset declares four different kinds of assertions via the CLASS keyword. For example: CLASS [Kissed ^Name LABEL ^Times NUMBER]
declares an assertion type 'Kissed' that has two attributes, called 'Name' and 'Times'. An attribute can store information, which may be one of the basic types, LABEL or NUMBER. Our agent will use the 'Kissed' class to store information about who it has kissed and how many times. Assertion type declarations may be prefixed with either SENSE or ACTION keywords. A SENSE assertion may be added to WM by a non-rule-based process, such as arbitrary C++ code. It is via instances of SENSE classes that state from the game world is translated into WM assertions. An ACTION assertion automatically calls C or C++ 'handlers'. It is via instances of ACTION classes that WM assertions are translated into calls that may change state in the game world. SENSE and ACTION classes define the inputs and outputs respectively to the rule-based engine within our agent. For brevity we will examine a single rule in the agent’s ruleset: RULE KissMyFriendsWhenClose a = [Sense ^Name ?name ^Location ?location] [Friend ^Name ?name] WHERE [AgentClass::GetMyLocation() == ?location] b = [Kissed ^Name ?name ^Times ?times] -> ADD [Kiss ^Name ?name] DO [?times = ?times + 1] MODIFY b [Kissed ^Times ?times] DELETE a ENDRULE
The first condition matches any sensory information derived from the C++ world via the SENSE class. The agent’s WM contains a list of friends and the second condition checks that the name of the object sensed is a name of a friend. The third condition specifies a constraint: the location of the friend must be the same as the agent’s location. Note that in this implementation access to location information is achieved by explicitly calling the C++ utility function, ‘GetMyLocation’. This illustrates a general feature: rules may arbitrarily invoke C++ functions and therefore may
predicate on native source state. For example, it may be more efficient to implement physics-based calculations or 3d calculations in native source, and allow rules to call such utilities as required. The third condition matches with a ‘Kissed’ record for the sensed friend. The action part of the rule contains four actions. The first action adds an ACTION assertion to WM: this has the sideeffect of invoking a C++ handler function, which will translate the assertion to actions in the virtual world (i.e., perform the actual ‘kiss’ in the C++ world, which might, for example, trigger some non-rule-based, facial animation code). The second action performs an imperative operation: it increments the ‘kiss’ count. The third action modifies the assertion in WM that matched condition b. The effect is to increment the appropriate ‘kiss’ count in WM. The final action deletes the sensory information from WM: this agent doesn’t remember much from one frame to the next. The rule performs an action to ‘kiss’ any friends that are very close by, and maintains a record of how many times this particular friend has been kissed. Note that this single rule will operate over multiple sensory items. There is no need to write looping code to deal with lots of friends in the same location. Now that the rule-based ‘thinking’ part of the agent is specified we need to define a C++ class that contains the rule program, and any associated utility and handler functions. // Agent C++ class declaration for ‘kissing’ agent class AgentClass: public RCAgentClass // inherits from abstract RC++ agent class { public: AgentClass(void); // Agent constructor instantiates the rule // program from the op-code data // Declares C++ handlers and utilities to rule // program. RCValueType GetMyLocation(void) const; // utility function referenced by rule void KissHandler($HANDLER AgentClass::Kiss$); // handler function that is invoked when the // corresponding ACTION class is added. // Any other associated agent functions, such as: void Execute(void); // Run the agent private: // Agent data and pointer to ruleset // instantiation };
Run-time construction of an AgentClass object will instantiate a WM and ruleset from the rule op-code program, which has been created by the RC++ compiler (see figure 1), and also declare the agent’s utility and handler functions to the rule executor. Execution of the agent object will add assertions to WM representing sensory updates, and also invoke the RC++ executor, which runs the rule program. The rule program may invoke C++ utilities and handlers, which implement agent actions. The cycle of sensing, thinking and acting is therefore accomplished. Although this example is necessarily brief it provides an indication of how RC++ integrates with native source code. RC++ allows game AI to be written with the benefits of
high-level rule-based programming and lower-level procedural programming. Agent classes can be defined that encapsulate rule programs. Hence, hierarchies of agent classes can be defined that override handler and utility functions, or replace some rule code with alternative rule code. If desired, rule code can be isolated from surrounding C/C++ code by restricting inter-language communication to ACTIONs and SENSEs, with all the benefits of reusability and modularity that implies. Rule code can be changed and altered in relative isolation from the C/C++ game engine. Once action and sense operations have been defined, game AI programmers can work in parallel with engine programmers, safe in the knowledge that interactions between the two code bases need not break the game.
Other Features RC++ supports rulefamilies, which are collections of communicating rulesets, allowing more modular rule programs to be written. NOT conditions (conditions that evaluate to true if no assertions match the specified pattern) are more sophisticated than normally allowed in OPS5. Rulesets can be REACTIVE and function as either simple stateless look-up tables or finite state machines. Rulesets can be ORDERED to specify a priority ordering over rules allowing some rules to be executed in preference to others. RC++ provides run-time trace facilities that include output of rule source instantiations with variable bindings, which is much easier to debug than pure op-code traces. RC++ integrates with a Process Manager (Wright & Marshall, 00); therefore, rule execution can be interrupted at a fine granularity in order to maintain constant visual frame-rates under variable AI load.
Future Plans In addition to testing RC++ in a real-time, arcade-style game, we hope to integrate RC++ with the EMOTER AI engine (Whittakker et al., 99), in order to combine the benefits of minimal state, fast execution finite-state machines with more flexible but slower execution RC++ rulesets. This will provide us with a flexible AI toolkit for building agent architectures with fast, stupid reactive layers and slower, more intelligent deliberative layers. Also, to allow non-programmers, such as level designers, to begin directly creating AI content we need to provide a userfriendly editor for creating RC++ rules, which hides the language syntax and provides rule and agent templates. These are the kind of engineering tools the game industry needs in order to begin delivering on its content promises.
CONCLUSION RC++ is a rule-based extension to C++, running on the PlayStation2, designed to allow easy scripting of high volumes of predetermined or logic-based events for game characters to perform, and ease the implementation difficulties of more sophisticated techniques required for correspondingly more intelligent behaviour. RC++ integrates with C++ allowing developers to use a high-level, rule-based
language for game AI, but drop down to more efficient C and C++ as required.
REFERENCES Baxter, J.; R. Hepplewhite; B. Logan, and A. Sloman. 1997. SIM_AGENT two years on. Technical Report CSRP-98-2. School of Computer Science, University of Birmingham, UK. Forgy, C.L. 1981. OPS5 user’s manual. Department of Computer Science Technical Report, Carnegie-Mellon University, 1981. Forgy, C.L. 1982. Rete: a fast algorithm for the many pattern/many object pattern match problem. Artificial Intelligence 19, 17-37. Friedman-Hill, E.J. 2000. Jess, the Java expert system shell. Available from http:// herzberq.ca.sandia.gov/ jess/. Funge, J. D. 1998. Hardcore AI for computer games and animation. Course Notes 10, SIGGRAPH 98, Orlando, FL, July 19-24, 1998. Giarratano, J.C. 1993. CLIPS User’s Guide. Technical Report, NASA, Lyndon. B. Johnson Space Center, Information Systems Directorate, Software Technology Branch. Available from http:// www.mines.u-nancy.fr/ ~gueniffe/ CoursEMN/I41/ cldoc.html Laird, J.L. 2000. It knows what you’re going to do: adding anticipation to Quakebot. AAAI 2000 Spring Symposium Series: Artificial Intelligence and Interactive Entertainment. March 2000: AAAI Technical Report SS-00-02. Miranker, D.P. 1990. TREAT: A new and efficient match algorithm for AI production systems. Research Notes in Artificial Intelligence, Morgan Kaufmann Publishers. Sloman, A. and R. Poli. 1995. SIM_AGENT: a toolkit for exploring agent designs. In Wooldridge, M., Mueller, J., & Tambe, M. (Eds.), Intelligent Agents Vol II, pages 392-407. SpringerVerlag. Whittaker, A.; T. Riolfo; and N. Rowlands. 1999. An object model for behavioural planning ina dynamic multi-agent system. Behaviour Planning for Life-like Characters and Avatars. 13 Spring Days, Elizabeth Andre ed. (1999). Wright, I. and J. Marshall. 2000. More AI in less processor time: “egocentric” AI. Gamasutra Online, http:// www.gamasutra.com/ features/ 20000619/ wright_pfv.htm.