Its interesting, Conways game of life was created to demonstrate a "determinisitc universe" - if you think as the little "critters" as life forms its pretty cool. A good example of how very very simple rules (and only a small number) can create complex behaviour.
I have just moved Jeff Brown's "Conways Game of Life" to a stateful
example, this is now the best place to look to understand jboss rules.
It's also good to compare this stateful implementation to the old
stateless version. Trunk introduces a new feature to help deal with
recursion "lock-on-active" which stops a rule being able to create
activations while it's agenda-group/rule-flow-group has focus - so it's
a much stronger no-loop. Probably the most important thing this example
shows is how to think relationally, the old example used nested
properties and sets to maintain the "neighbour" information; this
example shows how to achieve the same but expressing it relationally,
using the Neighbor relation class, further it shows how we can exploit
the the cross-product relational information to drive the engine without
us having to write loops. Currently the example is using agenda-groups
to drive execution flow, I'm now in the process of moving this to
rule-flow-groups.
You'll need trunk to be able to run this, so checkout:
http://anonsvn.labs.jboss.com/labs/jbossrules/trunk/
Make sure you have maven 2.0.6 installed and then type the following
from the root directory:
mvn clean install -Declipse=true
The eclipse plugin will now be built in the drools-eclipse/target
directory, so open that up and unzip into your eclipse install (make
sure you use -clean to start eclipse). Then import drools-examples and
"run as application" the ConwayGUI class.
I plan to do a blog or two on how this exampe works, and my next
challenge will be to migrate the pacman game to jboss rules.
Mark
--
Registered Address: Red Hat UK Ltd, Amberley Place, 107-111 Peascod
Street, Windsor, Berkshire,
SI4 1TE, United Kingdom.
Registered in UK and Wales under Company Registration No. 3798903
Directors: Michael Cunningham (USA), Charlie Peters (USA) and David
Owens (Ireland)
package org.drools.examples
import org.drools.examples.conway.Cell;
import org.drools.examples.conway.CellGrid;
import org.drools.examples.conway.Neighbor;
import org.drools.examples.conway.Phase ;
import org.drools.examples.conway.CellState;
import org.drools.WorkingMemory;
import org.drools.common.InternalWorkingMemoryActions;
import org.drools.RuleBase;
rule "register north east"
agenda-group "register neighbor"
when
CellGrid( $numberOfColumns : numberOfColumns )
$cell: Cell( $row : row > 0, $col : col < ( $numberOfColumns - 1 ) )
$northEast : Cell( row == ($row - 1), col == $col )
then
assert( new Neighbor( $cell, $northEast ) );
assert( new Neighbor( $northEast, $cell ) );
end
rule "register north"
agenda-group "register neighbor"
when
$cell: Cell( $row : row > 0, $col : col )
$north : Cell( row == ($row - 1), col == $col )
then
assert( new Neighbor( $cell, $north ) );
assert( new Neighbor( $north, $cell ) );
end
rule "register north west"
agenda-group "register neighbor"
when
$cell: Cell( $row : row > 0, $col : col > 0 )
$northWest : Cell( row == ($row - 1), col == ( $col - 1 ) )
then
assert( new Neighbor( $cell, $northWest ) );
assert( new Neighbor( $northWest, $cell ) );
end
rule "register west"
agenda-group "register neighbor"
when
$cell: Cell( $row : row > 0, $col : col > 0 )
$west : Cell( row == $row, col == ( $col - 1 ) )
then
assert( new Neighbor( $cell, $west ) );
assert( new Neighbor( $west, $cell ) );
end
rule "Kill The Lonely"
agenda-group "evaluate"
no-loop
when
# A live cell has fewer than 2 live neighbors
theCell: Cell(liveNeighbors < 2, cellState == CellState.LIVE, phase == Phase.EVALUATE)
then
theCell.setPhase(Phase.KILL);
modify( theCell );
end
rule "Kill The Overcrowded"
agenda-group "evaluate"
no-loop
when
# A live cell has more than 3 live neighbors
theCell: Cell(liveNeighbors > 3, cellState == CellState.LIVE, phase == Phase.EVALUATE)
then
theCell.setPhase(Phase.KILL);
modify( theCell );
end
rule "Give Birth"
agenda-group "evaluate"
no-loop
when
# A dead cell has 3 live neighbors
theCell: Cell(liveNeighbors == 3, cellState == CellState.DEAD , phase == Phase.EVALUATE)
then
theCell.setPhase(Phase.BIRTH);
modify( theCell );
end
rule "reset calculate"
agenda-group "reset calculate"
when
then
WorkingMemory wm = drools.getWorkingMemory();
wm.getAgenda().clearAgendaGroup( "calculate" );
end
rule "kill"
agenda-group "kill"
no-loop
when
theCell: Cell(phase == Phase.KILL)
then
theCell.setCellState(CellState.DEAD);
theCell.setPhase(Phase.DONE);
modify( theCell );
end
rule "birth"
agenda-group "birth"
no-loop
when
theCell: Cell(phase == Phase.BIRTH)
then
theCell.setCellState(CellState.LIVE);
theCell.setPhase(Phase.DONE);
modify( theCell );
end
rule "Calculate Live"
agenda-group "calculate"
lock-on-active
when
theCell: Cell(cellState == CellState.LIVE)
Neighbor(cell == theCell, $neighbor : neighbor)
then
$neighbor.setLiveNeighbors( $neighbor.getLiveNeighbors() + 1 );
$neighbor.setPhase( Phase.EVALUATE );
modify( $neighbor );
end
rule "Calculate Dead"
agenda-group "calculate"
lock-on-active
when
theCell: Cell(cellState == CellState.DEAD)
Neighbor(cell == theCell, $neighbor : neighbor )
then
$neighbor.setLiveNeighbors( $neighbor.getLiveNeighbors() - 1 );
$neighbor.setPhase( Phase.EVALUATE );
modify( $neighbor );
end
rule "Kill All"
agenda-group "kill all"
no-loop
when
theCell: Cell(cellState == CellState.LIVE)
then
theCell.setCellState(CellState.DEAD);
modify( theCell );
end
package org.drools.examples.conway;
/**
* A <code>Cell</code> represents a single cell within a <code>CellGrid</code>.
* A cell may be either live or dead. <p/>
*
* @author <a href="mailto:brown_j@ociweb.com">Jeff Brown</a>
* @see CellState
* @see CellGrid
*/
public class Cell {
private CellState cellState = CellState.DEAD;
private int phase = Phase.DONE;
private int liveNeighbors;
private int col;
private int row;
public Cell(int col,
int row) {
this.col = col;
this.row = row;
}
public int getCol() {
return col;
}
public int getRow() {
return row;
}
public int getPhase() {
return this.phase;
}
public void setPhase(int phase) {
this.phase = phase;
}
public int getLiveNeighbors() {
return this.liveNeighbors;
}
public void setLiveNeighbors(int liveNeighbors) {
this.liveNeighbors = liveNeighbors;
}
/**
* @return this cell's current life state
* @see #queueNextCellState(org.drools.examples.conway.CellState)
* @see CellState
*/
public CellState getCellState() {
return this.cellState ;
}
/**
* Sets this cells state
*
* @param newState
* new state for this cell
* @see CellState
*/
public void setCellState(final CellState newState) {
this.cellState = newState;
}
public String toString() {
return cellState + " col=" + this.col + " row=" + this.row + " phase '" + phase + "' liveNeighbors '" + liveNeighbors + "'";
}
}
package org.drools.examples.conway;
import org.drools.RuleBase;
import org.drools.WorkingMemory;
import org.drools.event.AgendaGroupPoppedEvent;
import org.drools.event.DefaultAgendaEventListener ;
import org.drools.examples.conway.patterns.ConwayPattern;
/**
* A <code>CellGrid</code> represents a grid of <code>Cell</code> objects.
* <p/>
*
* @author <a href="mailto: brown_j@ociweb.com">Jeff Brown</a>
* @see Cell
*/
public class CellGrid {
private final Cell[][] cells;
private WorkingMemory workingMemory;
/**
* Constructs a CellGrid
*
* @param rows
* number of rows in the grid
* @param columns
* number of columns in the grid
*/
public CellGrid(final int rows,
final int columns) {
this.cells = new Cell[rows][columns];
final RuleBase ruleBase = ConwayRuleBaseFactory.getRuleBase();
this.workingMemory = ruleBase.newWorkingMemory ();
DefaultAgendaEventListener listener = new DefaultAgendaEventListener() {
public void agendaGroupPopped(AgendaGroupPoppedEvent event,
WorkingMemory workingMemory) {
System.out.println( "popped AgendaGroup = '" + event.getAgendaGroup().getName() + "'" );
System.out.println( CellGrid.this.toString() );
System.out.println( "" );
}
};
this.workingMemory.addEventListener( listener );
this.workingMemory.assertObject( this );
// populate the array of Cells and hook each
// cell up with its neighbors...
for ( int row = 0; row < rows; row++ ) {
for ( int column = 0; column < columns; column++ ) {
final Cell newCell = new Cell( column,
row );
this.cells[row][column] = newCell;
this.workingMemory.assertObject( newCell );
}
}
this.workingMemory.setFocus ( "register neighbor" );
this.workingMemory.fireAllRules();
}
/**
* @param row
* row of the requested cell
* @param column
* column of the requested cell
* @return the cell at the specified coordinates
* @see Cell
*/
public Cell getCellAt(final int row,
final int column) {
return this.cells[row][column];
}
/**
* @return the number of rows in this grid
* @see #getNumberOfColumns()
*/
public int getNumberOfRows() {
return this.cells.length;
}
/**
* @return the number of columns in this grid
* @see #getNumberOfRows()
*/
public int getNumberOfColumns() {
return this.cells[0].length;
}
/**
* Moves this grid to its next generation
*
* @return <code>true</code> if the state changed, otherwise false
* @see #transitionState()
*/
public boolean nextGeneration() {
System.out.println( "next generation" );
workingMemory.setFocus( "calculate" );
workingMemory.setFocus( "kill" );
workingMemory.setFocus( "birth" );
workingMemory.setFocus( "reset calculate" );
workingMemory.setFocus( "rest" );
workingMemory.setFocus( "evaluate" );
workingMemory.fireAllRules();
return workingMemory.getAgenda().getAgendaGroup( "evaluate" ).size() != 0;
}
/**
* kills all cells in the grid
*/
public void killAll() {
this.workingMemory.setFocus( "calculate" );
this.workingMemory.setFocus( "kill all" );
this.workingMemory.setFocus( "reset calculate" );
this.workingMemory.fireAllRules();
}
/**
* Populates the grid with a <code>ConwayPattern</code>
*
* @param pattern
* pattern to populate the grid with
* @see ConwayPattern
*/
public void setPattern(final ConwayPattern pattern) {
final boolean[][] gridData = pattern.getPattern();
int gridWidth = gridData[0].length;
int gridHeight = gridData.length;
int columnOffset = 0;
int rowOffset = 0;
if ( gridWidth > getNumberOfColumns() ) {
gridWidth = getNumberOfColumns();
} else {
columnOffset = (getNumberOfColumns() - gridWidth) / 2;
}
if ( gridHeight > getNumberOfRows() ) {
gridHeight = getNumberOfRows();
} else {
rowOffset = (getNumberOfRows() - gridHeight) / 2;
}
killAll();
for ( int column = 0; column < gridWidth; column++ ) {
for ( int row = 0; row < gridHeight; row++ ) {
if ( gridData[row][column] ) {
final Cell cell = getCellAt( row + rowOffset,
column + columnOffset );
cell.setCellState ( CellState.LIVE );
this.workingMemory.modifyObject( this.workingMemory.getFactHandle( cell ),
cell );
}
}
}
workingMemory.setFocus( "calculate" );
workingMemory.fireAllRules();
System.out.println( "" );
}
public String toString() {
StringBuffer buf = new StringBuffer();
for ( int i = 0; i < this.cells.length; i++ ) {
for ( int j = 0; j < this.cells[i].length; j++ ) {
Cell cell = this.cells[i][j];
System.out.print( cell.getLiveNeighbors() + ((cell.getCellState() == CellState.DEAD) ? "D" : "L") + " " );
}
System.out.println( "" );
}
return buf.toString();
}
}
package org.drools.examples.conway;
public class Neighbor {
private Cell cell;
private Cell neighbor;
public Neighbor(Cell cell, Cell neighbor) {
this.cell = cell;
this.neighbor = neighbor;
}
public Cell getCell() {
return cell;
}
public Cell getNeighbor() {
return neighbor;
}
public String toString() {
return "cell '"+ this.cell + "' neighbour '" + this.neighbor + "'";
}
}
_______________________________________________
rules-users mailing list
rules-users@lists.jboss.org
https://lists.jboss.org/mailman/listinfo/rules-users