Number Guess

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 RuleFlow, a way of controlling the order in which rules are fired. It uses widely understood workflow diagrams to make clear the order that groups of rules will be executed.

Example 8.69. Creating the Number Guess RuleBase - extract 1 from NumberGuessExample.java main() method

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 a additional line to add the RuleFlow (NumberGuess.rf) as you have the option of specifying different ruleflows for the same KnowledgeBase. Otherwise the KnowledgeBase is created in the same manner as before.

Example 8.70. Starting the RuleFlow - extract 2 from NumberGuessExample.java main() method

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 KnowledgeBase we can use it to obtain a stateful session. Into our session we insert our facts (standard Java Objects). For simplicity in this sample, these classes are all contained within our NumberGuessExample.java file. The GameRules class provides the maximum range and the number of guesses allowed. The RandomNumber class automatically generates a number between 0 and 100 and makes it available to our rules after insertion (via the getValue() method). The Game class keeps track of the guesses we have made before, and the number of guesses we have made.

Note that before we call the standard fireAllRules() method, we also start the process that we loaded earlier (via the startProcess() method). We explain where to obtain the parameter we pass ("Number Guess" - the id of the ruleflow) when we talk about the RuleFlow file and the graphical RuleFlow editor below.

Before we finish we our Java code , we note that In 'real life' we would examine the final state of the objects (e.g. how many guesses it took, so that we could add it to a high score table). For this example we are content to ensure the working memory session is cleared by calling the dispose() method.

Figure 8.18. RuleFlow for the NumberGuess Example

RuleFlow for the NumberGuess Example

If you open the NumberGuess.rf file open in the Drools IDE (and 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 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 call the pallete. This diagram is saved in a (almost human) readable xml format, using xstream.

If it is not already open, ensure the properties view is visible in the IDE. It can opened by selecting Window -> Show View -> Other and then select the Properties view. If you do this before you select any item on the RuleFlow (or click on blank space in the RuleFlow) you should be presented with the following set of properties.

Figure 8.19. Properties for the Number Guess RuleFlow

Properties for the Number Guess RuleFlow

Keep an eye on the properties view as we progress through the example RuleFlow as it gives valuable information. In this case it provides us with the ID of the RuleFlow process that we used in our earlier code example when we called session.startprocess().

To give an overview of each of the node types (boxes) in the NumberGuess RuleFlow.

These various nodes work together with the Rules to make the Number Guess game work. For example, the "Guess" RuleFlowGroup allows only the rule "Get user Guess" to fire (details below) as only that Rule has a matching attribute of ruleflow-group "Guess"

Example 8.71. A Rule that will fire only a specific point in the RuleFlow - extract from 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 ) );
        modify ( game ) { guessCount = game.guessCount + 1 }
        i = br.readLine();        
    insert( new Guess( i ) );
end

The rest of this rule is fairly standard : The LHS (when) section of the rule states that it will be activated for each RandomNumber object inserted into the working memory where guessCount is less than the allowedGuesses ( read from the GameRules Class) and where the user has not guessed the correct number.

The RHS (consequence, then) prints a message to the user, then awaits user input from System.in. After getting this input (as System.in blocks until the <return> key is pressed) it updates/modifes the guess count, the actual guess and makes both available in the working memory.

The rest of the Rules file is fairly standard ; the package declares the dialect is set to MVEL, various Java classes are imported. In total, there are five rules in this file:

  1. Get User Guess, the Rule we examined above.

  2. A Rule to record the highest guess.

  3. A Rule to record the lowest guess.

  4. A Rule to inspect the guess and retract it from memory if incorrect.

  5. 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 'ruleflow-group' attribute on the rules (as dicussed above). A second point of integration between the Rules File (drl) and the Rules Flow .rf files is that the Split Nodes (the blue ovals) can use values in 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 (the button at the right that appears once you click on the 'Constraints' property line). You should see something similar to the Diagram below.

Figure 8.20. Edit Constraints for the GuessCorrect Node

Edit Constraints for the GuessCorrect Node

Click on 'Edit' beside 'To node Too High' and you see a dialog like the one below. The values in the 'Textual Editor' follow the standard Rule Format (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.

Figure 8.21. Constraints Editor for the GuessCorrect Node / value too high

Constraints Editor for the GuessCorrect Node / value too high

Since the NumberGuess.java example 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:

  1. Main() method of NumberGuessExample.java loads RuleBase, gets a StatefulSession and inserts Game, GameRules and RandomNumber (containing the target number) objects into it. This method sets the process flow we are going to use, and fires all rules. Control passes to the RuleFlow.

  2. The NumberGuess.rf RuleFlow begins at the Start node.

  3. Control passes (via the "more guesses" join node) to the Guess Node..

  4. At the Guess node, the appropriate RuleFlowGroup ("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 memory. Flow passes to the next Rule Flow Node.

  5. At the next node , "Guess Correct", constraints inspect the current session and decide which path we take next.

    If the guess in step 4 was too high / too low flow procees along a path which has (i) An action node with normal Java code prints a too high / too low statement and (ii) a RuleFlowGroup causes a highest guess / lowest guess Rule to be triggered in the Rules file. Flow passes from these nodes to step 6.

    If the guess in step 4 just 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 RuleFlow.

  6. Control passes as per the RuleFlow via a join node, a guess incorrect RuleFlowGroup (triggers a rule to retract a guess from working memory) onto the "more guesses" decision node.

  7. The "more guesses" decision node (right hand side of ruleflow) 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 workflow, via a RuleFlowGroup that triggers a rule stating "you have no more guesses".

  8. The Loop 3-7 continues until the number is guessed correctly, or we run out of guesses.