Unsing PyCLIPS to build a rule-based data driven game

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…

This sounds really interesting. Thanks for sharing!
I’ll definately look into this whenever I have some more time.

Yeah this is cool!

Maybe i can use this for rts AI (because its kind slow)… i still have to think more about it though.

Using PyCLIPS as a database system.

If we don’t wish to use PyCLIPS as rule based system for our AI we can still use it as a database system. It allows the usual sql operations of join, union and intersection at least. Besides PyCLIPS offers commands to load and save the entire fact list which is very convenient.

Without using something like PyCLIPS the direct approach is to create collections and classes to store the data. Create query functions and a solution to save and load data from disk. In Python this is a lot of work and since Python wasn’t build as a relational database system it won’t work as easily and as fast as in using PyCLIPS.

To show how this works i’m going to represent some game data for this tutorial as CLIPS facts and show how to do simple queries.

Starflight dialog happens during encounters with the player ship and an alien ship or fleet.

The hailing stage precedes the dialog stage. This is when both parties hail each other with an hailing message. The alien hails the player or the player hails the alien and when one hails and the other responds dialog starts. The player can choose a posture to use when hailing or responding and sometimes the wrong choice of posture will make the alien not interested in answering and leave the encounter.

During dialog the player can change posture, make a statement, request info on a certain subject or terminate communications. Aliens can, change posture, make a statement, ask questions, propose trading, answer info or terminate communications.

So we need a list of messages to for a certain purpose and a certain posture. The possible dialog postures are diplomatic, friendly agressive or obsequious.

The best way to do this is to write the facts in CLIPS syntax in a separate text file and then load it from PyCLIPS with the proper command.

This is the file with the messages:

;;; Player hailing

(dialog player hailing obsequious ph1)
(dialog player hailing obsequious ph2)
(dialog player hailing obsequious ph3)
(dialog player hailing obsequious ph4)
(dialog player hailing diplomatic ph5)
(dialog player hailing diplomatic ph6)
(dialog player hailing diplomatic ph7)
(dialog player hailing diplomatic ph8)
(dialog player hailing diplomatic ph9)
(dialog player hailing agressive ph10)
(dialog player hailing agressive ph11)
(dialog player hailing agressive ph12)
(dialog player hailing agressive ph13)
(dialog player hailing agressive ph14)


(text ph1
"Hail oh mighty ones, masters of the universe. We bow to your wonderful 
magnificence and ask that you do not harm our insignificant selves.")

(text ph2
"Greetings oh highest of the high, most great alien beings. We come in peace 
from the planet arth, and respectfully request that you identify your vastly 
superior selves.")

(text ph3
"This is captain $captain_name of the starship $ship_name. We humbly suggest 
that you may wish to identify yourselves as well. If not, that is perfectly 
o.k.")

(text ph4
"I am captain $captain_name of the vessel $ship_name. Greetings and 
felicitations oh kind and merciful aliens.")

(text ph5
"I am captain $captain_name of the starship $ship_name. We are on a peaceful 
exploration mission.")

(text ph6
"Hello. We come in peace from the planet arth.")

(text ph7
"Greetings. We come in peace and wish to communicate, please identify 
yourselves.")

(text ph8
"This is captain $captain_name of the $ship_name. We are on a peaceful mission 
and request communications. Please identify yourselves and your intentions.")

(text ph9
"We come in peace and bring greetings from the planet arth. Please respond.")

(text ph10
"This is captain $captain_name of the warship $ship_name. We are prepared to 
spare you if you comply with our demands.")

(text ph11
"We have no patience for your foolishness. You will cooperate with us or you 
will be destroyed.")

(text ph12
"This is captain $captain_name of the starship $ship_name. 
Identify yourselves immediately or be destroyed.")

(text ph13
"This is captain $captain_name of the powerful starship $ship_name. 
You will cooperate and identify yourselves immediately or be annihilated.")

(text ph14
"This is the starship $ship_name. We are heavily armed. We require information. 
Comply or be destroyed.")

To load this database use the python code:

import clips
clips.LoadFacts("my facts file.txt")
clips.PrintFacts()

We should have all the facts loaded. Now do run a queries we will use rules.

For example all player hailing messages done in agressive posture. We have two different types of facts (dialog and text) which is similar to having two different sql tables.

To obtain the info we need we will do the equivalent to an sql join operating on the antecedent of a rule and the run the rule. The symbols that start with ? are variables similar to those used with the sql select command.

clips.BuildRule(
"example1", 
"""
(dialog ?who hailing obsequious ?textid)
(text ?textid ?text)
""",
"""
(printout t ?text t t)
""")

clips.PrintAgenda()
clips.Run()
print clips.StdoutStream.Read()

The print agenda command before the run command shows how many times the rule will be activate and the facts that caused the activation.

For each activated rule the contents of variable ?text is written to CLIPS standard output stream.

The print after the run command dumps CLIPS stdout stream into python console.

In conclusion each rule can be seen as a SELECT command and each activation one result from the list of matches. The body of the rule is executed as many times as the number of activations.

In the next post of this tutorial i will show you how to register python functions to use in the consequent of a rule so that rules may actually affect a game. The opposite (the game affecting the AI system) is usually done with facts using the clips.Assert(fact) command from python where we use a fact to provide game aware data to the AI system.

Ok, short question… if you want to use CLIPS as a database system, why don’t you use a database system then? :slight_smile:

As a noob, CLIPS seems to be a bit above my limits.
A summary I got from the thread: with higher item/actor count CLIPS will make sense because its less cpu intensive.

What else would it make worth learning another syntax besides Python?

Regards, Bigfoot29

If you already know how to use rel database system like mSQL for example and don’t need a rule-based system then use what you are most comfortable with. I would still use clips as a database because it’s much easier to work with than having to write sql commands.

The problem noobs usually have is with building the AI for their games and not with using the tools available. Try doing, for example, a PACMAN game and create the AI for the ghosts, using a global AI update method with a list of if-then-else commands. It’s a accessible challenge and you wont need to do any searches. Try doing this in Python and then come back and read this tutorial again.

Don’t forget that you get a database system for free.