Name: Number Guess Main class: org.drools.examples.NumberGuessExample Type: Java application Rules file: NumberGuess.drl Objective: Demonstrate use of Rule Flow to organise Rules
The "Number Guess" example shows the use of Rule Flow, a way of controlling the order in which rules are fired. It uses widely understood workflow diagrams for defining the order in which groups of rules will be executed.
Example 8.69. Creating the Number Guess RuleBase: NumberGuessExample.main() - part 1
final KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(); kbuilder.add( ResourceFactory.newClassPathResource( "NumberGuess.drl", ShoppingExample.class ), ResourceType.DRL ); kbuilder.add( ResourceFactory.newClassPathResource( "NumberGuess.rf", ShoppingExample.class ), ResourceType.DRF ); final KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase(); kbase.addKnowledgePackages( kbuilder.getKnowledgePackages() );
The creation of the package and the loading of the rules (using
the add()
method) is the same as the previous examples.
There is an additional line to add the Rule Flow
(NumberGuess.rf
), which provides the option of
specifying different rule flows for the same Knowledge Base.
Otherwise, the Knowledge Base is created in the same manner as before.
Example 8.70. Starting the RuleFlow: NumberGuessExample.main() - part 2
final StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession(); KnowledgeRuntimeLogger logger = KnowledgeRuntimeLoggerFactory.newFileLogger(ksession, "log/numberguess"); ksession.insert( new GameRules( 100, 5 ) ); ksession.insert( new RandomNumber() ); ksession.insert( new Game() ); ksession.startProcess( "Number Guess" ); ksession.fireAllRules(); logger.close(); ksession.dispose();
Once we have a Knowledge Base, we can use it to obtain a Stateful
Session. Into our session we insert our facts, i.e., standard Java objects.
(For simplicity, in this sample, these classes are all contained within
our NumberGuessExample.java
file. Class
GameRules
provides the maximum range and the number of guesses
allowed. Class RandomNumber
automatically generates a number
between 0 and 100 and makes it available to our rules, by insertion via
the getValue()
method. Class Game
keeps track
of the guesses we have made before, and their number.
Note that before we call the standard fireAllRules()
method, we also start the process that we loaded earlier, via the
startProcess()
method. We'll learn where to obtain the
parameter we pass ("Number Guess", i.e., the identifier of the rule flow)
when we talk about the rule flow file and the graphical Rule Flow Editor
below.
Before we finish the discussion of our Java code, we note that in
some real-life application we would examine the final state of the objects.
(Here, we could retrieve the number of guesses, to add it to a high score
table.) For this example we are content to ensure that the Working Memory
session is cleared by calling the dispose()
method.
If you open the NumberGuess.rf
file in the
Drools IDE (provided you have the JBoss Rules extensions installed
correctly in Eclipse) you should see the above diagram, similar to a
standard flowchart. Its icons are similar (but not exactly the same)
as in the JBoss jBPM workflow product. Should you wish to edit the
diagram, a menu of available components should be available to the
left of the diagram in the IDE, which is called the
palette. This diagram is saved in XML, an
(almost) human readable format, using XStream.
If it is not already open, ensure that the Properties View is visible in the IDE. It can be opened by clicking "Window", then "Show View" and "Other", where you can select the "Properties" view. If you do this before you select any item on the rule flow (or click on the blank space in the rule flow) you should be presented with the following set of properties.
Keep an eye on the Properties View as we progress through the
example's rule flow, as it presents valuable information. In this case, it
provides us with the identification of the Rule Flow Process that
we used in our earlier code snippet, when we called
session.startProcess()
.
In the "Number Guess" Rule Flow we encounter several node types, many of them identified by an icon.
The Start node (white arrow in a green circle) and the End node (red box) mark beginning and end of the rule flow.
A Rule Flow Group box (yellow, without an icon) represents
a Rule Flow Groups defined in our rules (DRL) file that we will
look at later. For example, when the flow reaches the Rule Flow Group
"Too High", only those rules marked with an attribute of
<kw>ruleflow-group</kw> "Too High"
can potentially fire.
Action nodes (yellow, cog-shaped icon) perform standard Java
method calls. Most action nodes in this example call
System.out.println()
, indicating the program's progress
to the user.
Split and Join Nodes (blue ovals, no icon) such as "Guess Correct?" and "More guesses Join" mark places where the flow of control can split, according to various conditions, and rejoin, respectively
Arrows indicate the flow between the various nodes.
The various nodes in combination with the rules make the
Number Guess game work. For example, the "Guess" Rule Flow Group
allows only the rule "Get user Guess" to fire, because only that rule
has a matching attribute of <kw>ruleflow-group</kw> "Guess"
.
Example 8.71. A Rule firing only at a specific point in the Rule Flow: NumberGuess.drl
rule "Get user Guess" ruleflow-group "Guess" no-loop when $r : RandomNumber() rules : GameRules( allowed : allowedGuesses ) game : Game( guessCount < allowed ) not ( Guess() ) then System.out.println( "You have " + ( rules.allowedGuesses - game.guessCount ) + " out of " + rules.allowedGuesses + " guesses left.\nPlease enter your guess from 0 to " + rules.maxRange ); br = new BufferedReader( new InputStreamReader( System.in ) ); i = br.readLine(); modify ( game ) { guessCount = game.guessCount + 1 } insert( new Guess( i ) ); end
The rest of this rule is fairly standard. The LHS section
(after <kw>when</kw>) of the rule states that it will be activated
for each RandomNumber
object inserted into the Working
Memory where guessCount
is less than
allowedGuesses
from the GameRules
object
and where the user has not guessed the correct number.
The RHS section (or consequence, after <kw>then</kw>) prints a
message to the user and then awaits user input from
System.in
. After obtaining this input (the
readLine()
method call blocks until the return key is
pressed) it modifies the guess count and inserts the new
guess, making both available to the Working Memory.
The rest of the rules file is fairly standard: the package declares the dialect as MVEL, and various Java classes are imported. In total, there are five rules in this file:
Get User Guess, the Rule we examined above.
A Rule to record the highest guess.
A Rule to record the lowest guess.
A Rule to inspect the guess and retract it from memory if incorrect.
A Rule that notifies the user that all guesses have been used up.
One point of integration between the standard Rules and the RuleFlow is via the <kw>ruleflow-group</kw> attribute on the rules, as dicussed above. A second point of integration between the rules (.drl) file and the Rules Flow .rf files is that the Split Nodes (the blue ovals) can use values in the Working Memory (as updated by the rules) to decide which flow of action to take. To see how this works, click on the "Guess Correct Node"; then within the Properties View, open the Constraints Editor by clicking the button at the right that appears once you click on the "Constraints" property line. You should see something similar to the diagram below.
Click on the "Edit" button beside "To node Too High" and you'll see a dialog like the one below. The values in the "Textual Editor" window follow the standard rule format for the LHS and can refer to objects in Working Memory. The consequence (RHS) is that the flow of control follows this node (i.e., "To node Too High") if the LHS expression evaluates to true.
Since the file NumberGuess.java
contains a
main()
method, it
can be run as a standard Java application, either from the command line
or via the IDE. A typical game might result in the interaction below.
The numbers in bold are typed in by the user.
Example 8.72. Example Console output where the Number Guess Example beat the human!
You have 5 out of 5 guesses left. Please enter your guess from 0 to 100 50 Your guess was too high You have 4 out of 5 guesses left. Please enter your guess from 0 to 100 25 Your guess was too low You have 3 out of 5 guesses left. Please enter your guess from 0 to 100 37 Your guess was too low You have 2 out of 5 guesses left. Please enter your guess from 0 to 100 44 Your guess was too low You have 1 out of 5 guesses left. Please enter your guess from 0 to 100 47 Your guess was too low You have no more guesses The correct guess was 48
A summary of what is happening in this sample is:
The main()
method of
NumberGuessExample.java
loads a Rule Base,
creates a Stateful Session and inserts Game
,
GameRules
and RandomNumber
(containing the target number) objects into it. The
method also sets the process flow we are going to use, and fires all
rules. Control passes to the Rule Flow.
File NumberGuess.rf
, the Rule Flow,
begins at the "Start" node.
Control passes (via the "More guesses" join node) to the Guess node.
At the Guess node, the appropriate Rule Flow Group
("Get user Guess") is enabled. In this case the Rule "Guess" (in the
NumberGuess.drl
file) is triggered. This rule
displays a message to the user, takes the response, and puts it into
Working Memory. Flow passes to the next Rule Flow Node.
At the next node, "Guess Correct", constraints inspect the current session and decide which path to take.
If the guess in step 4 was too high or too low, flow proceeds along a path which has an action node with normal Java code printing a suitable message and a Rule Flow Group causing a highest guess or lowest guess rule to be triggered. Flow passes from these nodes to step 6.
If the guess in step 4 was right, we proceed along the path towards the end of the Rule Flow. Before we get there, an action node with normal Java code prints a statement "you guessed correctly". There is a join node here (just before the Rule Flow end) so that our no-more-guesses path (step 7) can also terminate the Rule Flow.
Control passes as per the Rule Flow via a join node, a guess incorrect Rule Flow Group (triggering a rule to retract a guess from Working Memory) onto the "More guesses" decision node.
The "More guesses" decision node (on the right hand side of the rule flow) uses constraints, again looking at values that the rules have put into the working memory, to decide if we have more guesses and if so, goto step 3. If not, we proceed to the end of the rule flow, via a Rule Flow Group that triggers a rule stating "you have no more guesses".
The loop over steps 3 to 7 continues until the number is guessed correctly, or we run out of guesses.