You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2181 lines
64 KiB
2181 lines
64 KiB
12 years ago
|
<?php
|
||
|
|
||
|
//
|
||
|
// Edit History:
|
||
|
//
|
||
|
// Dick Munroe (munroe@csworks.com) 02-Nov-2005
|
||
|
// Initial version created.
|
||
|
//
|
||
|
// Dick Munroe (munroe@csworks.com) 12-Nov-2005
|
||
|
// Allow initialzePuzzle to accept a file name in addition
|
||
|
// to a resource. Windows doesn't do file redirection properly
|
||
|
// so the examples have to be able to handle a file NAME as
|
||
|
// input as well as a redirected file.
|
||
|
// Allow initializePuzzle to accept a string of 81 characters.
|
||
|
//
|
||
|
// Dick Munroe (munroe@csworks.com) 13-Nov-2005
|
||
|
// It appears that getBoardAsString screws up somehow. Rewrite it.
|
||
|
//
|
||
|
// Dick Munroe (munroe@csworks.com) 16-Nov-2005
|
||
|
// Add a "pair" inference.
|
||
|
//
|
||
|
// Dick Munroe (munroe@csworks.com) 17-Nov-2005
|
||
|
// Add comments to input files.
|
||
|
// There was a bug in _applyTuple that caused premature exiting of the inference
|
||
|
// engine.
|
||
|
// If SDD isn't present, don't display error.
|
||
|
//
|
||
|
// Dick Munroe (munroe@csworks.com) 19-Nov-2005
|
||
|
// Add a new tuple inference.
|
||
|
// Do a ground up ObjectS oriented redesign to make the addition of arbitrary
|
||
|
// inferences MUCH easier.
|
||
|
// Get the printing during solving right.
|
||
|
// Somehow array_equal developed a "bug".
|
||
|
//
|
||
|
// Dick Munroe (munroe@csworks.com) 22-Nov-2005
|
||
|
// Add n,n+1 tuple recognition for n=2.
|
||
|
// Restructure inference engine to get maximum benefit out of each pass.
|
||
|
//
|
||
|
// Dick Munroe (munroe@csworks.com) 28-Nov-2005
|
||
|
// Attempt to build harder Sudoku by implementing a coupling coefficient
|
||
|
// attempting to distribute clues more optimally.
|
||
|
//
|
||
|
|
||
|
@include_once "SDD/class.SDD.php" ;
|
||
|
|
||
|
/**
|
||
|
* @author Dick Munroe <munroe@csworks.com>
|
||
|
* @copyright copyright @ 2005 by Dick Munroe, Cottage Software Works, Inc.
|
||
|
* @license http://www.csworks.com/publications/ModifiedNetBSD.html
|
||
|
* @version 2.2.0
|
||
|
* @package Sudoku
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Basic functionality needed for ObjectSs in the Sudoku solver.
|
||
|
*
|
||
|
* Technically speaking these aren't restricted to the Sudoku classes
|
||
|
* and are of use generally.
|
||
|
*
|
||
|
* @package Sudoku
|
||
|
*/
|
||
|
|
||
|
class ObjectS
|
||
|
{
|
||
|
/**
|
||
|
* @desc Are two array's equal (have the same contents).
|
||
|
* @param array
|
||
|
* @param array
|
||
|
* @return boolean
|
||
|
*/
|
||
|
|
||
|
function array_equal($theArray1, $theArray2)
|
||
|
{
|
||
|
if (!(is_array($theArray1) && is_array($theArray2)))
|
||
|
{
|
||
|
return false ;
|
||
|
}
|
||
|
|
||
|
if (count($theArray1) != count($theArray2))
|
||
|
{
|
||
|
return false ;
|
||
|
}
|
||
|
|
||
|
$xxx = array_diff($theArray1, $theArray2) ;
|
||
|
|
||
|
return (count($xxx) == 0) ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Deep copy anything.
|
||
|
*
|
||
|
* @access public
|
||
|
* @param array $theArray [optional] Something to be deep copied. [Default is the current
|
||
|
* ObjectS.
|
||
|
* @return mixed The deep copy of the input. All references embedded within
|
||
|
* the array have been resolved into copies allowing things like the
|
||
|
* board array to be copied.
|
||
|
*/
|
||
|
|
||
|
function deepCopy($theArray = NULL)
|
||
|
{
|
||
|
if ($theArray === NULL)
|
||
|
{
|
||
|
return unserialize(serialize($this)) ;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return unserialize(serialize($theArray)) ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @desc Debugging output interface.
|
||
|
* @access public
|
||
|
* @param mixed $theValue The "thing" to be pretty printed.
|
||
|
* @param boolean $theHTMLFlag True if the output will be seen in a browser, false otherwise.
|
||
|
*/
|
||
|
|
||
|
function print_d(&$theValue, $theHTMLFlag = true)
|
||
|
{
|
||
|
print SDD::dump($theValue, $theHTMLFlag) ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The individual cell on the Sudoku board.
|
||
|
*
|
||
|
* These cells aren't restricted to 9x9 Sudoku (although pretty much everything else
|
||
|
* at the moment). This class provides the state manipulation and searching capabilities
|
||
|
* needed by the inference engine (class RCS).
|
||
|
*
|
||
|
* @package Sudoku
|
||
|
*/
|
||
|
|
||
|
class Cell extends ObjectS
|
||
|
{
|
||
|
var $r ;
|
||
|
var $c ;
|
||
|
|
||
|
var $state = array() ;
|
||
|
var $applied = false ;
|
||
|
|
||
|
|
||
|
/**
|
||
|
* @desc Constructor
|
||
|
* @param integer $r row address of this cell (not used, primarily for debugging purposes).
|
||
|
* @param integer $c column address of this cell (ditto).
|
||
|
* @param integer $nStates The number of states each cell can have. Looking forward to
|
||
|
* implementing Super-doku.
|
||
|
*/
|
||
|
|
||
|
function Cell($r, $c, $nStates = 9)
|
||
|
{
|
||
|
|
||
|
$this->r = $r ;
|
||
|
$this->c = $c ;
|
||
|
|
||
|
for ($i = 1; $i <= $nStates; $i++)
|
||
|
{
|
||
|
$this->state[$i] = $i ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @desc This cell has been "applied", i.e., solved, to the board.
|
||
|
*/
|
||
|
|
||
|
function applied()
|
||
|
{
|
||
|
$this->applied = true ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Only those cells which are not subsets of the tuple have the
|
||
|
* contents of the tuple removed.
|
||
|
*
|
||
|
* @desc apply a 23Tuple to a cell.
|
||
|
* @access public
|
||
|
* @param array $aTuple the tuple to be eliminated.
|
||
|
*/
|
||
|
|
||
|
function apply23Tuple($aTuple)
|
||
|
{
|
||
|
if (is_array($this->state))
|
||
|
{
|
||
|
$xxx = array_intersect($this->state, $aTuple) ;
|
||
|
if ((count($xxx) > 0) && (count($xxx) != count($this->state)))
|
||
|
{
|
||
|
return $this->un_set($aTuple) ;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return false ;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return false ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* For more details on the pair tuple algorithm, see RCS::_pairSolution.
|
||
|
*
|
||
|
* @desc Remove all values in the tuple, but only if the cell is a superset.
|
||
|
* @access public
|
||
|
* @param array A tuple to be eliminated from the cell's state.
|
||
|
*/
|
||
|
|
||
|
function applyTuple($aTuple)
|
||
|
{
|
||
|
if (is_array($this->state))
|
||
|
{
|
||
|
if (!$this->array_equal($aTuple, $this->state))
|
||
|
{
|
||
|
return $this->un_set($aTuple) ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @desc Return the string representation of the cell.
|
||
|
* @access public
|
||
|
* @param boolean $theFlag true if the intermediate states of the cell are to be visible.
|
||
|
* @return string
|
||
|
*/
|
||
|
|
||
|
function asString($theFlag = false)
|
||
|
{
|
||
|
if (is_array($this->state))
|
||
|
{
|
||
|
if (($theFlag) || (count($this->state) == 1))
|
||
|
{
|
||
|
return implode(", ", $this->state) ;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return " " ;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return $this->state ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Used to make sure that solved positions show up at print time.
|
||
|
* The value is used as a candidate for "slicing and dicing" by elimination in
|
||
|
* Sudoku::_newSolvedPosition.
|
||
|
*
|
||
|
* @desc Assert pending solution.
|
||
|
* @access public
|
||
|
* @param integer $value The value for the solved position.
|
||
|
*/
|
||
|
|
||
|
function flagSolvedPosition($value)
|
||
|
{
|
||
|
$this->state = array($value => $value) ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @desc return the state of a cell.
|
||
|
* @access protected
|
||
|
* @return mixed Either solved state or array of state pending solution.
|
||
|
*/
|
||
|
|
||
|
function &getState()
|
||
|
{
|
||
|
return $this->state ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @desc Has the state of this cell been applied to the board.
|
||
|
* @access public
|
||
|
* @return boolean True if it has, false otherwise. Implies that IsSolved is true as well.
|
||
|
*/
|
||
|
|
||
|
function IsApplied()
|
||
|
{
|
||
|
return $this->applied ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @desc Has this cell been solved?
|
||
|
* @access public
|
||
|
* @return boolean True if this cell has hit a single state.
|
||
|
*/
|
||
|
|
||
|
function IsSolved()
|
||
|
{
|
||
|
return !is_array($this->state) ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This is used primarily by the pretty printer, but has other applications
|
||
|
* in the code.
|
||
|
*
|
||
|
* @desc Return information about the state of a cell.
|
||
|
* @access public
|
||
|
* @return integer 0 => the cell has been solved.
|
||
|
* 1 => the cell has been solved but not seen a solved.
|
||
|
* 2 => the cell has not been solved.
|
||
|
*/
|
||
|
|
||
|
function solvedState()
|
||
|
{
|
||
|
if (is_array($this->state))
|
||
|
{
|
||
|
if (count($this->state) == 1)
|
||
|
{
|
||
|
return 1 ;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return 2 ;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return 0 ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This is the negative inference of Sudoku. By eliminating values the
|
||
|
* cells approach solutions. Once a cell has been completely eliminated,
|
||
|
* the value causing the complete elimination must be the solution and the
|
||
|
* cell is promoted into the solved state.
|
||
|
*
|
||
|
* @desc Eliminate one or more values from the state information of the cell.
|
||
|
* @access public
|
||
|
* @param mixed The value or values to be removed from the cell state.
|
||
|
* @return boolean True if the cell state was modified, false otherwise.
|
||
|
*/
|
||
|
|
||
|
function un_set($theValues)
|
||
|
{
|
||
|
if (is_array($theValues))
|
||
|
{
|
||
|
$theReturn = FALSE ;
|
||
|
|
||
|
foreach ($theValues as $theValue)
|
||
|
{
|
||
|
$theReturn |= $this->un_set($theValue) ;
|
||
|
}
|
||
|
|
||
|
return $theReturn ;
|
||
|
}
|
||
|
|
||
|
if (is_array($this->state))
|
||
|
{
|
||
|
$theReturn = isset($this->state[$theValues]) ;
|
||
|
unset($this->state[$theValues]) ;
|
||
|
if (count($this->state) == 0)
|
||
|
{
|
||
|
$this->state = $theValues ;
|
||
|
}
|
||
|
return $theReturn ;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return false ;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The individual row column or square on the Sudoku board.
|
||
|
*
|
||
|
* @package Sudoku
|
||
|
*/
|
||
|
|
||
|
class RCS extends ObjectS
|
||
|
{
|
||
|
var $theIndex ;
|
||
|
|
||
|
var $theRow = array() ;
|
||
|
|
||
|
var $theHeader = "" ;
|
||
|
|
||
|
var $theTag = "" ;
|
||
|
|
||
|
/**
|
||
|
* This
|
||
|
* @desc Constructor
|
||
|
* @access public
|
||
|
* @param string $theTag "Row", "Column", "Square", used primarily in debugging.
|
||
|
* @param integer $theIndex 1..9, where is this on the board. Square are numbered top
|
||
|
* left, ending bottom right
|
||
|
* @param ObjectS $a1..9 of class Cell. The cells comprising this entity. This interface is what
|
||
|
* limts things to 9x9 Sudoku currently.
|
||
|
*/
|
||
|
|
||
|
function RCS($theTag, $theIndex, &$a1, &$a2, &$a3, &$a4, &$a5, &$a6, &$a7, &$a8, &$a9)
|
||
|
{
|
||
|
$this->theTag = $theTag ;
|
||
|
$this->theIndex = $theIndex ;
|
||
|
$this->theRow[1] = &$a1 ;
|
||
|
$this->theRow[2] = &$a2 ;
|
||
|
$this->theRow[3] = &$a3 ;
|
||
|
$this->theRow[4] = &$a4 ;
|
||
|
$this->theRow[5] = &$a5 ;
|
||
|
$this->theRow[6] = &$a6 ;
|
||
|
$this->theRow[7] = &$a7 ;
|
||
|
$this->theRow[8] = &$a8 ;
|
||
|
$this->theRow[9] = &$a9 ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* There is a special case that comes up a lot in Sudoku. If there
|
||
|
* are values i, j, k and cells of the form (i, j), (j, k), (i, j, k)
|
||
|
* the the values i, j, and k cannot appear in any other cells. The
|
||
|
* proof is a simple "by contradiction" proof. Assume that the values
|
||
|
* do occur elsewhere and you always get a contradiction for these
|
||
|
* three cells. I'm pretty sure that this is a general rule, but for
|
||
|
* 9x9 Sudoku, they probably aren't of interested.
|
||
|
*
|
||
|
* @desc
|
||
|
* @access private
|
||
|
* @return boolean True if a 23 solution exists and has been applied.
|
||
|
*/
|
||
|
|
||
|
function _23Solution()
|
||
|
{
|
||
|
$theCounts = array() ;
|
||
|
$theTuples = array() ;
|
||
|
$theUnsolved = 0 ;
|
||
|
|
||
|
for ($i = 1; $i <= 9; $i++)
|
||
|
{
|
||
|
$j = count($this->theRow[$i]->getState());
|
||
|
$theCounts[ $j][] = $i ;
|
||
|
$theUnsolved++ ;
|
||
|
}
|
||
|
|
||
|
if( array_key_exists( 2, $theCounts) and array_key_exists( 3, $theCounts))
|
||
|
{
|
||
|
if ((count($theCounts[2]) < 2) || (count($theCounts[3]) < 1))
|
||
|
return false ;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Look at each pair of 2 tuples and see if their union exists in the 3 tuples.
|
||
|
* If so, eliminate everything from the set and bail.
|
||
|
*/
|
||
|
|
||
|
$the2Tuples = &$theCounts[2] ;
|
||
|
$the3Tuples = &$theCounts[3] ;
|
||
|
$theCount2 = count($the2Tuples) ;
|
||
|
$theCount3 = count($the3Tuples) ;
|
||
|
|
||
|
for ($i = 0; $i < $theCount2 - 1; $i++)
|
||
|
{
|
||
|
for ($j = $i + 1; $j < $theCount2; $j++)
|
||
|
{
|
||
|
$xxx = array_unique(array_merge($this->theRow[$the2Tuples[$i]]->getState(),
|
||
|
$this->theRow[$the2Tuples[$j]]->getState())) ;
|
||
|
for ($k = 0; $k < $theCount3; $k++)
|
||
|
{
|
||
|
if ($this->array_equal($xxx, $this->theRow[$the3Tuples[$k]]->getState()))
|
||
|
{
|
||
|
$theTuples[] = $xxx ;
|
||
|
break ;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Since it takes 3 cells to construct the 23 tuple, unless there are more than 3
|
||
|
* unsolved cells, further work doesn't make any sense.
|
||
|
*/
|
||
|
|
||
|
$theReturn = false ;
|
||
|
|
||
|
if ((count($theTuples) != 0) && ($theUnsolved > 3))
|
||
|
{
|
||
|
foreach ($theTuples as $aTuple)
|
||
|
{
|
||
|
foreach($this->theRow as $theCell)
|
||
|
{
|
||
|
$theReturn |= $theCell->apply23Tuple($aTuple) ;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ($theReturn)
|
||
|
{
|
||
|
$this->theHeader[] = sprintf("<br />Apply %s[%d] 23 Tuple Inference:", $this->theTag, $this->theIndex) ;
|
||
|
}
|
||
|
|
||
|
return $theReturn ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @desc apply a tuple to exclude items from within the row/column/square.
|
||
|
* @param array $aTuple the tuple to be excluded.
|
||
|
* @access private
|
||
|
* @return boolean true if anything changes.
|
||
|
*/
|
||
|
|
||
|
function _applyTuple(&$aTuple)
|
||
|
{
|
||
|
$theReturn = FALSE ;
|
||
|
|
||
|
for ($i = 1; $i <=9; $i++)
|
||
|
{
|
||
|
$theReturn |= $this->theRow[$i]->applyTuple($aTuple) ;
|
||
|
}
|
||
|
|
||
|
return $theReturn ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This is a placeholder to be overridden to calculate the "coupling" for
|
||
|
* a cell. Coupling is defined to be the sum of the sizes of the intersection
|
||
|
* between this cell and all others in the row/column/square. This provides
|
||
|
* a metric for deciding placement of clues within puzzles. In effect, this
|
||
|
* forces the puzzle generator to select places for new clues depending upon
|
||
|
* how little information is changed by altering the state of a cell. The larger
|
||
|
* the number returned by the coupling, function, the less information is currently
|
||
|
* available for the state of the cell. By selecting areas with the least information
|
||
|
* the clue sets are substantially smaller than simple random placement.
|
||
|
*
|
||
|
* @desc Calculate the coupling for a cell within the row/column/square.
|
||
|
* @access abstract
|
||
|
* @param integer $theRow the row coordinate on the board of the cell.
|
||
|
* @param integer $theColumn the column coordinate on the board of the cell.
|
||
|
* @return integer the degree of coupling between the cell and the rest of the cells
|
||
|
* within the row/column/square.
|
||
|
*/
|
||
|
|
||
|
function coupling($theRow, $theColumn)
|
||
|
{
|
||
|
return 0 ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* I think that the goal of the inference engine is to eliminate
|
||
|
* as much "junk" state as possible on each pass. Therefore the
|
||
|
* order of the inferences should be 23 tuple, pair, unique because
|
||
|
* the 23 tuple allows you to eliminate 3 values (if it works), and the
|
||
|
* pair (generally) only 2. The unique solution adds no new information.
|
||
|
*
|
||
|
* @desc Run the inference engine for a row/column/square.
|
||
|
* @access public
|
||
|
* @param array theRow A row/column/square data structure.
|
||
|
* @param string theType A string merged with the standard headers during
|
||
|
* intermediate solution printing.
|
||
|
* @return boolean True when at least one inference has succeeded.
|
||
|
*/
|
||
|
|
||
|
function doAnInference()
|
||
|
{
|
||
|
$this->theHeader = NULL ;
|
||
|
|
||
|
$theReturn = $this->_23Solution() ;
|
||
|
$theReturn |= $this->_pairSolution() ;
|
||
|
$theReturn |= $this->_uniqueSolution() ;
|
||
|
|
||
|
return $theReturn ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @desc Find all tuples with the same contents.
|
||
|
* @param array Array of n size tuples.
|
||
|
* @returns array of tuples that appear the same number of times as the size of the contents
|
||
|
*/
|
||
|
|
||
|
function _findTuples(&$theArray)
|
||
|
{
|
||
|
$theReturn = array() ;
|
||
|
for ($i = 0; $i < count($theArray); $i++)
|
||
|
{
|
||
|
$theCount = 1 ;
|
||
|
|
||
|
for ($j = $i + 1; $j < count($theArray); $j++)
|
||
|
{
|
||
|
$s1 = &$this->theRow[$theArray[$i]] ;
|
||
|
$s1 =& $s1->getState() ;
|
||
|
|
||
|
$s2 = &$this->theRow[$theArray[$j]] ;
|
||
|
$s2 =& $s2->getState() ;
|
||
|
|
||
|
$aCount = count($s1) ;
|
||
|
|
||
|
if ($this->array_equal($s1, $s2))
|
||
|
{
|
||
|
$theCount++ ;
|
||
|
|
||
|
if ($theCount == $aCount)
|
||
|
{
|
||
|
$theReturn[] = $s1 ;
|
||
|
break ;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $theReturn ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @desc Get a reference to the specified cell.
|
||
|
* @access public
|
||
|
* @return reference to ObjectS of class Cell.
|
||
|
*/
|
||
|
|
||
|
function &getCell($i)
|
||
|
{
|
||
|
return $this->theRow[$i] ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @desc Get the header set by the last call to doAnInference.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
function getHeader()
|
||
|
{
|
||
|
return $this->theHeader ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Turns out if you every find a position of n squares which can only contain
|
||
|
* the same values, then those values cannot appear elsewhere in the structure.
|
||
|
* This is a second positive inference that provides additional negative information.
|
||
|
* Thanks to Ghica van Emde Boas (also an author of a Sudoku class) for convincing
|
||
|
* me that these situations really occurred.
|
||
|
*
|
||
|
* @desc Eliminate tuple-locked alternatives.
|
||
|
* @access private
|
||
|
* @return boolean True if something changed.
|
||
|
*/
|
||
|
|
||
|
function _pairSolution()
|
||
|
{
|
||
|
$theCounts = array() ;
|
||
|
$theTuples = array() ;
|
||
|
|
||
|
for ($i = 1; $i <= 9; $i++)
|
||
|
{
|
||
|
$c = &$this->theRow[$i] ;
|
||
|
$theCounts[count($c->getState())][] = $i ;
|
||
|
}
|
||
|
|
||
|
unset($theCounts[1]) ;
|
||
|
|
||
|
/*
|
||
|
** Get rid of any set of counts which cannot possibly meet the
|
||
|
** requirements.
|
||
|
*/
|
||
|
|
||
|
$thePossibilities = $theCounts ;
|
||
|
|
||
|
foreach ($theCounts as $theKey => $theValue)
|
||
|
{
|
||
|
if (count($theValue) < $theKey)
|
||
|
{
|
||
|
unset($thePossibilities[$theKey]) ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (count($thePossibilities) == 0)
|
||
|
{
|
||
|
return false ;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* At this point there are 1 or more tuples which MAY satisfy the conditions.
|
||
|
*/
|
||
|
|
||
|
$theReturn = false ;
|
||
|
|
||
|
foreach ($thePossibilities as $theValue)
|
||
|
{
|
||
|
$theTuples = $this->_findTuples($theValue) ;
|
||
|
|
||
|
if (count($theTuples) != 0)
|
||
|
{
|
||
|
foreach ($theTuples as $aTuple)
|
||
|
{
|
||
|
$theReturn |= $this->_applyTuple($aTuple) ;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ($theReturn)
|
||
|
{
|
||
|
$this->theHeader[] = sprintf("<br />Apply %s[%d] Pair Inference:", $this->theTag, $this->theIndex) ;
|
||
|
}
|
||
|
|
||
|
return $theReturn ;
|
||
|
}
|
||
|
|
||
|
function un_set($theValues)
|
||
|
{
|
||
|
$theReturn = false ;
|
||
|
|
||
|
for ($i = 1; $i <= 9; $i++)
|
||
|
{
|
||
|
$c = &$this->theRow[$i] ;
|
||
|
$theReturn |= $c->un_set($theValues) ;
|
||
|
}
|
||
|
|
||
|
return $theReturn ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Find a solution to a row/column/square.
|
||
|
*
|
||
|
* Find any unique numbers within the row/column/square under consideration.
|
||
|
* Look through a row structure for a value that appears in only one cell.
|
||
|
* When you find one, that's a solution for that cell.
|
||
|
*
|
||
|
* There is a second inference that can be taken. Given "n" cells in a row/column/square
|
||
|
* and whose values can only consist of a set of size "n", then those values may obtain
|
||
|
* there and ONLY there and may be eliminated from consideration in the rest of the set.
|
||
|
* For example, if two cells must contain the values 5 or 6, then no other cell in that
|
||
|
* row/column/square may contain those values, similarly for 3 cells, etc.
|
||
|
*
|
||
|
* @access private
|
||
|
* @return boolean True if one or more values in the RCS has changed state.
|
||
|
*/
|
||
|
|
||
|
function _uniqueSolution()
|
||
|
{
|
||
|
$theSet = array() ;
|
||
|
|
||
|
for ($i = 1; $i <= 9; $i++)
|
||
|
{
|
||
|
$c = &$this->theRow[$i] ;
|
||
|
if (!$c->IsSolved())
|
||
|
{
|
||
|
foreach ($c->getState() as $theValue)
|
||
|
{
|
||
|
$theSet[$theValue][] = $i ;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* If there were no unsolved positions, then we're done and nothing has
|
||
|
* changed.
|
||
|
*/
|
||
|
|
||
|
if (count($theSet) == 0)
|
||
|
{
|
||
|
return false ;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Pull out all those keys having only one occurrance in the RCS.
|
||
|
*/
|
||
|
|
||
|
foreach ($theSet as $theKey => $theValues)
|
||
|
{
|
||
|
if (count($theValues) != 1)
|
||
|
{
|
||
|
unset($theSet[$theKey]) ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* If there aren't any unique values, we're done.
|
||
|
*/
|
||
|
|
||
|
if (count($theSet) == 0)
|
||
|
{
|
||
|
return false ;
|
||
|
}
|
||
|
|
||
|
foreach ($theSet as $theValue => $theIndex)
|
||
|
{
|
||
|
$this->theRow[$theIndex[0]]->flagSolvedPosition($theValue) ;
|
||
|
}
|
||
|
|
||
|
$this->theHeader[] = sprintf("<br />Apply %s[%d] Unique Inference:", $this->theTag, $this->theIndex) ;
|
||
|
|
||
|
return true ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @desc Check to see if the RCS contains a valid state.
|
||
|
* @access public
|
||
|
* @return boolean True if the state of the RCS could be part of a valid
|
||
|
* solution, false otherwise.
|
||
|
*/
|
||
|
|
||
|
function validateSolution()
|
||
|
{
|
||
|
$theNewSet = array() ;
|
||
|
|
||
|
foreach ($this->theRow as $theCell)
|
||
|
{
|
||
|
if ($theCell->solvedState() == 0)
|
||
|
{
|
||
|
$theNewSet[] = $theCell->getState() ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$xxx = array_unique($theNewSet) ;
|
||
|
|
||
|
return (count($xxx) == count($this->theRow)) ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Validate a part of a trial solution.
|
||
|
*
|
||
|
* Check a row/column/square to see if there are any invalidations on this solution.
|
||
|
* Only items that are actually solved are compared. This is used during puzzle
|
||
|
* generation.
|
||
|
*
|
||
|
* @access public
|
||
|
* @return True if the input parameter contains a valid solution, false otherwise.
|
||
|
*/
|
||
|
|
||
|
function validateTrialSolution()
|
||
|
{
|
||
|
$theNewSet = array() ;
|
||
|
|
||
|
foreach($this->theRow as $theCell)
|
||
|
{
|
||
|
if ($theCell->solvedState() == 0)
|
||
|
{
|
||
|
$theNewSet[] = $theCell->getState() ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$xxx = array_unique($theNewSet) ;
|
||
|
|
||
|
return ((count($xxx) == count($theNewSet) ? TRUE : FALSE)) ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Row ObjectS.
|
||
|
*
|
||
|
* @package Sudoku
|
||
|
*/
|
||
|
|
||
|
class R extends RCS
|
||
|
{
|
||
|
/**
|
||
|
* @desc Constructor
|
||
|
* @access public
|
||
|
* @param string $theTag "Row", "Column", "Square", used primarily in debugging.
|
||
|
* @param integer $theIndex 1..9, where is this on the board. Square are numbered top
|
||
|
* left, ending bottom right
|
||
|
* @param ObjectS $a1..9 of class Cell. The cells comprising this entity. This interface is what
|
||
|
* limts things to 9x9 Sudoku currently.
|
||
|
*/
|
||
|
|
||
|
function R($theTag, $theIndex, &$a1, &$a2, &$a3, &$a4, &$a5, &$a6, &$a7, &$a8, &$a9)
|
||
|
{
|
||
|
$this->RCS($theTag, $theIndex, $a1, $a2, $a3, $a4, $a5, $a6, $a7, $a8, $a9) ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @see RCS::coupling
|
||
|
*/
|
||
|
|
||
|
function coupling($theRow, $theColumn)
|
||
|
{
|
||
|
return $theState = $this->_coupling($theColumn) ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @see RCS::coupling
|
||
|
* @desc Heavy lifting for row/column coupling calculations.
|
||
|
* @access private
|
||
|
* @param integer $theIndex the index of the cell within the row or column.
|
||
|
* @return integer the "coupling coefficient" for the cell. The sum of the
|
||
|
* sizes of the intersection between this and all other
|
||
|
* cells in the row or column.
|
||
|
*/
|
||
|
|
||
|
function _coupling($theIndex)
|
||
|
{
|
||
|
$theCommonState =& $this->getCell($theIndex) ;
|
||
|
$theCommonState =& $theCommonState->getState() ;
|
||
|
|
||
|
$theCoupling = 0 ;
|
||
|
|
||
|
for ($i = 1; $i <= count($this->theRow); $i++)
|
||
|
{
|
||
|
if ($i != $theIndex)
|
||
|
{
|
||
|
$theCell =& $this->getCell($i) ;
|
||
|
if ($theCell->solvedState() != 0)
|
||
|
{
|
||
|
$theCoupling += count(array_intersect($theCommonState, $theCell->getState())) ;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $theCoupling ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The column ObjectS.
|
||
|
*
|
||
|
* @package Sudoku
|
||
|
*/
|
||
|
|
||
|
class C extends R
|
||
|
{
|
||
|
/**
|
||
|
* @desc Constructor
|
||
|
* @access public
|
||
|
* @param string $theTag "Row", "Column", "Square", used primarily in debugging.
|
||
|
* @param integer $theIndex 1..9, where is this on the board. Square are numbered top
|
||
|
* left, ending bottom right
|
||
|
* @param ObjectS $a1..9 of class Cell. The cells comprising this entity. This interface is what
|
||
|
* limts things to 9x9 Sudoku currently.
|
||
|
*/
|
||
|
|
||
|
function C($theTag, $theIndex, &$a1, &$a2, &$a3, &$a4, &$a5, &$a6, &$a7, &$a8, &$a9)
|
||
|
{
|
||
|
$this->R($theTag, $theIndex, $a1, $a2, $a3, $a4, $a5, $a6, $a7, $a8, $a9) ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @see R::coupling
|
||
|
*/
|
||
|
|
||
|
function coupling($theRow, $theColumn)
|
||
|
{
|
||
|
return $theState = $this->_coupling($theRow) ;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The Square ObjectS.
|
||
|
*
|
||
|
* @package Sudoku
|
||
|
*/
|
||
|
|
||
|
class S extends RCS
|
||
|
{
|
||
|
/**
|
||
|
* The cells within the 3x3 sudoku which participate in the coupling calculation for a square.
|
||
|
* Remember that the missing cells have already participated in the row or column coupling
|
||
|
* calculation.
|
||
|
*
|
||
|
* @access private
|
||
|
* @var array
|
||
|
*/
|
||
|
|
||
|
var $theCouplingOrder =
|
||
|
array( 1 => array(5, 6, 8, 9),
|
||
|
2 => array(4, 6, 7, 9),
|
||
|
3 => array(4, 5, 7, 8),
|
||
|
4 => array(2, 3, 8, 9),
|
||
|
5 => array(1, 3, 7, 9),
|
||
|
6 => array(1, 2, 7, 8),
|
||
|
7 => array(2, 3, 5, 6),
|
||
|
8 => array(1, 3, 4, 6),
|
||
|
9 => array(1, 2, 4, 5)) ;
|
||
|
|
||
|
/**
|
||
|
* @desc Constructor
|
||
|
* @access public
|
||
|
* @param string $theTag "Row", "Column", "Square", used primarily in debugging.
|
||
|
* @param integer $theIndex 1..9, where is this on the board. Square are numbered top
|
||
|
* left, ending bottom right
|
||
|
* @param ObjectS $a1..9 of class Cell. The cells comprising this entity. This interface is what
|
||
|
* limts things to 9x9 Sudoku currently.
|
||
|
*/
|
||
|
|
||
|
function S($theTag, $theIndex, &$a1, &$a2, &$a3, &$a4, &$a5, &$a6, &$a7, &$a8, &$a9)
|
||
|
{
|
||
|
$this->RCS($theTag, $theIndex, $a1, $a2, $a3, $a4, $a5, $a6, $a7, $a8, $a9) ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @see RCS::coupling
|
||
|
*/
|
||
|
|
||
|
function coupling($theRow, $theColumn)
|
||
|
{
|
||
|
$theIndex = ((($theRow - 1) % 3) * 3) + (($theColumn - 1) % 3) + 1 ;
|
||
|
$theCommonState =& $this->getCell($theIndex) ;
|
||
|
$theCommonState =& $theCommonState->getState() ;
|
||
|
|
||
|
$theCoupling = 0 ;
|
||
|
|
||
|
foreach ($this->theCouplingOrder[$theIndex] as $i)
|
||
|
{
|
||
|
$theCell =& $this->getCell($i) ;
|
||
|
if ($theCell->solvedState() != 0)
|
||
|
{
|
||
|
$theCoupling += count(array_intersect($theCommonState, $theCell->getState())) ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $theCoupling ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Solve and generate Sudoku puzzles.
|
||
|
*
|
||
|
* Solve and generate Sudoku. A simple output interface is provided for
|
||
|
* web pages. The primary use of this class is as infra-structure for
|
||
|
* Sudoku game sites.
|
||
|
*
|
||
|
* The solver side of this class (solve) relies on the usual characteristic
|
||
|
* of logic puzzles, i.e., at any point in time there is one (or more)
|
||
|
* UNIQUE solution to some part of the puzzle. This solution can be
|
||
|
* applied, then iterated upon to find the next part of the puzzle. A
|
||
|
* properly constructed Sudoku can have only one solution which guarangees
|
||
|
* that this is the case. (Sudoku with multiple solutions will always
|
||
|
* require guessing at some point which is specifically disallowed by
|
||
|
* the rules of Sudoku).
|
||
|
*
|
||
|
* While the solver side is algorithmic, the generator side is much more
|
||
|
* difficult and, in fact, the generation of Sudoku appears to be NP
|
||
|
* complete. That being the case, I observed that most successful
|
||
|
* generated initial conditions happened quickly, typically with < 40
|
||
|
* iterations. So the puzzle generator runs "for a while" until it
|
||
|
* either succeeds or doesn't generated a solveable puzzle. If we get
|
||
|
* to that position, I just retry and so far I've always succeeded in
|
||
|
* generating an initial state. Not guarateed, but in engineering terms
|
||
|
* "close enough".
|
||
|
*
|
||
|
* @package Sudoku
|
||
|
* @example ./example.php
|
||
|
* @example ./example1.php
|
||
|
* @example ./example2.php
|
||
|
* @example ./example3.php
|
||
|
*/
|
||
|
|
||
|
class Sudoku extends ObjectS
|
||
|
{
|
||
|
/**
|
||
|
* An array of Cell ObjectSs, organized into rows and columns.
|
||
|
*
|
||
|
* @access private
|
||
|
* @var array of ObjectSs of type Cell.
|
||
|
*/
|
||
|
|
||
|
var $theBoard = array() ;
|
||
|
|
||
|
/**
|
||
|
* True if debugging output is to be provided during a run.
|
||
|
*
|
||
|
* @access private
|
||
|
* @var boolean
|
||
|
*/
|
||
|
|
||
|
var $theDebug = FALSE ;
|
||
|
|
||
|
/**
|
||
|
* An array of RCS ObjectSs, one ObjectS for each row.
|
||
|
*
|
||
|
* @access private
|
||
|
* @var ObjectS of type R
|
||
|
*/
|
||
|
|
||
|
var $theRows = array() ;
|
||
|
|
||
|
/**
|
||
|
* An array of RCS ObjectSs, one ObjectS for each Column.
|
||
|
*
|
||
|
* @access private
|
||
|
* @var ObjectS of type C
|
||
|
*/
|
||
|
|
||
|
var $theColumns = array() ;
|
||
|
|
||
|
/**
|
||
|
* An array of RCS ObjectSs, one ObjectS for each square.
|
||
|
*
|
||
|
* @access private
|
||
|
* @var ObjectS of type S
|
||
|
*/
|
||
|
|
||
|
var $theSquares = array() ;
|
||
|
|
||
|
/**
|
||
|
* Used during puzzle generation for debugging output. There may
|
||
|
* eventually be some use of theLevel to figure out where to stop
|
||
|
* the backtrace when puzzle generation fails.
|
||
|
*
|
||
|
* @access private
|
||
|
* @var integer.
|
||
|
*/
|
||
|
|
||
|
var $theLevel = 0 ;
|
||
|
|
||
|
/**
|
||
|
* Used during puzzle generation to determine when the generation
|
||
|
* will fail. Failure, in this case, means to take a LONG time. The
|
||
|
* backtracing algorithm used in the puzzle generator will always find
|
||
|
* a solution, it just might take a very long time. This is a way to
|
||
|
* limit the damage before taking another guess.
|
||
|
*
|
||
|
* @access private
|
||
|
* @var integer.
|
||
|
*/
|
||
|
|
||
|
var $theMaxIterations = 50 ;
|
||
|
|
||
|
/**
|
||
|
* Used during puzzle generation to limit the number of trys at
|
||
|
* generation a puzzle in the event puzzle generation fails
|
||
|
* (@see Suduko::$theMaxIterations). I've never seen more than
|
||
|
* a couple of failures in a row, so this should be sufficient
|
||
|
* to get a puzzle generated.
|
||
|
*
|
||
|
* @access private
|
||
|
* @var integer.
|
||
|
*/
|
||
|
|
||
|
var $theTrys = 10 ;
|
||
|
|
||
|
/**
|
||
|
* Used during puzzle generation to count the number of iterations
|
||
|
* during puzzle generation. It the number gets above $theMaxIterations,
|
||
|
* puzzle generation has failed and another try is made.
|
||
|
*
|
||
|
* @access private
|
||
|
* @var integer.
|
||
|
*/
|
||
|
|
||
|
var $theGenerationIterations = 0 ;
|
||
|
|
||
|
function Sudoku($theDebug = FALSE)
|
||
|
{
|
||
|
$this->theDebug = $theDebug ;
|
||
|
|
||
|
for ($i = 1; $i <= 9; $i++)
|
||
|
{
|
||
|
for ($j = 1; $j <= 9; $j++)
|
||
|
{
|
||
|
$this->theBoard[$i][$j] = new Cell($i, $j) ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$this->_buildRCS() ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Apply a pending solved position to the row/square/column.
|
||
|
*
|
||
|
* At this point, the board has been populated with any pending solutions.
|
||
|
* This applies the "negative" inference that no row, column, or square
|
||
|
* containing the value within the cell.
|
||
|
*
|
||
|
* @access private
|
||
|
* @param integer $row The row of the board's element whose value is now fixed.
|
||
|
* @param integer $col The column of the board's element whose value is now fixed.
|
||
|
*/
|
||
|
|
||
|
function _applySolvedPosition($row, $col)
|
||
|
{
|
||
|
$theValue = $this->theBoard[$row][$col]->getState() ;
|
||
|
|
||
|
/*
|
||
|
** No other cell in the row, column, or square can take on the value "value" any longer.
|
||
|
*/
|
||
|
|
||
|
$i = (((int)(($row - 1) / 3)) * 3) ;
|
||
|
$i = $i + ((int)(($col - 1) / 3)) + 1 ;
|
||
|
|
||
|
$this->theRows[$row]->un_set($theValue) ;
|
||
|
|
||
|
$this->theColumns[$col]->un_set($theValue) ;
|
||
|
|
||
|
$this->theSquares[$i]->un_set($theValue) ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @desc Apply all pending solved positions to the board.
|
||
|
* @access private
|
||
|
* @return boolean True if at least one solved position was applied, false
|
||
|
* otherwise.
|
||
|
*/
|
||
|
|
||
|
function _applySolvedPositions()
|
||
|
{
|
||
|
$theReturn = false ;
|
||
|
|
||
|
for ($i = 1; $i <= 9; $i++)
|
||
|
{
|
||
|
for ($j = 1; $j <= 9; $j++)
|
||
|
{
|
||
|
if (!$this->theBoard[$i][$j]->IsApplied())
|
||
|
{
|
||
|
if ($this->theBoard[$i][$j]->solvedState() == 0)
|
||
|
{
|
||
|
$this->_applySolvedPosition($i, $j) ;
|
||
|
|
||
|
/*
|
||
|
** Update the solved position matrix and make sure that the board actually
|
||
|
** has a value in place.
|
||
|
*/
|
||
|
|
||
|
$this->theBoard[$i][$j]->applied() ;
|
||
|
$theReturn = TRUE ;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $theReturn ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @desc build the row/column/square structures for the board.
|
||
|
* @access private
|
||
|
*/
|
||
|
|
||
|
function _buildRCS()
|
||
|
{
|
||
|
for ($i = 1; $i <= 9; $i++)
|
||
|
{
|
||
|
$this->theRows[$i] =
|
||
|
new R("Row",
|
||
|
$i,
|
||
|
$this->theBoard[$i][1],
|
||
|
$this->theBoard[$i][2],
|
||
|
$this->theBoard[$i][3],
|
||
|
$this->theBoard[$i][4],
|
||
|
$this->theBoard[$i][5],
|
||
|
$this->theBoard[$i][6],
|
||
|
$this->theBoard[$i][7],
|
||
|
$this->theBoard[$i][8],
|
||
|
$this->theBoard[$i][9]) ;
|
||
|
$this->theColumns[$i] =
|
||
|
new C("Column",
|
||
|
$i,
|
||
|
$this->theBoard[1][$i],
|
||
|
$this->theBoard[2][$i],
|
||
|
$this->theBoard[3][$i],
|
||
|
$this->theBoard[4][$i],
|
||
|
$this->theBoard[5][$i],
|
||
|
$this->theBoard[6][$i],
|
||
|
$this->theBoard[7][$i],
|
||
|
$this->theBoard[8][$i],
|
||
|
$this->theBoard[9][$i]) ;
|
||
|
|
||
|
$r = ((int)(($i - 1) / 3)) * 3 ;
|
||
|
$c = (($i - 1) % 3) * 3 ;
|
||
|
|
||
|
$this->theSquares[$i] =
|
||
|
new S("Square",
|
||
|
$i,
|
||
|
$this->theBoard[$r + 1][$c + 1],
|
||
|
$this->theBoard[$r + 1][$c + 2],
|
||
|
$this->theBoard[$r + 1][$c + 3],
|
||
|
$this->theBoard[$r + 2][$c + 1],
|
||
|
$this->theBoard[$r + 2][$c + 2],
|
||
|
$this->theBoard[$r + 2][$c + 3],
|
||
|
$this->theBoard[$r + 3][$c + 1],
|
||
|
$this->theBoard[$r + 3][$c + 2],
|
||
|
$this->theBoard[$r + 3][$c + 3]) ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Seek alternate solutions in a solution set.
|
||
|
*
|
||
|
* Given a solution, see if there are any alternates within the solution.
|
||
|
* In theory this should return the "minimum" solution given any solution.
|
||
|
*
|
||
|
* @access public
|
||
|
* @param array $theInitialState (@see Sudoku::initializePuzzleFromArray)
|
||
|
* @return array A set of triples containing the minimum solution.
|
||
|
*/
|
||
|
|
||
|
function findAlternateSolution($theInitialState)
|
||
|
{
|
||
|
$j = count($theInitialState) ;
|
||
|
|
||
|
for ($i = 0; $i < $j; $i++)
|
||
|
{
|
||
|
$xxx = $theInitialState ;
|
||
|
|
||
|
$xxx = array_splice($xxx, $i, 1) ;
|
||
|
|
||
|
$this->Sudoku() ;
|
||
|
|
||
|
$this->initializePuzzleFromArray($xxx) ;
|
||
|
|
||
|
if ($this->solve())
|
||
|
{
|
||
|
return $this->findAlternateSolution($xxx) ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $theInitialState ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Initialize Sudoku puzzle generation and generate a puzzle.
|
||
|
*
|
||
|
* Turns out that while the solution of Sudoku is mechanical, the creation of
|
||
|
* Sudoku is an NP-Complete problem. Which means that I can use the inference
|
||
|
* engine to help generate puzzles, but I need to test the solution to see if
|
||
|
* I've gone wrong and back up and change my strategy. So something in the
|
||
|
* recursive descent arena will be necessary. Since the generation can take
|
||
|
* a long time to force a solution, it's easier to probe for a solution
|
||
|
* if you go "too long".
|
||
|
*
|
||
|
* @access public
|
||
|
* @param integer $theDifficultyLevel [optional] Since virtually everybody who
|
||
|
* plays sudoku wants a variety of difficulties this controls that.
|
||
|
* 1 is the easiest, 10 the most difficult. The easier Sudoku have
|
||
|
* extra information.
|
||
|
* @param integer $theMaxInterations [optional] Controls the number of iterations
|
||
|
* before the puzzle generator gives up and trys a different set
|
||
|
* of initial parameters.
|
||
|
* @param integer $theTrys [optional] The number of attempts at resetting the
|
||
|
* initial parameters before giving up.
|
||
|
* @return array A set of triples suitable for initializing a new Sudoku class
|
||
|
* (@see Sudoku::initializePuzzleFromArray).
|
||
|
*/
|
||
|
|
||
|
function generatePuzzle($theDifficultyLevel = 10, $theMaxIterations = 50, $theTrys = 10)
|
||
|
{
|
||
|
$theDifficultyLevel = min($theDifficultyLevel, 10) ;
|
||
|
$theDifficultyLevel = max($theDifficultyLevel, 1) ;
|
||
|
|
||
|
$this->theLevel = 0 ;
|
||
|
$this->theTrys = $theTrys ;
|
||
|
$this->theMaxIterations = $theMaxIterations ;
|
||
|
$this->theGenerationIterations = 0 ;
|
||
|
|
||
|
for ($theTrys = 0; $theTrys < $this->theTrys ; $theTrys++)
|
||
|
{
|
||
|
$theAvailablePositions = array() ;
|
||
|
$theCluesPositions = array() ;
|
||
|
$theClues = array() ;
|
||
|
|
||
|
for ($i = 1; $i <= 9; $i++)
|
||
|
{
|
||
|
for ($j = 1; $j <= 9; $j++)
|
||
|
$theAvailablePositions[] = array($i, $j) ;
|
||
|
}
|
||
|
|
||
|
$theInitialState = $this->_generatePuzzle($theAvailablePositions, $theCluesPositions, $theClues) ;
|
||
|
|
||
|
if ($theInitialState)
|
||
|
{
|
||
|
if ($theDifficultyLevel != 10)
|
||
|
{
|
||
|
$xxx = array() ;
|
||
|
|
||
|
foreach ($theInitialState as $yyy)
|
||
|
$xxx[] = (($yyy[0] - 1) * 9) + ($yyy[1] - 1) ;
|
||
|
|
||
|
/*
|
||
|
** Get rid of the available positions already used in the initial state.
|
||
|
*/
|
||
|
|
||
|
sort($xxx) ;
|
||
|
$xxx = array_reverse($xxx) ;
|
||
|
|
||
|
foreach ($xxx as $i)
|
||
|
array_splice($theAvailablePositions, $i, 1) ;
|
||
|
|
||
|
/*
|
||
|
** Easy is defined as the number of derivable clues added to the minimum
|
||
|
** required information to solve the puzzle as returned by _generatePuzzle.
|
||
|
*/
|
||
|
|
||
|
for ($i = 0; $i < (10 - $theDifficultyLevel); $i++)
|
||
|
{
|
||
|
$xxx = mt_rand(0, count($theAvailablePositions)-1) ;
|
||
|
$row = $theAvailablePositions[$xxx][0] ;
|
||
|
$col = $theAvailablePositions[$xxx][1] ;
|
||
|
$theInitialState[] = array($row, $col, $this->theBoard[$row][$col]) ;
|
||
|
array_splice($theAvailablePositions, $xxx, 1) ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//echo "found $theTrys<br>";
|
||
|
return $theInitialState ;
|
||
|
}
|
||
|
|
||
|
if ($this->theDebug)
|
||
|
printf("<br>Too many iterations (%d), %d\n", $this->theMaxIterations, $theTrys);
|
||
|
|
||
|
$this->Sudoku($this->theDebug) ;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
** No solution possible, we guess wrong too many times.
|
||
|
*/
|
||
|
|
||
|
//echo "try=$theTrys<br>";
|
||
|
return array() ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sudoku puzzle generator.
|
||
|
*
|
||
|
* This is the routine that does the heavy lifting
|
||
|
* for the puzzle generation. It works by taking a guess for a value of a cell, applying
|
||
|
* the solver, testing the solution, and if it's a valid solution, calling itself
|
||
|
* recursively. If during this process, a solution cannot be found, the generator backs
|
||
|
* up (backtrace in Computer Science parlance) and trys another value. Since the generation
|
||
|
* appears to be an NP complete problem (according to the literature) limits on the number
|
||
|
* of iterations are asserted. Once these limits are passed, the generator gives up and
|
||
|
* makes another try. If enough tries are made, the generator gives up entirely.
|
||
|
*
|
||
|
* @access private
|
||
|
* @param array $theAvailablePositions A set of pairs for all positions which have not been
|
||
|
* filled by the solver or the set of guesses. When we run out of available
|
||
|
* positions, the solution is in hand.
|
||
|
* @param array $theCluesPositions A set of pairs for which values have been set by the
|
||
|
* puzzle generator.
|
||
|
* @param array $theClues A set of values for each pair in $theCluesPositions.
|
||
|
* @return array NULL array if no solution is possible, otherwise a set of triples
|
||
|
* suitable for feeding to {@link Sudoku::initializePuzzleFromArray}
|
||
|
*/
|
||
|
|
||
|
function _generatePuzzle($theAvailablePositions, $theCluesPositions, $theClues)
|
||
|
{
|
||
|
$this->theLevel++ ;
|
||
|
|
||
|
$this->theGenerationIterations++ ;
|
||
|
|
||
|
/*
|
||
|
** Since the last solution sequence may have eliminated one or more positions by
|
||
|
** generating forced solutions for them, go through the list of available positions
|
||
|
** and get rid of any that have already been solved.
|
||
|
*/
|
||
|
|
||
|
$j = count($theAvailablePositions) ;
|
||
|
|
||
|
for ($i = 0; $i < $j; $i++)
|
||
|
{
|
||
|
if ($this->theBoard[$theAvailablePositions[$i][0]][$theAvailablePositions[$i][1]]->IsApplied())
|
||
|
{
|
||
|
array_splice($theAvailablePositions, $i, 1) ;
|
||
|
$i = $i - 1;
|
||
|
$j = $j - 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (count($theAvailablePositions) == 0)
|
||
|
{
|
||
|
/*
|
||
|
** We're done, so we can return the clues and their positions to the caller.
|
||
|
** This test is being done here to accommodate the eventual implementation of
|
||
|
** generation from templates in which partial boards will be fed to the solver
|
||
|
** and then the remaining board fed in.
|
||
|
*/
|
||
|
|
||
|
for ($i = 0; $i < count($theCluesPositions); $i++)
|
||
|
array_push($theCluesPositions[$i], $theClues[$i]) ;
|
||
|
|
||
|
return $theCluesPositions ;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
** Calculate the coupling for each available position.
|
||
|
**
|
||
|
** "coupling" is a measure of the amount of state affected by any change
|
||
|
** to a given cell. In effect, the larger the coupling, the less constrained
|
||
|
** the state of the cell is and the greater the effect of any change made to
|
||
|
** the cell. There is some literature to this effect associated with Roku puzzles
|
||
|
** (4x4 grid). I'm trying this attempting to find a way to generate consistently
|
||
|
** more difficult Sudoku and it seems to have worked; the clue count drops to 25 or
|
||
|
** fewer, more in line with the numbers predicted by the literature. The remainder
|
||
|
** of the work is likely to be associated with finding better algorithms to solve
|
||
|
** Sudoku (which would have the effect of generating harder ones).
|
||
|
*/
|
||
|
|
||
|
$theCouplings = array() ;
|
||
|
|
||
|
foreach ($theAvailablePositions as $xxx)
|
||
|
{
|
||
|
$theRowCoupling = $this->theRows[$xxx[0]]->coupling($xxx[0], $xxx[1]) ;
|
||
|
$theColumnCoupling = $this->theColumns[$xxx[1]]->coupling($xxx[0], $xxx[1]) ;
|
||
|
$theSquareCoupling = $this->theSquares[$this->_squareIndex($xxx[0], $xxx[1])]->coupling($xxx[0], $xxx[1]) ;
|
||
|
$theCouplings[$theRowCoupling + $theColumnCoupling + $theSquareCoupling][] = $xxx ;
|
||
|
}
|
||
|
|
||
|
$theMaximumCoupling = max(array_keys($theCouplings)) ;
|
||
|
|
||
|
/*
|
||
|
** Pick a spot on the board and get the clues set up.
|
||
|
*/
|
||
|
|
||
|
$theChoice = mt_rand(0, count($theCouplings[$theMaximumCoupling])-1) ;
|
||
|
$theCluesPositions[] = $theCouplings[$theMaximumCoupling][$theChoice] ;
|
||
|
$theRow = $theCouplings[$theMaximumCoupling][$theChoice][0] ;
|
||
|
$theColumn = $theCouplings[$theMaximumCoupling][$theChoice][1] ;
|
||
|
|
||
|
/*
|
||
|
** Capture the necessary global state of the board
|
||
|
*/
|
||
|
|
||
|
$theCurrentBoard = $this->deepCopy($this->theBoard) ;
|
||
|
|
||
|
/*
|
||
|
** This is all possible states for the chosen cell. All values will be
|
||
|
** randomly tried to see if a solution results. If all solutions fail,
|
||
|
** the we'll back up in time and try again.
|
||
|
*/
|
||
|
|
||
|
$thePossibleClues = array_keys($this->theBoard[$theRow][$theColumn]->getState()) ;
|
||
|
|
||
|
while (count($thePossibleClues) != 0)
|
||
|
{
|
||
|
if ($this->theGenerationIterations > $this->theMaxIterations)
|
||
|
{
|
||
|
$this->theLevel = $this->theLevel - 1 ;
|
||
|
return array() ;
|
||
|
}
|
||
|
|
||
|
$theClueChoice = mt_rand(0, count($thePossibleClues)-1) ;
|
||
|
$theValue = $thePossibleClues[$theClueChoice] ;
|
||
|
array_splice($thePossibleClues, $theClueChoice, 1) ;
|
||
|
|
||
|
$theClues[] = $theValue ;
|
||
|
|
||
|
$this->theBoard[$theRow][$theColumn]->flagSolvedPosition($theValue) ;
|
||
|
|
||
|
if ($this->theDebug ) { printf("<br>(%03d, %03d) Trying (%d, %d) = %d\n", $this->theLevel, $this->theGenerationIterations, $theRow, $theColumn, $theValue) ; } ;
|
||
|
|
||
|
$theFlag = $this->solve(false) ;
|
||
|
|
||
|
if ($this->_validateTrialSolution())
|
||
|
{
|
||
|
if ($theFlag)
|
||
|
{
|
||
|
/*
|
||
|
** We're done, so we can return the clues and their positions to the caller.
|
||
|
*/
|
||
|
|
||
|
for ($i = 0; $i < count($theCluesPositions); $i++)
|
||
|
{
|
||
|
array_push($theCluesPositions[$i], $theClues[$i]) ;
|
||
|
}
|
||
|
|
||
|
return $theCluesPositions ;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$xxx = $this->_generatePuzzle($theAvailablePositions, $theCluesPositions, $theClues) ;
|
||
|
|
||
|
if ($xxx)
|
||
|
{
|
||
|
return $xxx ;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
** We failed of a solution, back out the state and try the next possible value
|
||
|
** for this position.
|
||
|
*/
|
||
|
|
||
|
$this->theBoard = $theCurrentBoard ;
|
||
|
$this->_buildRCS() ;
|
||
|
array_pop($theClues) ;
|
||
|
}
|
||
|
|
||
|
$this->theLevel = $this->theLevel - 1 ;
|
||
|
|
||
|
/*
|
||
|
** If we get here, we've tried all possible values remaining for the chosen
|
||
|
** position and couldn't get a solution. Back out and try something else.
|
||
|
*/
|
||
|
|
||
|
return array() ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return the contents of the board as a string of digits and blanks. Blanks
|
||
|
* are used where the corresponding board item is an array, indicating the cell
|
||
|
* has not yet been solved.
|
||
|
*
|
||
|
* @desc Get the current state of the board as a string.
|
||
|
* @access public
|
||
|
*/
|
||
|
|
||
|
function getBoardAsString()
|
||
|
{
|
||
|
$theString = "" ;
|
||
|
|
||
|
for ($i = 1; $i <= 9; $i++)
|
||
|
{
|
||
|
for ($j = 1; $j <= 9; $j++)
|
||
|
{
|
||
|
$theString .= $this->theBoard[$i][$j]->asString() ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $theString ;
|
||
|
}
|
||
|
|
||
|
function &getCell($r, $c)
|
||
|
{
|
||
|
return $this->theBoard[$r][$c] ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Each element of the input array is a triple consisting of (row, column, value).
|
||
|
* Each of these values is in the range 1..9.
|
||
|
*
|
||
|
* @access public
|
||
|
* @param array $theArray
|
||
|
*/
|
||
|
|
||
|
function initializePuzzleFromArray($theArray)
|
||
|
{
|
||
|
foreach ($theArray as $xxx)
|
||
|
{
|
||
|
$c =& $this->getCell($xxx[0], $xxx[1]) ;
|
||
|
$c->flagSolvedPosition($xxx[2]) ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Initialize puzzle from an input file.
|
||
|
*
|
||
|
* The input file is a text file, blank or tab delimited, with each line being a
|
||
|
* triple consisting of "row column value". Each of these values is in the range
|
||
|
* 1..9. Input lines that are blank (all whitespace) or which begin with whitespace
|
||
|
* followed by a "#" character are ignored.
|
||
|
*
|
||
|
* @access public
|
||
|
* @param mixed $theHandle [optional] defaults to STDIN. If a string is passed
|
||
|
* instead of a file handle, the file is opened.
|
||
|
*/
|
||
|
|
||
|
function initializePuzzleFromFile($theHandle = STDIN)
|
||
|
{
|
||
|
$theOpenedFileFlag = FALSE ;
|
||
|
|
||
|
/*
|
||
|
** If a file name is passed instead of a resource, open the
|
||
|
** file and process it.
|
||
|
*/
|
||
|
|
||
|
if (is_string($theHandle))
|
||
|
{
|
||
|
$theHandle = fopen($theHandle, "r") ;
|
||
|
if ($theHandle === FALSE)
|
||
|
{
|
||
|
exit() ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$yyy = array() ;
|
||
|
|
||
|
if ($theHandle)
|
||
|
{
|
||
|
while (!feof($theHandle))
|
||
|
{
|
||
|
$theString = trim(fgets($theHandle)) ;
|
||
|
if (($theString != "") &&
|
||
|
(!preg_match('/^\s*#/', $theString)))
|
||
|
{
|
||
|
$xxx = preg_split('/\s+/', $theString) ;
|
||
|
if (!feof($theHandle))
|
||
|
{
|
||
|
$yyy[] = array((int)$xxx[0], (int)$xxx[1], (int)$xxx[2]) ;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$this->initializePuzzleFromArray($yyy) ;
|
||
|
|
||
|
if ($theOpenedFileFlag)
|
||
|
{
|
||
|
fclose($theHandle) ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The input parameter consists of a string of 81 digits and blanks. If fewer characters
|
||
|
* are provide, the string is padded on the right.
|
||
|
*
|
||
|
* @desc Initialize puzzle from a string.
|
||
|
* @access public
|
||
|
* @param string $theString The initial state of each cell in the puzzle.
|
||
|
*/
|
||
|
|
||
|
function initializePuzzleFromString($theString)
|
||
|
{
|
||
|
$theString = str_pad($theString, 81, " ") ;
|
||
|
|
||
|
for ($i = 0; $i < 81; $i++)
|
||
|
{
|
||
|
if ($theString{$i} != " ")
|
||
|
{
|
||
|
$theArray[] = array((int)($i/9) + 1, ($i % 9) + 1, (int)$theString{$i}) ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$this->initializePuzzleFromArray($theArray) ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @desc predicate to determine if the current puzzle has been solved.
|
||
|
* @access public
|
||
|
* @return boolean true if the puzzle has been solved.
|
||
|
*/
|
||
|
|
||
|
function isSolved()
|
||
|
{
|
||
|
for ($i = 1; $i <= 9; $i++)
|
||
|
{
|
||
|
for ($j = 1; $j <=9; $j++)
|
||
|
{
|
||
|
if (!$this->theBoard[$i][$j]->IsSolved())
|
||
|
{
|
||
|
return false ;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Convert pending to actual solutions.
|
||
|
*
|
||
|
* This step is actually unnecessary unless you want a pretty output of the
|
||
|
* intermediate.
|
||
|
*
|
||
|
* @access private
|
||
|
* @return boolean True if at least on pending solution existed, false otherwise.
|
||
|
*/
|
||
|
|
||
|
function _newSolvedPosition()
|
||
|
{
|
||
|
$theReturn = false ;
|
||
|
|
||
|
for ($i = 1; $i <= 9; $i++)
|
||
|
{
|
||
|
for ($j = 1; $j <= 9; $j++)
|
||
|
{
|
||
|
if ($this->theBoard[$i][$j]->solvedState() == 1)
|
||
|
{
|
||
|
$this->theBoard[$i][$j]->un_set($this->theBoard[$i][$j]->getState()) ;
|
||
|
$theReturn = true ;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $theReturn ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Print the contents of the board in HTML format.
|
||
|
*
|
||
|
* A "hook" so that extension classes can show all the steps taken by
|
||
|
* the solve function.
|
||
|
*
|
||
|
* @see SudokuIntermediateSolution.
|
||
|
*
|
||
|
* @access private
|
||
|
* @param string $theHeader [optional] The header line to be output along
|
||
|
* with the intermediate solution.
|
||
|
*/
|
||
|
|
||
|
function _printIntermediateSolution($theHeader = NULL)
|
||
|
{
|
||
|
if ($this->theDebug)
|
||
|
$this->printSolution($theHeader) ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Print the contents of the board in HTML format.
|
||
|
*
|
||
|
* Simple output, is tailored by hand so that an initial state and
|
||
|
* a solution will find nicely upon a single 8.5 x 11 page of paper.
|
||
|
*
|
||
|
* @access public
|
||
|
* @param mixed $theHeader [optional] The header line[s] to be output along
|
||
|
* with the solution.
|
||
|
*/
|
||
|
|
||
|
function printSolution($theHeader = NULL)
|
||
|
{
|
||
|
if (($this->theDebug) && ($theHeader != NULL))
|
||
|
{
|
||
|
if (is_array($theHeader))
|
||
|
{
|
||
|
foreach ($theHeader as $aHeader)
|
||
|
print $aHeader ;
|
||
|
}
|
||
|
else
|
||
|
print $theHeader ;
|
||
|
}
|
||
|
|
||
|
$theColors = array("green", "blue", "red") ;
|
||
|
$theFontSize = array("1em", "1em", ".8em") ;
|
||
|
$theFontWeight = array("bold", "bold", "lighter") ;
|
||
|
|
||
|
printf("<br /><table border=\"1\" style=\"border-collapse: separate; border-spacing: 0px;\">\n") ;
|
||
|
|
||
|
$theLast = 2 ;
|
||
|
|
||
|
for ($i = 1; $i <= 9; $i++)
|
||
|
{
|
||
|
if ($theLast == 2)
|
||
|
printf("<tr>\n") ;
|
||
|
|
||
|
printf("<td><table border=\"1\" width=\"100%%\">\n") ;
|
||
|
|
||
|
$theLast1 = 2 ;
|
||
|
|
||
|
for ($j = 1; $j <=9; $j++)
|
||
|
{
|
||
|
if ($theLast1 == 2)
|
||
|
printf("<tr>\n") ;
|
||
|
|
||
|
$c = &$this->theSquares[$i] ;
|
||
|
$c =& $c->getCell($j) ; ;
|
||
|
$theSolvedState = $c->solvedState() ;
|
||
|
|
||
|
printf("<td style=\"text-align: center; padding: .6em; color: %s; font-weight: %s; font-size: %s;\">",
|
||
|
$theColors[$theSolvedState],
|
||
|
$theFontWeight[$theSolvedState],
|
||
|
$theFontSize[$theSolvedState]) ;
|
||
|
$xxx = $c->asString($this->theDebug) ;
|
||
|
print ($xxx == " " ? " " : $xxx) ;
|
||
|
printf("</td>\n") ;
|
||
|
|
||
|
$theLast1 = ($j - 1) % 3 ;
|
||
|
if ($theLast1 == 2)
|
||
|
printf("</tr>\n") ;
|
||
|
}
|
||
|
|
||
|
printf("</table></td>\n") ;
|
||
|
|
||
|
$theLast = ($i - 1) % 3 ;
|
||
|
if ($theLast == 2)
|
||
|
printf("</tr>\n") ;
|
||
|
}
|
||
|
|
||
|
printf("</table>\n") ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Solve a Sudoku.
|
||
|
*
|
||
|
* As explained earlier, this works by iterating upon three different
|
||
|
* types of inference:
|
||
|
*
|
||
|
* 1. A negative one, in which a value used within a row/column/square
|
||
|
* may not appear elsewhere within the enclosing row/column/square.
|
||
|
* 2. A positive one, in which any value with is unique in a row
|
||
|
* or column or square must be the solution to that position.
|
||
|
* 3. A tuple based positive one which comes in a number of flavors:
|
||
|
* 3a. The "Pair" rule as stated by the author of the "other" Sudoku
|
||
|
* class on phpclasses.org and generalized by me, e.g., in any RCS
|
||
|
* two cells containing a pair of values eliminate those values from
|
||
|
* consideration in the rest of the RC or S.
|
||
|
* 3b. The n/n+1 set rule as discovered by me, e.g., in any RCS, three cells
|
||
|
* containing the following pattern, (i, j)/(j, k)/(i, j, k) eliminate
|
||
|
* the values i, j, k from consideration in the rest of the RC or S.
|
||
|
*
|
||
|
* During processing I explain which structures (row, column, square)
|
||
|
* are being used to infer solutions.
|
||
|
*
|
||
|
* @access public
|
||
|
* @param boolean $theInitialStateFlag [optional] True if the initial
|
||
|
* state of the board is to be printed upon entry, false
|
||
|
* otherwise. [Default = true]
|
||
|
* @return boolean true if a solution was possible, false otherwise.
|
||
|
*/
|
||
|
|
||
|
function solve($theInitialStateFlag = true)
|
||
|
{
|
||
|
$theHeader = "<br />Initial Position:" ;
|
||
|
|
||
|
do
|
||
|
{
|
||
|
do
|
||
|
{
|
||
|
$this->_applySolvedPositions() ;
|
||
|
if ($theInitialStateFlag)
|
||
|
{
|
||
|
$this->_printIntermediateSolution($theHeader) ;
|
||
|
$theHeader = NULL ;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$theInitialStateFlag = true ;
|
||
|
$theHeader = "<br />Apply Slice and Dice:" ;
|
||
|
}
|
||
|
} while ($this->_newSolvedPosition()) ;
|
||
|
|
||
|
$theRowIteration = FALSE ;
|
||
|
|
||
|
for ($i = 1; $i <= 9; $i++)
|
||
|
{
|
||
|
if ($this->theRows[$i]->doAnInference())
|
||
|
{
|
||
|
$theHeader = $this->theRows[$i]->getHeader() ;
|
||
|
$theRowIteration = TRUE ;
|
||
|
break ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$theColumnIteration = FALSE ;
|
||
|
|
||
|
if (!$theRowIteration)
|
||
|
{
|
||
|
for ($i = 1; $i <= 9; $i++)
|
||
|
{
|
||
|
if ($this->theColumns[$i]->doAnInference())
|
||
|
{
|
||
|
$theHeader = $this->theColumns[$i]->getHeader() ;
|
||
|
$theColumnIteration = TRUE ;
|
||
|
break ;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$theSquareIteration = FALSE ;
|
||
|
|
||
|
if (!($theRowIteration || $theColumnIteration))
|
||
|
{
|
||
|
for ($i = 1; $i <= 9; $i++)
|
||
|
{
|
||
|
if ($this->theSquares[$i]->doAnInference())
|
||
|
{
|
||
|
$theHeader = $this->theSquares[$i]->getHeader() ;
|
||
|
$theSquareIteration = TRUE ;
|
||
|
break ;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
} while ($theRowIteration || $theColumnIteration || $theSquareIteration) ;
|
||
|
|
||
|
return $this->IsSolved() ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Here there be dragons. In conversations with other Sudoku folks, I find that there ARE Sudoku with
|
||
|
* unique solutions for which a clue set may be incomplete, i.e., does not lead to a solution. The
|
||
|
* solution may only be found by guessing the next move. I'm of the opinion that this violates the
|
||
|
* definition of Sudoku (in which it's frequently said "never guess") but if it's possible to find
|
||
|
* a solution, this will do it.
|
||
|
*
|
||
|
* The problem is that it can take a LONG time if there ISN'T a solution since this is basically a
|
||
|
* backtracing solution trier.
|
||
|
*
|
||
|
* The basic algorithm is pretty simple:
|
||
|
*
|
||
|
* 1. Find the first unsolved cell.
|
||
|
* 2. For every possible value, substutite value for the cell, apply inferences.
|
||
|
* 3. If a solution was found, we're done.
|
||
|
* 4. Recurse looking for the next cell to try a value for.
|
||
|
*
|
||
|
* There's a bit of bookkeeping to keep the state right when backing up, but that's pretty
|
||
|
* straightforward and looks a lot like that of generatePuzzle.
|
||
|
*
|
||
|
* @desc Brute force additional solutions.
|
||
|
* @access public
|
||
|
* @returns array The clues added sufficient to solve the puzzle.
|
||
|
*/
|
||
|
|
||
|
function solveBruteForce($i = 1, $j = 1)
|
||
|
{
|
||
|
for (; $i <= 9; $i++)
|
||
|
{
|
||
|
for (; $j <= 9; $j++)
|
||
|
{
|
||
|
if ($this->theBoard[$i][$j]->solvedState() != 0)
|
||
|
{
|
||
|
if ($this->theDebug)
|
||
|
{
|
||
|
printf("<br />Applying Brute Force to %d, %d\n", $i, $j) ;
|
||
|
}
|
||
|
|
||
|
$theCurrentBoard = $this->deepCopy($this->theBoard) ;
|
||
|
$theValues = $this->theBoard[$i][$j]->getState() ;
|
||
|
|
||
|
foreach ($theValues as $theValue)
|
||
|
{
|
||
|
$this->theBoard[$i][$j]->flagSolvedPosition($theValue) ;
|
||
|
|
||
|
$theSolutionFlag = $this->solve() ;
|
||
|
$theTrialSolutionFlag = $this->_validateTrialSolution() ;
|
||
|
|
||
|
if ($theTrialSolutionFlag && $theSolutionFlag)
|
||
|
{
|
||
|
return array(array($i, $j, $theValue)) ;
|
||
|
}
|
||
|
|
||
|
if ($theTrialSolutionFlag)
|
||
|
{
|
||
|
$theNewGuesses = $this->solveBruteForce($i, $j+1) ;
|
||
|
|
||
|
if ($theNewGuesses)
|
||
|
{
|
||
|
$theNewGuesses[] = array($i, $j, $theValue) ;
|
||
|
|
||
|
return $theNewGuesses ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ($this->theDebug)
|
||
|
{
|
||
|
printf("<br />Backing out\n") ;
|
||
|
}
|
||
|
|
||
|
$this->theBoard = $theCurrentBoard ;
|
||
|
$this->_buildRCS() ;
|
||
|
}
|
||
|
|
||
|
return array() ;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @desc Calculate the index of the square containing a specific cell.
|
||
|
* @param integer $theRow the row coordinate.
|
||
|
* @param integer $theColumn the column coordinate.
|
||
|
* @return integer the square index in the range 1..9
|
||
|
*/
|
||
|
|
||
|
function _squareIndex($theRow, $theColumn)
|
||
|
{
|
||
|
$theIndex = ((int)(($theRow - 1) / 3) * 3) + (int)(($theColumn - 1) / 3) + 1 ;
|
||
|
return $theIndex ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Validate a complete solution.
|
||
|
*
|
||
|
* After a complete solution has been generated check the board and
|
||
|
* report any inconsistencies. This is primarily intended for debugging
|
||
|
* purposes.
|
||
|
*
|
||
|
* @access public
|
||
|
* @return mixed true if the solution is valid, an array containing the
|
||
|
* error details.
|
||
|
*/
|
||
|
|
||
|
function validateSolution()
|
||
|
{
|
||
|
$theReturn = array() ;
|
||
|
|
||
|
for ($i = 1; $i <= 9; $i++)
|
||
|
{
|
||
|
if (!$this->theRows[$i]->validateSolution())
|
||
|
{
|
||
|
$theReturn[0][] = $i ;
|
||
|
}
|
||
|
if (!$this->theColumns[$i]->validateSolution())
|
||
|
{
|
||
|
$theReturn[1][] = $i ;
|
||
|
}
|
||
|
if (!$this->theSquares[$i]->validateSolution())
|
||
|
{
|
||
|
$theReturn[2][] = $i ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return (count($theReturn) == 0 ? TRUE : $theReturn) ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Validate an entire trial solution.
|
||
|
*
|
||
|
* Used during puzzle generation to determine when to backtrace.
|
||
|
*
|
||
|
* @access private
|
||
|
* @return True when the intermediate soltuion is valid, false otherwise.
|
||
|
*/
|
||
|
|
||
|
function _validateTrialSolution()
|
||
|
{
|
||
|
|
||
|
for ($i = 1; $i <= 9; $i++)
|
||
|
{
|
||
|
if (!(($this->theRows[$i]->validateTrialSolution()) &&
|
||
|
($this->theColumns[$i]->validateTrialSolution()) &&
|
||
|
($this->theSquares[$i]->validateTrialSolution())))
|
||
|
{
|
||
|
return FALSE ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return TRUE ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Extend Sudoku to generate puzzles based on templates.
|
||
|
*
|
||
|
* Templates are either input files or arrays containing doubles.
|
||
|
*
|
||
|
* @package Sudoku
|
||
|
*/
|
||
|
|
||
|
class SudokuTemplates extends Sudoku
|
||
|
{
|
||
|
function SudokuTemplates($theDebug = FALSE)
|
||
|
{
|
||
|
$this->Sudoku($theDebug) ;
|
||
|
}
|
||
|
|
||
|
function generatePuzzleFromFile($theHandle = STDIN, $theDifficultyLevel = 10)
|
||
|
{
|
||
|
$yyy = array() ;
|
||
|
|
||
|
if ($theHandle)
|
||
|
{
|
||
|
while (!feof($theHandle))
|
||
|
{
|
||
|
$theString = trim(fgets($theHandle)) ;
|
||
|
$xxx = preg_split("/\s+/", $theString) ;
|
||
|
if (!feof($theHandle))
|
||
|
{
|
||
|
$yyy[] = array((int)$xxx[0], (int)$xxx[1]) ;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $this->generatePuzzleFromArray($yyy, $theDifficultyLevel) ;
|
||
|
}
|
||
|
|
||
|
function generatePuzzleFromArray($theArray, $theDifficultyLevel = 10)
|
||
|
{
|
||
|
$this->_generatePuzzle($theArray, array(), array()) ;
|
||
|
|
||
|
/*
|
||
|
** Because the generation process may infer values for some of the
|
||
|
** template cells, we construct the clues from the board and the
|
||
|
** input array before continuing to generate the puzzle.
|
||
|
*/
|
||
|
|
||
|
foreach ($theArray as $theKey => $thePosition)
|
||
|
{
|
||
|
$theTemplateClues[] = array($thePosition[0], $thePosition[1], $this->theBoard[$thePosition[0]][$thePosition[1]]) ;
|
||
|
}
|
||
|
|
||
|
$theOtherClues = $this->generatePuzzle($theDifficultyLevel) ;
|
||
|
|
||
|
return array_merge($theTemplateClues, $theOtherClues) ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Extend Sudoku to print all intermediate results.
|
||
|
*
|
||
|
* @package Sudoku
|
||
|
*/
|
||
|
|
||
|
class SudokuIntermediateSolution extends Sudoku
|
||
|
{
|
||
|
function SudokuIntermediateResults($theDebug = FALSE)
|
||
|
{
|
||
|
$this->Sudoku($theDebug) ;
|
||
|
}
|
||
|
|
||
|
function _printIntermediateSolution($theHeader = NULL)
|
||
|
{
|
||
|
$this->printSolution($theHeader) ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function make_seed()
|
||
|
{
|
||
|
list($usec, $sec) = explode(' ', microtime());
|
||
|
return (float) $sec + ((float) $usec * 100000);
|
||
|
}
|
||
|
|
||
|
?>
|