I almost never read anyone discussing this subject so i decided to create a tutorial thread to explain how to do a reasonably complex dialog rule-based AI system, using PyCLIPS.
What is PyCLIPS and CLIPS?
Just like people use a physics system, for example PyODE to work with the physics simulation side of their games instead of doing it by hand it is becoming common, even among mobile phone games, to use a rule-based system to handle both the data and the behaviors of a game.
The advantage of using a rule-based AI system is speed. When we have hundreds of rules or more the RETE algorithm, used in these systems, can greatly speed up testing rules and database access. The limit of 100 rules can be easily break even with a simple shooter gamer if you decide to have interesting npc behaviors in combat.
Another advantage is that a rule-base system integrates it’s own database system where we can do queries similar to sql and thus have a much easier time processing our data.
The disadvantage is that it’s syntax it’s a little LISP like (the braket hell) and it deals with more advanced programming concepts which scares people away. However you don’t have to use every single feature CLIPS provides and once you understand the basics you will welcome the advantage of using more advanced programming techniques and improved algorithms that can greatly speedup your game AI.
How do I install PyCLIPS?
PyCLIPS which is a binding to the CLIPS system, written in C. To use it you only need to download PyCLIPS (just goggle CLIPS or PyCLIPS to find the sites) and install it in your Panda3D python directory. I’m sure you already know how to install packages in python so i won’t explain this. You may want to check CLIPS site to obtain the user and programmer manuals for CLIPS.
What is this tutorial about?
This dialog tutorial will focus on reproducing the dialog of Starflight with an alien race called the Mechan9.
For more information on Starflight and it’s Dialog system see these pages.
starflt.com/starflt.php?ID=d … n%20System
This is a list of dialog lines used by the Mechan9 race.
starflt.com/starflt.php?ID=SF1Table#MECHAN
What are the basics of using PyCLIPS and a data-driven rule-based system?
A rule-based system like PyCLIPS supports the concepts of environment, facts, rules, agenda and activation.
An environment is comparable to a database system which collects rules and facts which in turn are comparable to table records.
I will use an environment to handle Mechan9 dialog. It will collect the facts known by the Mechan9, their dialog lines and the rules that dictate their answers to dialog events.
Here’s how to create an environment using PyCLIPS. To run this example open a dos prompt and start the python console, then write the following:
import clips
clips.Clear()
clips.Reset()
mec9 = clips.Environment()
mec9 will now hold a reference to a CLIPS environment and can be used to add facts and rules to that environment.
Let’s add a few facts. This is much like a table record. It’s written as a list of tokens inside parenthesis. The first value is always a name that identifies the record “type”, following by a list of values. There are two forms for facts values, a list of values or a list of (key value) pairs. We can’t mix the two syntaxes.
All these are examples of facts:
(game-won 0)
(mec9-answered-questions 0)
(item (id “hyperion”) (energy 20) (weight “10 tons”) (volume “100 m^3”))
To add these facts to the mec9 environment above we write:
mec9.Assert('(game-won 0)')
mec9.Assert('(mec9-answered-questions 0)')
mec9.Assert('(item (id "hyperion") (energy 20) (weight "10 tons") (volume "100 m^3"))')
// use this to list available facts
mec9.PrintFacts()
// or browse them as a Python, however this is very inefficient
mec9.FactsList()
A rule is something that tests for the existence of certain facts and data in your database and if true executes the commands associated to that rule. The first are called predicates or antecedent of the rule and the latter the actions or consequent of the rule.
An example of adding a rule to the mec9 environment.
mec9.Assert("(duck)")
r0 = mec9.BuildRule("duck-rule", "(duck)", "(assert (quack))", "the Duck Rule")
// use this so see the facts and rules
mec9.PrintFacts()
mec9.PrintRules()
// use this to print a rule for which you have an handle in pretty print form
print r0.PPForm()
// the agenda will show activations for rules,
// in this case it will show one activation with an id of 0
// activations are the rules that are true and for which their
// consequent will be executed
mec9.PrintAgenda()
Now that we have a rule and we know it is activated (because of the (duck) fact) how do we execute it’s consequent? Being activated by itself doesn’t automatically executes anything. For this we need to call the method Run with an optional argument to control the number of steps taken.
mec9.Run(1)
This method returns the value 1 one which is to say it has executed the single rule that was activated. This was the command in clips syntax (assert (quack)), and now if you print the list of facts again you will see there is a new fact.
Executing mec9.PrintAgenda() will show there are no rules activated so if mec9.Run(1) was executed again it would return 0 meaning no rule was executed. The situation would be different if there was a rule whose antecedent was ‘(quake)’.
Some confusion often arises here because people expect the rule to be activated again on the second step. After all the rule antecedent is (duck) and there is a (duck) fact so why isn’t the rule activated again? This is because the rule has already been executed and it is marked as such in the agenda (even if we don’t see it). Once a rule has been executed and his marked as such it is never executed again. To loop the execution of a rule there is an alternative which i will explain later.
For an environment with around 100 rules the agenda is modified almost instantly when a assertion is made. Compare this gain in efficiency to having to iterate over the entire list of rule to see those that will be activated by a change in the database.
To be continued…