Clips Tutorial
Clips Tutorial
(clear) Removes all rules and facts from memory. Equivalent to shutting down and
restarting CLIPS.
(reset) Removes facts information from memory (but not rules) and resets the
agenda.
(run) Starts executing a CLIPS program.
The above commands can also be executed from the CLIPS menu bar.
The <Fact-0> part is the response from CLIPS to say that a new fact (fact number 0)
has been placed on the fact database. The (facts) command will list all current
facts. Try it, and you’ll get the following:
CLIPS>(facts)
f-0 (colour green)
For a total of 1 fact.
Facts may also be retracted (removed) from the fact database by using the retract
command. As an example, assert two facts as shown:
Then retract the first fact and display the fact list:
CLIPS>(retract 0)
CLIPS>(facts)
f-1 (colour red)
For a total of 1 fact.
There are two things to note here: firstly, to retract a fact you must specify a
number (the fact-index), not the fact itself, and secondly, fact-indices are not
reused. Once fact 0 has been retracted, the next fact asserted will have the index 2,
not 0.
Facts on their own are of only limited use. The application of rules is necessary to
develop a program capable of some useful function. In general, a rule is expressed in
the form ‘IF something is true THEN do some action’. This kind of rule is known as a
production. For this reason, rule-based expert systems are often known as
production systems (CLIPS actually stands for C Language Integrated Production
System). In CLIPS, a typical rule looks like this:
(defrule duck
(animal-is duck)
=>
(assert (sound-is quack)))
The rule consists of three parts. The first part, (defrule duck, simply gives the rule
a unique name. The second part, (animal-is duck), is the pattern (the IF part) of
the rule and the last part, (assert (sound-is quack)), is the action (the THEN
part). In plain language, this rule means ‘if there is a fact (animal-is duck) on the
fact database, then assert another fact, (sound-is quack), onto the fact database’.
Try it. Clear the system, then type in the rule exactly as printed above. Typing
(rules) will give you a list of rules (just the one, in this case) present in the system.
At this point, there are no facts present. Now, type (assert (animal-is duck)).
Check the fact list - there’s one fact. To trigger your rule, type (run). Although
nothing appears to happen, if you check the fact list again you’ll see that there is a
new fact, (sound-is quack), which has been inferred by the rule. This is the power
of rule-based programming - the ability to make inferences from data, particularly as
the results of one rule can be used as the pattern for another. Add the rule
(defrule is-it-a-duck
(animal-has webbed-feet)
(animal-has feathers)
=>
(assert (animal-is duck)))
Then type (reset) to clear the facts (the rules will be untouched). Note that this rule
has two patterns. Both must be satisfied for the action to be taken. This translates to
‘IF the animal has webbed feet AND the animal has feathers THEN the animal is a
duck’ (taxonomists and pedants may disagree with this rule). If you now assert the
facts (animal-has webbed-feet) and (animal-has feathers) there will be two
facts present. (run) the rules, and suddenly there are four. Firstly, rule is-it-a-
duck has fired, asserting the fact (animal-is duck). This fact has then triggered
rule duck, which has asserted the fact (sound-is quack). Very powerful systems
can be built using this ability to chain rules.
Asserting facts is a rather unsatisfactory way of presenting results. Type in the first
rule again, this time with the multiple actions as shown below:
(defrule duck
(animal-is duck)
=>
(assert (sound-is quack))
(printout t "it’s a duck" crlf))
Next time you run the rules, you'll get a message on screen as well as the asserted
quack fact.
It’s rather inefficient having to type all your rules in each time you run
CLIPS. Fortunately, you can load them from a file using the ‘Load
Constructs..’ command on the file menu. CLIPS will expect a file with the
extension .CLP, and there’s a handy editor to help you create them. You
can’t put facts in a .CLP file in the same way as you can from the command prompt,
so for now you’ll still enter them as before.
Here’s a more
complex example
of rules and facts.
The decision tree
opposite
represents a small
section of the
diagnosis of a
car’s failure to
start. Each
rounded box is a
recommended
remedy. Each
rectangular box is
piece of evidence,
which might be
represented by a
fact such as
(lights-working
no) or (petrol
yes). Each connecting path to a remedy represents a rule, for example ‘IF starter is
turning AND there is no petrol THEN buy some petrol’.
CLIPS Tutorial 2 - Patterns and actions
Persistent facts
For the following exercises, you will need to use the same set of facts several times.
Rather than type them in repeatedly, you should use the deffacts structure. This is
a way of specifying facts which are recreated every time a (reset) is executed. For
example, the code
will assert three facts onto the database every time the system is reset. Once they
are asserted, the facts are the same as any others - they can be retracted or used in
rule patterns - but even if they are retracted they will reappear after a (reset).
Below is the list of facts you will need to use - use the CLIPS editor to enter them in
a deffacts structure then reset CLIPS and look at the fact list to check that they are
present.
Matching things
So far, the patterns used to match rules against facts have been very simple and
rather restrictive. Each pattern has matched one specific fact. By using wildcards, it
is possible to make rules match multiple facts, executing their actions repeatedly.
For instance, the rule:
(defrule animal
(animal ?)
=>
(printout t "animal found" crlf))
CLIPS>(run)
Animal found
Animal found
Animal found
Animal found
CLIPS>
Which shows that it has triggered four times, once for each fact matching the
(animal ?) pattern. In this pattern, the ? symbol is a wildcard. It will match any
symbol. You can use as many wildcards as you like in a pattern, but the first symbol
may not be one. So (child-of ? ?) is legal and will match four facts, but
(? ? hatchling) is illegal.
Variables in patterns
Simple wildcards are only mildly useful. Variables make them indispensable. If we
use something like ?var instead of ? on its own, we can use the value of ?var each
time the rule is fired. Try this example:
(defrule list-animals
(animal ?name)
=>
(printout t ?name " found" crlf))
CLIPS>(run)
turtle found
duck found
cat found
dog found
CLIPS>
The rule has matched four facts, and each time the variable ?name has taken the
value of the symbol it represents in the pattern, so that in the action part of the rule
it can be printed. The real power of this feature is apparent when two or more
patterns are used, as in the next example:
(defrule mammal
(animal ?name)
(warm-blooded ?name)
(not (lays-eggs ?name))
=>
(assert (mammal ?name))
(printout t ?name " is a mammal" crlf))
You may notice the not function sneaked in there. The purpose of this should be
self-evident. This rule gives the results
CLIPS>(run)
cat is a mammal
dog is a mammal
CLIPS>
When you are satisfied that you understand how this works, try the next step:
(defrule mammal2
(mammal ?name)
(child-of ?name ?young)
=>
(assert (mammal ?young))
(printout t ?young " is a mammal" crlf))
After you have run this rule, look at the fact list
CLIPS>(run)
kitten is a mammal
puppy is a mammal
CLIPS>
(defrule remove-mammals
?fact <- (mammal ?)
=>
(printout t "retracting " ?fact crlf)
(retract ?fact))
In the pattern part of this rule, the variable ?fact is given the fact-index of each fact
matching the pattern (mammal ?) in turn. That's what the leftwards arrow (<-)
symbol means. When you run it, this is what happens (the fact numbers may be
different):
CLIPS>(run)
retracting <Fact-13>
retracting <Fact-14>
retracting <Fact-15>
retracting <Fact-16>
CLIPS>
(defrule take-umbrella
(or (weather raining)
(weather snowing))
=>
(assert (umbrella required)))
Which means "if it is raining or it is snowing, then take an umbrella". Notice the way
the or comes before the two arguments, rather than between them. this is known as
prefix notation, and all CLIPS operators work this way. For example, to express a
sum of two numbers in most computer languages, you would use something like 5 +
7 (this is known as infix notation). In CLIPS, the expression would be written (+ 5
7). Examine the following examples which show the addition, subtraction,
multiplication and division operators:
CLIPS>(+ 5 7)
12
CLIPS>(- 5 7)
-2
CLIPS>(* 5 7)
35
CLIPS>(/ 5 7)
0.7142857142857143
CLIPS>
Rewrite the expression 10+4*19-35/12 in CLIPS notation and verify that you get the
result 83.0833. (Answer at bottom of page).
(defrule what-is-child
(animal ?name)
(not (child-of ?name ?))
=>
(printout t "What do you call the child of a " ?name "?")
(assert (child-of ?name (read))))
When you run it, the system will now prompt you for the name of the young of any
animal it doesn't know about. It will use the data you enter to assert a fact, which
could then be used by other rules. In the car diagnostic example of the previous
tutorial, you could have used a rule such as
(defrule are-lights-working
(not (lights-working ?))
=>
(printout t "Are the car's lights working (yes or no)?")
(assert (lights-working (read))))
CLIPS Tutorial 3
If we wish to write a rule which will be triggered by all three of these facts, we can't
easily use the ? wildcard, because it will match only one symbol. Instead, we will use
the multi-field wildcard, $?, which will match zero or more symbols, thus:
(defrule bands
(member-of ?band $?)
=>
(printout t "there is a band called " ?band crlf))
CLIPS>(run)
there is a band called ebtg
there is a band called beatles
there is a band called who
Taking this one step further, we can get a list of all members of all bands:
(defrule band-members
(member-of ?band $? ?member $?)
=>
(printout t ?member " is a member of " ?band crlf))
In the left hand side of this rule, the multi-field wildcard, $?, will match zero or more
symbols. The wildcard ?member will only match one symbol at a time. Thus, the ?
member wildcard will match once for each individual member of a band, while the $?
wildcards match to the other preceding and following members. For example, the
following table shows all the different ways this rule would match the facts for the
Beatles:
?member
Match#first $? matches last $? matches
matches
paul_mccartney
1 nothing john_lennon
george_harrison ringo_starr
2 john_lennon paul_mccartney george_harrison ringo_starr
3 john_lennon paul_mccartney george_harrisonringo_starr
john_lennon paul_mccartney
4 ringo_starr nothing
george_harrison
(defrule band-members
(member-of ?band $?members)
=>
(printout t "The members of " ?band " are " $?members crlf))
(defrule addup
(number ?x)
(number ?y)
=>
(bind ?total (+ ?x ?y))
(printout t ?x " + " ?y " = " ?total crlf)
(assert (total ?total)))
You should be aware that the temporary variable only exists inside the rule - don't
expect to be able to use the value elsewhere. If you need variables which can be
used by more than one rule or function without losing their values, you need to
declare them using the defglobal construct in a file, like in this example:
(defglobal
?*var1* = 17
?*oranges* = "seven"
)
After a (reset), there will be two global variables, ?*var1* and ?*oranges* (the
asterisks are necessary) with the values 17 and "seven" respectively, which may be
accessed by any rule. To change their values, the bind function can be used.
Functions
Rules and facts, while offering great flexibility, are not suited to all tasks.
CLIPS offers a full range of procedural programming functions as well.
(age Andrew 20)
(weight Andrew 80)
(height Andrew 188)
(bloodpressure Andrew 130 80)
(age brenda 23)
(weight brenda 50)
(height brenda 140)
(bloodpressure brenda 120 60)
But this involves lots of separate facts, with nothing to link them together other than
a single field bearing the person's name. A better way to do this is by using the
deftemplate structure, thus:
(deftemplate personaldata
(slot name)
(slot age)
(slot weight)
(slot height)
(multislot bloodpressure)
)
deftemplate does not create any facts, but rather the form which facts can take.
Every time a fact of this type is created, it contains the slots specified in its
definition, each of which can contain a value and can be accessed by name. Each
person's data can now be asserted thus:
(assert (personaldata (name Andrew) (age 20) (weight 80)
(height 188) (bloodpressure 130 80)))
(deffacts people
(personaldata (name Andrew) (age 20) (weight 80)
(height 188) (bloodpressure 130 80))
(personaldata (name Cyril) (age 63) (weight 70)
(height 1678) (bloodpressure 180 90)))
(assert (personaldata (weight 150) (age 23) (name Brenda)))
is perfectly valid. The order in which you access the slots does not matter, as you are
referring to them by name (this also allows you to set as few of them as you wish).
Template facts can be altered without the need to retract them and assert a new
version. The function modify allows you to change the value of one or more slots on
a fact. Suppose it's Andrew's birthday. If we define the rule
(defrule birthday
?birthday < (birthday ?name)
?datafact < (personaldata (name ?name) (age ?age))
=>
(modify ?datafact (age (+ ?age 1)))
(retract ?birthday)
)
then asserting the fact (birthday Andrew) will modify Andrew's age while leaving all
his other personal data intact. Incidentally, the reason we retract the birthday fact is
for the purposes of truth maintenance. It's only Andrew's birthday once a year, and if
we left the fact lying around it would soon become false. Further, every time any
other part of Andrew's personal data (weight, for example) was changed the birthday
rule would be fired again, causing rapid ageing!
(defrule lardybugger
(personaldata (name ?name) (weight ?weight))
(test (> ?weight 100))
=>
(printout t ?name " weighs " ?weight " kg the fat sod." crlf)
)
But, wait! I hear you cry - what's all this (test (> ?weight 100)) business? That's
what is known as a conditional element. It allows a rule to do a certain amount of
expression evaluation on the left hand side of the rule. In this case, it will match
facts where the value of the weight slot is greater than 100 kg. There are several
ways of employing conditional elements in rules. The first is the logical and which is
implied simply by using two patterns in the left hand side of a rule, thus:
(defrule printages
(personaldata (name ?name) (age ?age))
=>
(printout t ?name " is " ?age " years old." crlf)
)
This rule is really saying "if there is a fact with a name and an age, then print it out".
The and connective can also be used explicitly if needed, thus:
(defrule printages
(and
(personaldata (name ?name) (age ?age))
(personaldata (name ?name) (weight ?weight))
)
=>
(printout t ?name " weighs " ?weight " at " ?age " years old."
crlf)
)
Although the and is superfluous in the above example, it is very useful in more
complex logical constructs, as we shall see. We can also use the or connective, thus:
(defrule takeanumbrella
(or
(weather raining)
(weather snowing)
)
=>
(printout t "Take an umbrella" crlf)
)
Which of course means "if it's snowing or raining, take an umbrella". Notice that both
or and and are prefix operators, just like addition or subtraction, so you write (or
(thing1) (thing2)), not ((thing) or (thing2)). The other conditional element
recognisable from traditional logic is not, which simply negates the truth of a
predicate, thus:
(defrule notbirthday
(personaldata (name ?name) (weight ?weight))
(not (birthday ?name))
=>
(printout t "It's not " ?name "'s birthday" crlf)
)
Now, back to that test element. Using test allows you to check anything in the left
hand side of a rule, not just facts. So, we could write a rule which (for no readily
apparent reason) checks to see if six is greater than five:
(defrule pointless
(test (> 6 5))
=>
(printout t "Six is indeed greater than five" crlf)
)
The final two conditional elements are exists and forall. exists is satisfied if
there are one or more facts which match its predicate; the rule containing it is fired
only once regardless of how many facts it matches. forall, on the other hand,
triggers once if every fact matches its pattern. Make sure you have the personal data
of a few people on the fact base, then try the following two rules:
(defrule personexists
(personaldata (name ?name))
=>
(printout t "Rule person exists reports there is a person called
" ?name crlf)
)
(defrule aretherepeople
(exists (personaldata (name ?name)))
=>
(printout t "Rule aretherepeople reports there is at least one
person" crlf)
)
(defrule checkeachperson
(forall (personaldata (name ?name))
(
)
=>
(printout t "Rule checkeachperson reports that all persons
have a name" crlf)
)
Now try creating a new person without a name, and run them again. Rule check
eachperson will not fire because it is not true for all facts which match its pattern.
Summary
Elements introduced in this tutorial: template facts, conditional elements
(deftemplate (slot slotname1) (slot slotname1))
(and (predicate1) (predicate2))
(or (predicate1) (predicate2))
(not (predicate1))
(test (predicate1))
(exists (pattern))
(forall (pattern1)
(pattern2)
(deftemplate personal-data
(slot name)
(slot age)
(slot weight)
(slot smoker)
(multislot date_of_birth)
)
(deffacts people
(personal-data (name adam) (weight 60) (age 30)
(smoker no) (date-of-birth 18 06 1970))
(personal-data (name brenda) (weight 120) (age 45)
(smoker yes) (date-of-birth 18 06 1955))
(personal-data (name charles) (weight 120) (age 60)
(smoker yes)(date-of-birth 18 06 1940))
)
(deffacts data
(date 18 06 2000)
)
In the last tutorial, we learned about several conditional elements: and, or, not,
test, forall and exists. There is still one more: logical. The logical conditional
element assists with the perennial problem of truth maintenance. Enter and run the
following rule, using the facts defined above.
(defrule cardiac-risk
(person (name ?name) (smoker yes) (weight ?weight))
(test (> ?weight 100))
=>
(assert (cardiac-risk ?name))
)
This rule tests whether a person is overweight and a smoker. If he is, then it asserts
a warning that his heart is at risk. Now what happens if the person gives up
smoking, or loses weight? The warning fact will still be present, because it is not
linked in any way to the rule which created it. This means that the fact base is no
longer consistent with reality. We can overcome the problem by using the logical
element. Modify the rule you have just entered so that it looks like this:
(defrule cardiac-risk
(logical (person (name ?name) (smoker yes) (weight ?weight)))
(logical (test (> ?weight 100)))
=>
(assert (cardiac-risk ?name))
)
When we run this rule, the results are identical to those generated by the first
version (i.e. the facts (cardiac-risk brenda) and (cardiac-risk charles) are
asserted). The difference occurs if we change the initial data. Make sure the fact
window is open, then having run the rule, locate the fact-index of Brenda's personal
data (on my system, it's 2) and type at the command line
As if by magic, the fact (cardiac-risk brenda) disappears from the fact list. By
using the logical keyword, we have created a link between the fact asserted and
the premises on which it was based. When the premises changed, their results were
no longer valid, so they were removed. So why not make everything logical in this
way? Firstly, it increases the memory and processing time needed - in a complex
system, there can be many links to check. Secondly, it's not always appropriate - for
most applications, the standard facts are quite sufficient.
As you are now aware, the order in which rules are triggered in CLIPS is not easily
controlled. Any rule which matches a fact may be placed on the agenda in any
position. How, then, are we to establish any sort of control over our expert systems?
There are two primary solutions to this problem - by using control facts or salience.
We'll come back to salience later. A control fact is one whose only purpose is to
direct program operation rather than to express knowledge about the problem
domain. By including these control facts in the left hand sides of rules, we can
control when the rules will fire. For example, suppose we wish to check all our
personal data records to see whose birthday it is today, and update their ages
accordingly. If we have a fact of the form (date 18 6 2000) in the fact list
representing today's date, then the rule
(defrule birthdays-today
?person <- (personal-data (age ?age) (date-of-birth ?day ?
month ?))
(date ?day ?month ?)
=>
(modify ?person (age (+ ?age 1)))
)
might seem a reasonable way of doing this. Try it, and see what happens. You might
want to know that pressing ctrl-break will stop a CLIPS program. Why doesn't this
work? The reason it gets into an infinite loop is that the modify function actually
retracts the personal-data fact and asserts a new one with the updated data in it.
This new fact then matches the same rule, which causes it to be modified, and so on
until the end of time. The way around this problem is to divide the task into discrete
phases and use control facts to execute them in order. there are really two phases to
this problem - identifying all the people who have birthdays today, and then updating
their ages. We can implement this using the four rules below:
(defrule birthdays-today
(check-birthdays)
(personal-data (name ?name) (date-of-birth ?day ?month ?))
(date ?day ?month ?)
=>
(assert (birthday ?name))
)
(defrule done-checking-birthdays
?check-birthday-fact <- (check-birthdays)
(forall (and (personal-data (name ?name) (date-of-birth ?day ?
month ?))
(date ?day ?month ?)
)
(birthday ?name)
)
=>
(retract ?check-birthday-fact)
(assert (update-ages))
)
(defrule update-ages
?person<-(personal-data (name ?name) (age ?age) (date-of-birth ?
day ?month ?year))
?birthday-fact<-(birthday ?name)
(update-ages)
=>
(modify ?person (age (+ ?age 1)))
(retract ?birthday-fact)
)
(defrule done-updating-ages
?update-age-fact<-(update-ages)
(not (birthday ?))
=>
(retract ?update-age-fact)
)
The other way of controlling rule firing is by using the salience property of rules.
When a rule is declared, it may be given a salience value between -10000 and
10000, the default value being 0. All other things being equal, rules with a higher
salience are fired before rules with a lower salience. Consider the following two rules:
(defrule poke-fun-at-smokers
(personal-data (name ?name) (smoker yes))
=>
(printout t ?name " is a fool." crlf)
)
(defrule worry-about-thin-people
(personal-data (name ?name) (weight ?weight))
(test (< ?weight 80))
=>
(printout t ?name " is looking a bit thin." crlf)
)
As things stand, if you run those rules then they will both fire, but the order of their
firing will depend only upon the order in which the facts on which they depend were
created. If, however, you modify them thus:
(defrule poke-fun-at-smokers
(declare (salience 10))
(personal-data (name ?name) (smoker yes))
=>
(printout t ?name " is a fool." crlf)
)
(defrule worry-about-thin-people
(declare (salience 20))
(personal-data (name ?name) (weight ?weight))
(test (< ?weight 80))
=>
(printout t ?name " is looking a bit thin." crlf)
)
then the higher salience of rule worry-about-thin-people will ensure that it fires
first. As a general rule, try to use salience sparingly. If you find you need many
levels of salience (more than four is a good rule of thumb) then you should probably
consider either (a) writing your program in a language which gives you the level of
control you desire, or (b) rewriting your program to suit the production system
paradigm. There is a third way of controlling the execution of rules or groups of
rules, and that is by collecting them into modules, only one of which is active at any
given time.
(defglobal ?*shuffleswaps* = 150)
(deffacts cards
(cardnames ace two three four five six seven eight nine ten
jack queen king)
(cardvalues 1 2 3 4 5 6 7 8 9 10 10 10 10)
(cardsuits hearts clubs diamonds spades)
)
The first job is to set up the pack of cards we will us to play the game. This is done
with a two rules, thus:
(defrule go
(initialfact)
=>
(assert (state createpack))
)
(defrule createcards
?oldstate < (state createpack)
(cardnames $?names)
(cardsuits $?suits)
=>
(bind ?number 0)
(loopforcount (?suit 1 4) do
(loopforcount (?name 1 13) do
(bind ?number (+ ?number 1))
(assert (drawpile (nth$ ?name ?names) (nth$ ?
suit ?suits) ?number))
)
)
(assert (topcard 1))
(retract ?oldstate)
(assert (state shufflepack))
)
The first rule is of course only a control rule; the second does the work. It uses a
construct you've not seen before - the loopforcount iterator. This is very similar
to a for loop in C or BASIC. You give it a variable, a lower bound and an upper
bound, and it repeats everything between the do and the closing bracket the
appropriate number of times, incrementing the variable from the lower to the upper
bounds. You might also want to look at the while function, which is similar. Another
new function in this rule is nth$, which returns a single value at a given position in a
multifield variable. So, if we had a variable ?a which had the multiple value (dog cat
fish), then (nth$ 2 ?a) would return the value (fish). So, by the time this rule
has finished, we have a pack of cards, but they are in a perfect order. We need to
shuffle them. The following two rules do just that:
(defrule startshuffle
(state shufflepack)
(not (swapcount ?))
=>
(seed (round (time)))
(assert (swapcount 1))
(assert (swapposition1 (round (+ (* (/ (random) 32767) 51)
1))))
(assert (swapposition2 (round (+ (* (/ (random) 32767) 51)
1))))
)
(defrule shufflepack
(state shufflepack)
?swapcard1 < (swapposition1 ?cp1)
?swapcard2 < (swapposition2 ?cp2)
?swapcount < (swapcount ?cc)
(test (< ?cc ?*shuffleswaps*))
?card1 < (drawpile ?name1 ?suit1 ?cp1)
?card2 < (drawpile ?name2 ?suit2 ?cp2)
=>
(retract ?card1)
(retract ?card2)
(retract ?swapcount)
(retract ?swapcard1)
(retract ?swapcard2)
(assert (drawpile ?name1 ?suit1 ?cp2))
(assert (drawpile ?name2 ?suit2 ?cp1))
(assert (swapcount (+ ?cc 1)))
(assert (swapposition1 (round (+ (* (/ (random) 32767) 51)
1))))
(assert (swapposition2 (round (+ (* (/ (random) 32767) 51)
1))))
)
(defrule packshuffled
?state < (state shufflepack)
?swapcount < (swapcount ?cc)
(test (= ?cc ?*shuffleswaps*))
=>
(retract ?swapcount)
(retract ?state)
(assert (state printdeck))
)
The shuffling algorithm isn't brilliant. All it does is pick random pairs of cards and
swap their places in the pack. There is no guarantee that the pack will be well
shuffled. (How could you do this better?) The only new things in this rule are (seed
(round (time))) and (random).
The next part of the program prints out the deck of cards in the order they would be
dealt.
(defrule startprintdeck
(state printdeck)
(not (nextcard ?))
(topcard ?topcard)
=>
(assert (nextcard ?topcard))
)
(defrule printdeck
(state printdeck)
?nextcard < (nextcard ?number)
(drawpile ?name ?suit ?number)
=>
(printout t ?number ": " ?name " of " ?suit " has value " (card
value ?name) crlf)
(retract ?nextcard)
(assert (nextcard (+ ?number 1)))
)
This rule uses a function (cardvalue) which is not part of CLIPS, but is user-
defined. The definition is below:
(deffunction cardvalue
(?cardname)
(switch ?cardname
(case ace then (bind ?returnvalue 1))
(case two then (bind ?returnvalue 2))
(case three then (bind ?returnvalue 3))
(case four then (bind ?returnvalue 4))
(case five then (bind ?returnvalue 5))
(case six then (bind ?returnvalue 6))
(case seven then (bind ?returnvalue 7))
(case eight then (bind ?returnvalue 8))
(case nine then (bind ?returnvalue 9))
(case ten then (bind ?returnvalue 10))
(case jack then (bind ?returnvalue 10))
(case queen then (bind ?returnvalue 10))
(case king then (bind ?returnvalue 10))
(default (bind ?returnvalue 0))
)
(return ?returnvalue)
)
The function takes the name of a card (ace, two, king etc) and returns its numeric
value. It uses the switch statement to do this.