org.drools.examples.sudoku.rules.DroolsSudokuGridModel Maven / Gradle / Ivy
/*
* JBoss, the OpenSource J2EE webOS
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.drools.examples.sudoku.rules;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.drools.KnowledgeBase;
import org.drools.event.rule.ObjectInsertedEvent;
import org.drools.event.rule.ObjectRetractedEvent;
import org.drools.event.rule.ObjectUpdatedEvent;
import org.drools.event.rule.WorkingMemoryEventListener;
import org.drools.examples.sudoku.swing.AbstractSudokuGridModel;
import org.drools.examples.sudoku.swing.SudokuGridEvent;
import org.drools.examples.sudoku.swing.SudokuGridModel;
import org.drools.runtime.StatefulKnowledgeSession;
/**
* An implementation of the SudokuGridModel interface which is backed by the Drools engine.
*
* Two rule bases are used in this implementation. The first, defined at SUDOKU_SOLVER_DRL
* provides a set of rules that attempt to solve a partially completed Sudoku grid. The
* second, defined at SUDOKU_VALIDATOR_DRL provides a set of rules which can validate
* whether the current state of the model represents a valid solution.
*
*
* @author Pete Bennett
* @version $Revision: 1.1 $
*/
@SuppressWarnings("unchecked")
public class DroolsSudokuGridModel extends AbstractSudokuGridModel
implements
SudokuGridModel {
/**
* The location of the DRL file which defines the rule base for solving a Sudoku grid
*/
public final static String SUDOKU_SOLVER_DRL = "../sudokuSolver.drl";
/**
* The location of the DRL file which defines the rule base for validating the content of a Sudoku grid
*/
public final static String SUDOKU_VALIDATOR_DRL = "../sudokuValidator.drl";
/** A set of AbtractCellValues capturing the current state of the grid */
private Set allCellValues = new HashSet();
/** A index into the AbstractCellValues based on row and column */
private Set[][] cellValuesByRowAndCol = new HashSet[SudokuGridModel.NUM_ROWS][SudokuGridModel.NUM_COLS];
/** The solver rule base */
private KnowledgeBase solverRuleBase;
/** The stateful session working memory for the solver rule base */
private StatefulKnowledgeSession solverStatefulSession;
/** An inner class implementation listening to working memory events */
private SudokuWorkingMemoryListener workingMemoryListener = new SudokuWorkingMemoryListener();
/**
* Create a new DroolsSudokuGridModel with an empty grid.
*/
public DroolsSudokuGridModel() {
}
/**
* Create a new DroolsSudokuGridModel with the specified values.
*
* @param cellValues a two dimensional grid of Integer values for cells, a null means the value is not yet resolved
*/
public DroolsSudokuGridModel(Integer[][] cellValues) {
this();
setCellValues( cellValues );
}
/**
* Set the state of the Grid based on a two dimensional array of Integers.
*
* @param cellValues a two dimensional grid of Integer values for cells, a null means the value is not yet resolved
*/
public void setCellValues(Integer[][] cellValues) {
long startTime = System.currentTimeMillis();
if ( solverRuleBase == null ) {
try {
solverRuleBase = DroolsUtil.getInstance().readRuleBase( SUDOKU_SOLVER_DRL );
} catch ( Exception ex ) {
ex.printStackTrace();
throw new RuntimeException( "Error Reading RuleBase for Solver" );
}
}
if ( solverStatefulSession != null ) {
solverStatefulSession.removeEventListener( workingMemoryListener );
}
solverStatefulSession = solverRuleBase.newStatefulKnowledgeSession();
solverStatefulSession.addEventListener( workingMemoryListener );
for ( int row = 0; row < cellValues.length; row++ ) {
for ( int col = 0; col < cellValues[row].length; col++ ) {
cellValuesByRowAndCol[row][col] = new HashSet();
if ( cellValues[row][col] == null ) {
for ( int value = 1; value < 10; value++ ) {
PossibleCellValue cellValue = new PossibleCellValue( value,
row,
col );
addCellValue( cellValue );
allCellValues.add( cellValue );
}
} else {
ResolvedCellValue cellValue = new ResolvedCellValue( cellValues[row][col],
row,
col );
addCellValue( cellValue );
}
}
}
insertAllCellValues( solverStatefulSession );
System.out.println( "Setting up working memory and inserting all cell value POJOs took " + (System.currentTimeMillis() - startTime) + "ms." );
}
/**
* Determines whether a given cell is editable from another class.
*
* @param row the row in the grid for the cell
* @param col the column in the grid for the cell
* @return is the specified cell editable
*/
public boolean isCellEditable(int row,
int col) {
return false;
}
/**
* Determines whether a given cell has been solved.
*
* @param row the row in the grid for the cell
* @param col the column in the grid for the cell
* @return is the specified cell solved
*/
public boolean isCellResolved(int row,
int col) {
return getPossibleCellValues( row,
col ).size() == 1;
}
/**
* Evaluates the current state of the Grid against the
* validation rules determined in the SUDOKU_VALIDATOR_DRL
* and indicates if the grid is currently solved or not.
*
* @return true if the current state represents a completely filled out
* and valid Sudoku solution, false otherwise
*/
public boolean isGridSolved() {
boolean solved = true;
// TODO: move this logic into SUDOKU_VALIDATOR_DRL and out of Java code
for ( int row = 0; row < NUM_ROWS; row++ ) {
for ( int col = 0; col < NUM_COLS; col++ ) {
if ( !isCellResolved( row,
col ) ) {
System.out.print( "(" + row + "," + col + ") has not been resolved but has been narrowed down to " );
for ( Integer possibleInt : getPossibleCellValues( row,
col ) ) {
System.out.print( possibleInt + " " );
}
System.out.println();
solved = false;
}
}
}
if ( solved ) {
try {
KnowledgeBase kbase = DroolsUtil.getInstance().readRuleBase( SUDOKU_VALIDATOR_DRL );
StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();
List issues = new ArrayList();
ksession.setGlobal( "issues",
issues );
insertAllCellValues( ksession );
ksession.fireAllRules();
if ( issues.isEmpty() ) {
System.out.println( "Sucessfully Validated Solution" );
} else {
solved = false;
for ( Object issue : issues ) {
System.out.println( issue );
}
}
} catch ( Exception ex ) {
ex.printStackTrace();
throw new RuntimeException();
}
}
return solved;
}
/**
* Returns the possible values of the cell at a specific
* row and column in the Grid.
*
* @param row the row in the Grid
* @param col the column in the Grid
* @return the Set of possible Integer values this cell can have, if
* the Set is of size one then this is the value this cell
* must have, otherwise it is a list of the possibilities
*/
public Set getPossibleCellValues(int row,
int col) {
return cellValuesByRowAndCol[row][col];
}
/**
* Attempt to solve the Sudoku puzzle from its current state by
* firing all of the rules in SUDOKU_SOLVER_DRL against the
* current state of the Grid then validate if we have solved the
* Grid after this.
*
* @return true if the state after firing all rules
* represents a completely filled out
* and valid Sudoku solution, false otherwise
*/
public boolean solve() {
solverStatefulSession.fireAllRules();
return isGridSolved();
}
/**
* Fire the next rule on the agenda and return
*
* @return true if the state after firing the single rule
* represents a completely filled out
* and valid Sudoku solution, false otherwise
*/
public boolean step() {
// TODO: I am not sure where the fireAllRules(int) method has gone
// should be solverStatefulSession.fireAllRules(1)
solverStatefulSession.fireAllRules();
return isGridSolved();
}
/**
* Inserts all of the current state of the Grid as represented
* by the set of AbstractCellValues this class is maintaining
* into the specified StatefulSession working memory.
*
* @param statefulSession the target StatefulSession
*/
private void insertAllCellValues(StatefulKnowledgeSession statefulSession) {
for ( AbstractCellValue cellValue : allCellValues ) {
statefulSession.insert( cellValue );
}
}
/**
* Adds the specified AbstractCellValue into the set of
* AbstractCellValues that this class is maintaining.
*
* @param cellValue the AbstractCellValue to add
*/
private void addCellValue(AbstractCellValue cellValue) {
allCellValues.add( cellValue );
cellValuesByRowAndCol[cellValue.getRow()][cellValue.getCol()].add( cellValue.getValue() );
}
/**
* Removes the specified AbstractCellValue from the set of
* AbstractCellValues that this class is maintaining.
*
* @param cellValue the AbstractCellValue to remove
*/
private void removeCellValue(AbstractCellValue cellValue) {
allCellValues.remove( cellValue );
cellValuesByRowAndCol[cellValue.getRow()][cellValue.getCol()].remove( cellValue.getValue() );
}
class SudokuWorkingMemoryListener
implements
WorkingMemoryEventListener {
public void objectInserted(ObjectInsertedEvent ev) {
if ( ev.getObject() instanceof AbstractCellValue ) {
addCellValue( ((AbstractCellValue) ev.getObject()) );
}
if ( ev.getObject() instanceof ResolvedCellValue ) {
ResolvedCellValue cellValue = (ResolvedCellValue) ev.getObject();
fireCellResolvedEvent( new SudokuGridEvent( this,
cellValue.getRow(),
cellValue.getCol(),
cellValue.getValue() ) );
}
if ( ev.getObject() instanceof String ) {
System.out.println( ev.getObject() );
}
}
public void objectRetracted(ObjectRetractedEvent ev) {
if ( ev.getOldObject() instanceof AbstractCellValue ) {
AbstractCellValue cellValue = (AbstractCellValue) ev.getOldObject();
removeCellValue( cellValue );
fireCellUpdatedEvent( new SudokuGridEvent( this,
cellValue.getRow(),
cellValue.getCol(),
cellValue.getValue() ) );
}
}
public void objectUpdated(ObjectUpdatedEvent ev) {
if ( ev.getObject() instanceof ResolvedCellValue ) {
ResolvedCellValue cellValue = (ResolvedCellValue) ev.getObject();
fireCellUpdatedEvent( new SudokuGridEvent( this,
cellValue.getRow(),
cellValue.getCol(),
cellValue.getValue() ) );
}
}
}
}