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.
		
		
		
		
			
				
					
					
						
							1898 lines
						
					
					
						
							63 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							1898 lines
						
					
					
						
							63 KiB
						
					
					
				| <?php | |
| // This file is part of Moodle - http://moodle.org/ | |
| // | |
| // Moodle is free software: you can redistribute it and/or modify | |
| // it under the terms of the GNU General Public License as published by | |
| // the Free Software Foundation, either version 3 of the License, or | |
| // (at your option) any later version. | |
| // | |
| // Moodle is distributed in the hope that it will be useful, | |
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | |
| // GNU General Public License for more details. | |
| // | |
| // You should have received a copy of the GNU General Public License | |
| // along with Moodle.  If not, see <http://www.gnu.org/licenses/>. | |
|  | |
| /* | |
|  * 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. | |
|  */ | |
| 
 | |
| /** | |
|  * This class is used by Sudoku game. | |
|  * | |
|  * @package    mod_game | |
|  * @copyright  2007 Vasilis Daloukas | |
|  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
|  */ | |
| 
 | |
| defined('MOODLE_INTERNAL') || die(); | |
| 
 | |
| @require_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 | |
|  */ | |
| 
 | |
| /** | |
|  * Basic functionality needed for ObjectSs in the Sudoku solver. | |
|  * | |
|  * @package    mod_game | |
|  * @copyright  2007 Vasilis Daloukas | |
|  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
|  */ | |
| class objects { | |
|     /** | |
|      * Are two array's equal (have the same contents). | |
|      * | |
|      * @param array $thearray1 | |
|      * @param array $thearray2 | |
|      * @return boolean | |
|      */ | |
|     public 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. | |
|      * | |
|      * @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. | |
|      */ | |
|     public function deepcopy($thearray = null) { | |
|         if ($thearray === null) { | |
|             return unserialize(serialize($this)); | |
|         } else { | |
|             return unserialize(serialize($thearray)); | |
|         } | |
|     } | |
| } | |
| 
 | |
| /** | |
|  * 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    mod_game | |
|  * @copyright  2007 Vasilis Daloukas | |
|  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
|  */ | |
| class cell extends objects { | |
|     /** @var $r */ | |
|     protected $r; | |
|     /** @var $c */ | |
|     protected $c; | |
| 
 | |
|     /** @var $state */ | |
|     protected $state = array(); | |
|     /** @var $applied */ | |
|     protected $applied = false; | |
| 
 | |
|     /** | |
|      * Constructor | |
|      * | |
|      * @param integer $inpr row address of this cell (not used, primarily for debugging purposes). | |
|      * @param integer $inpc column address of this cell (ditto). | |
|      * @param integer $nstates The number of states each cell can have.  Looking forward to | |
|      *                         implementing Super-doku. | |
|      */ | |
|     public function init($inpr, $inpc, $nstates = 9) { | |
|         $this->r = $inpr; | |
|         $this->c = $inpc; | |
| 
 | |
|         for ($i = 1; $i <= $nstates; $i++) { | |
|             $this->state[$i] = $i; | |
|         } | |
|     } | |
| 
 | |
|     /** | |
|      * This cell has been "applied", i.e., solved, to the board. | |
|      */ | |
|     public function applied() { | |
|         $this->applied = true; | |
|     } | |
| 
 | |
|     /** | |
|      * Only those cells which are not subsets of the tuple have the | |
|      * contents of the tuple removed. | |
|      * | |
|      * apply a 23Tuple to a cell. | |
|      * @param array $atuple the tuple to be eliminated. | |
|      */ | |
|     public 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. | |
|      * | |
|      * Remove all values in the tuple, but only if the cell is a superset. | |
|      * @param array $atuple to be eliminated from the cell's state. | |
|      */ | |
|     public function applytuple($atuple) { | |
|         if (is_array($this->state)) { | |
|             if (!$this->array_equal($atuple, $this->state)) { | |
|                 return $this->un_set($atuple); | |
|             } | |
|         } | |
| 
 | |
|         return false; | |
|     } | |
| 
 | |
|     /** | |
|      * Return the string representation of the cell. | |
|      * | |
|      * @param boolean $theflag true if the intermediate states of the cell are to be visible. | |
|      * | |
|      * @return string | |
|      */ | |
|     public 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; | |
|         } | |
|     } | |
| 
 | |
|     /** | |
|      * Assert pending solution. | |
|      * 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. | |
|      * | |
|      * @param integer $value The value for the solved position. | |
|      */ | |
|     public function flagsolvedposition($value) { | |
|         $this->state = array($value => $value); | |
|     } | |
| 
 | |
|     /** | |
|      * return the state of a cell. | |
|      * | |
|      * @return mixed Either solved state or array of state pending solution. | |
|      */ | |
|     public function &getstate() { | |
|         return $this->state; | |
|     } | |
| 
 | |
|     /** | |
|      * Has the state of this cell been applied to the board. | |
|      * | |
|      * @return boolean True if it has, false otherwise.  Implies that IsSolved is true as well. | |
|      */ | |
|     public function isapplied() { | |
|         return $this->applied; | |
|     } | |
| 
 | |
|      /** | |
|       * Has this cell been solved? | |
|       * | |
|       * @return boolean True if this cell has hit a single state. | |
|       */ | |
|     public function issolved() { | |
|         return !is_array($this->state); | |
|     } | |
| 
 | |
|     /** | |
|      * This is used primarily by the pretty printer, but has other applications in the code. | |
|      * | |
|      * Return information about the state of a cell. | |
|      * | |
|      * @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. | |
|      */ | |
|     public function solvedstate() { | |
|         if (is_array($this->state)) { | |
|             if (count($this->state) == 1) { | |
|                 return 1; | |
|             } else { | |
|                 return 2; | |
|             } | |
|         } else { | |
|             return 0; | |
|         } | |
|     } | |
| 
 | |
|     /** | |
|      * Eliminate one or more values from the state information of the cell. | |
|      * | |
|      * 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. | |
|      * | |
|      * @param mixed $thevalues or values to be removed from the cell state. | |
|      * | |
|      * @return boolean True if the cell state was modified, false otherwise. | |
|      */ | |
|     public 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 | |
|  * @copyright  2007 Vasilis Daloukas | |
|  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
|  */ | |
| class rcs extends ObjectS { | |
|     /** @var theindex */ | |
|     protected $theindex; | |
| 
 | |
|     /** @var therow */ | |
|     protected $therow = array(); | |
| 
 | |
|     /** @var theheader */ | |
|     protected $theheader = ""; | |
| 
 | |
|     /** @var thetag */ | |
|     protected $thetag = ""; | |
| 
 | |
|     /** | |
|      * Constructor | |
|      * | |
|      *  This interface is what limts things to 9x9 Sudoku currently | |
|      * @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 of class Cell. | |
|      * @param ObjectS $a2 of class Cell. | |
|      * @param ObjectS $a3 of class Cell. | |
|      * @param ObjectS $a4 of class Cell. | |
|      * @param ObjectS $a5 of class Cell. | |
|      * @param ObjectS $a6 of class Cell. | |
|      * @param ObjectS $a7 of class Cell. | |
|      * @param ObjectS $a8 of class Cell. | |
|      * @param ObjectS $a9 of class Cell. | |
|      */ | |
|     public function init($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. | |
|      * | |
|      * @return boolean True if a 23 solution exists and has been applied. | |
|      */ | |
|     public 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; | |
|     } | |
| 
 | |
|     /** | |
|      * apply a tuple to exclude items from within the row/column/square. | |
|      * | |
|      * @param array $atuple the tuple to be excluded. | |
|      * | |
|      * @return boolean true if anything changes. | |
|      */ | |
|     protected function _applytuple(&$atuple) { | |
|         $thereturn = false; | |
| 
 | |
|         for ($i = 1; $i <= 9; $i++) { | |
|             $thereturn |= $this->therow[$i]->applytuple( $atuple); | |
|         } | |
| 
 | |
|         return $thereturn; | |
|     } | |
| 
 | |
|     /** | |
|      * Calculate the coupling for a cell within the row/column/square. | |
|      * | |
|      * 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. | |
|      * | |
|      * @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. | |
|      */ | |
|     public function coupling($therow, $thecolumn) { | |
|         return 0; | |
|     } | |
| 
 | |
|     /** | |
|      * Run the inference engine for a row/column/square. | |
|      * | |
|      * 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. | |
|      * | |
|      * @return boolean True when at least one inference has succeeded. | |
|      */ | |
|     public function doaninference() { | |
|         $this->theheader = null; | |
| 
 | |
|         $thereturn = $this->_23solution(); | |
|         $thereturn |= $this->_pairsolution(); | |
|         $thereturn |= $this->_uniquesolution(); | |
| 
 | |
|         return $thereturn; | |
|     } | |
| 
 | |
|     /** | |
|      * Find all tuples with the same contents. | |
|      * | |
|      * @param array $thearray of n size tuples. | |
|      * | |
|      * @return array of tuples that appear the same number of times as the size of the contents | |
|      */ | |
|     public 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; | |
|     } | |
| 
 | |
|     /** | |
|      * Get a reference to the specified cell. | |
|      * | |
|      * @param int $i | |
|      * | |
|      * @return reference to ObjectS of class Cell. | |
|      */ | |
|     public function &getcell($i) { | |
|         return $this->therow[$i]; | |
|     } | |
| 
 | |
|     /** | |
|      * Get the header set by the last call to doAnInference. | |
|      */ | |
|     public function getheader() { | |
|         return $this->theheader; | |
|     } | |
| 
 | |
|     /** | |
|      * Eliminate tuple-locked alternatives. | |
|      * | |
|      * 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. | |
|      * | |
|      * @return boolean True if something changed. | |
|      */ | |
|     protected 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->_applyruple($atuple); | |
|                 } | |
|             } | |
|         } | |
| 
 | |
|         if ($thereturn) { | |
|             $this->theheader[] = sprintf("<br />Apply %s[%d] Pair Inference:", $this->thetag, $this->theindex); | |
|         } | |
| 
 | |
|         return $thereturn; | |
|     } | |
| 
 | |
|     /** | |
|      * un set | |
|      * | |
|      * @param object $thevalues | |
|      * | |
|      * @return boolean True if one or more values in the RCS has changed state. | |
|      */ | |
|     public 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. | |
|      * | |
|      * @return boolean True if one or more values in the RCS has changed state. | |
|      */ | |
|     protected 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; | |
|     } | |
| 
 | |
|     /** | |
|      * Check to see if the RCS contains a valid state. | |
|      * | |
|      * @return boolean True if the state of the RCS could be part of a valid | |
|      *          solution, false otherwise. | |
|      */ | |
|     public 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. | |
|      * | |
|      * @return True if the input parameter contains a valid solution, false otherwise. | |
|      */ | |
|     public 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 | |
|  * @copyright  2007 Vasilis Daloukas | |
|  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
|  */ | |
| class r extends rcs { | |
|     /** | |
|      * Constructor | |
|      * | |
|      * @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. | |
|      */ | |
| 
 | |
|     /** | |
|      * see RCS::coupling | |
|      * | |
|      * @param int $therow | |
|      * @param int $thecolumn | |
|      */ | |
|     public function coupling($therow, $thecolumn) { | |
|         return $thestate = $this->_coupling($thecolumn); | |
|     } | |
| 
 | |
|     /** | |
|      * Heavy lifting for row/column coupling calculations. | |
|      * | |
|      * RCS::coupling | |
|      * @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. | |
|      */ | |
|     protected 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 | |
|  * @copyright  2007 Vasilis Daloukas | |
|  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
|  */ | |
| class c extends r { | |
|     /** | |
|      * Constructor | |
|      * | |
|      * @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. | |
|      */ | |
| 
 | |
|     /** | |
|      * see R::coupling | |
|      * | |
|      * @param int $therow | |
|      * @param int $thecolumn | |
|      */ | |
|     public function coupling($therow, $thecolumn) { | |
|         return $thestate = $this->_coupling($therow); | |
|     } | |
| } | |
| 
 | |
| /** | |
|  * The Square ObjectS. | |
|  * | |
|  * package Sudoku | |
|  * @copyright  2007 Vasilis Daloukas | |
|  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
|  */ | |
| 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. | |
|      * | |
|      * @var array | |
|      */ | |
|     protected $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)); | |
| 
 | |
|     /** | |
|      * Constructor | |
|      * | |
|      * @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 | |
|      *                                      limits things to 9x9 Sudoku currently. | |
|      */ | |
| 
 | |
|     /** | |
|      * see RCS::coupling | |
|      * | |
|      * @param int $therow | |
|      * @param int $thecolumn | |
|      */ | |
|     public 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 | |
|  * @copyright  2007 Vasilis Daloukas | |
|  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
|  */ | |
| class sudoku extends ObjectS { | |
|     /** @var array of ObjectSs of type Cell. */ | |
|     protected $theboard = array(); | |
| 
 | |
|     /** @var boolean True if debugging output is to be provided during a run. */ | |
|     protected $thedebug = false; | |
| 
 | |
|     /** @var ObjectS of type R An array of RCS ObjectSs, one ObjectS for each row. */ | |
|     protected $therows = array(); | |
| 
 | |
|     /** @var ObjectS of type C An array of RCS ObjectSs, one ObjectS for each Column. */ | |
|     private $thecolumns = array(); | |
| 
 | |
|     /** @var ObjectS of type S An array of RCS ObjectSs, one ObjectS for each square. */ | |
|     protected $thesquares = array(); | |
| 
 | |
|     /** @var integer. 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. | |
|      */ | |
|     protected $thelevel = 0; | |
| 
 | |
|     /** @var integer. 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. | |
|      */ | |
|     protected $themaxiterations = 50; | |
| 
 | |
|     /** @var integer. Used during puzzle generation to limit the number of trys at | |
|      * generation a puzzle in the event puzzle generation fails | |
|      */ | |
|     protected $thetrys = 10; | |
| 
 | |
|     /** @var integer. 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. | |
|      */ | |
|     protected $thegenerationiterations = 0; | |
| 
 | |
|     /** | |
|      * Constructor | |
|      * | |
|      * @param boolean $thedebug | |
|      */ | |
|     public function init($thedebug = false) { | |
|         $this->thedebug = $thedebug; | |
| 
 | |
|         for ($i = 1; $i <= 9; $i++) { | |
|             for ($j = 1; $j <= 9; $j++) { | |
|                 $this->theboard[$i][$j] = new cell; | |
|                 $this->theboard[$i][$j].init( $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. | |
|      * | |
|      * @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. | |
|      */ | |
|     protected 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); | |
|     } | |
| 
 | |
|     /** | |
|      * Apply all pending solved positions to the board. | |
|      * | |
|      * @return boolean True if at least one solved position was applied, false | |
|      *                 otherwise. | |
|      */ | |
|     protected 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; | |
|     } | |
| 
 | |
|     /** | |
|      * build the row/column/square structures for the board. | |
|      */ | |
|     protected 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. | |
|      * | |
|      * @param array $theinitialstate | |
|      * | |
|      * @return array A set of triples containing the minimum solution. | |
|      */ | |
|     public 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". | |
|      * | |
|      * @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 $themaxiterations [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 | |
|      */ | |
|     public 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); | |
|                     } | |
|                 } | |
| 
 | |
|                 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. | |
|          */ | |
| 
 | |
|         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. | |
|      * | |
|      * @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} | |
|      */ | |
|     protected 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(); | |
|     } | |
| 
 | |
|     /** | |
|      * Get the current state of the board as a string. | |
|      * | |
|      * 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. | |
|      */ | |
|     public function getboardasstring() { | |
|         $thestring = ""; | |
| 
 | |
|         for ($i = 1; $i <= 9; $i++) { | |
|             for ($j = 1; $j <= 9; $j++) { | |
|                 $thestring .= $this->theboard[$i][$j]->asstring(); | |
|             } | |
|         } | |
| 
 | |
|         return $thestring; | |
|     } | |
| 
 | |
|     /** | |
|      * Get cel | |
|      * | |
|      * @param int $r | |
|      * @param int $c | |
|      */ | |
|     public 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. | |
|      * | |
|      * @param array $thearray | |
|      */ | |
|     public 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. | |
|      * | |
|      * @param mixed $thehandle [optional] defaults to STDIN.  If a string is passed | |
|      *              instead of a file handle, the file is opened. | |
|      */ | |
|     public 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); | |
|         } | |
|     } | |
| 
 | |
|     /** | |
|      * Initialize puzzle from a string. | |
|      * | |
|      * The input parameter consists of a string of 81 digits and blanks.  If fewer characters | |
|      * are provide, the string is padded on the right. | |
|      * | |
|      * @param string $thestring The initial state of each cell in the puzzle. | |
|      */ | |
|     public 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->initializepuzzlefromrray($therray); | |
|     } | |
| 
 | |
|     /** | |
|      * predicate to determine if the current puzzle has been solved. | |
|      * | |
|      * @return boolean true if the puzzle has been solved. | |
|      */ | |
|     public 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. | |
|      * | |
|      * @return boolean True if at least on pending solution existed, false otherwise. | |
|      */ | |
|     protected 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. | |
|      * | |
|      * @param string $theheader [optional] The header line to be output along | |
|      *               with the intermediate solution. | |
|      */ | |
|     protected 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. | |
|      * | |
|      * @param mixed $theheader [optional] The header line[s] to be output along | |
|      *               with the solution. | |
|      */ | |
|     public 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. | |
|      * | |
|      * @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. | |
|      */ | |
|     public 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]->doninference()) { | |
|                     $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(); | |
|     } | |
| 
 | |
|     /** | |
|      * Brute force additional solutions. | |
|      * | |
|      * 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. | |
|      * | |
|      * @param int $i | |
|      * @param int $j | |
|      * | |
|      * @return array The clues added sufficient to solve the puzzle. | |
|      */ | |
|     public 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(); | |
|                 } | |
|             } | |
|         } | |
|     } | |
| 
 | |
|     /** | |
|      * 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 | |
|      */ | |
|     protected 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. | |
|      * | |
|      * @return mixed true if the solution is valid, an array containing the | |
|      *               error details. | |
|      */ | |
|     public 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. | |
|      * | |
|      * @return True when the intermediate soltuion is valid, false otherwise. | |
|      */ | |
|     protected 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. | |
|  * @copyright  2007 Vasilis Daloukas | |
|  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
|  */ | |
| class SudokuTemplates extends Sudoku | |
| { | |
|     /** | |
|      * Generate puzzle from file | |
|      * | |
|      * @param int $thehandle | |
|      * @param int $thedifficultylevel | |
|      */ | |
|     public 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); | |
|     } | |
| 
 | |
|     /** | |
|      * Generate puzzle from file | |
|      * | |
|      * @param int $thearray | |
|      * @param int $thedifficultylevel | |
|      */ | |
|     public 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. | |
|  * | |
|  * @copyright  2007 Vasilis Daloukas | |
|  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
|  */ | |
| class sudokuintermediatesolution extends sudoku { | |
|     /** | |
|      * intermediate results | |
|      * | |
|      * @param int $thedebug | |
|      */ | |
|     public function sudokuintermediateresults($thedebug = false) { | |
|         $this->sudoku($thedebug); | |
|     } | |
|     /** | |
|      * print intermediate solution | |
|      * | |
|      * @param object $theheader | |
|      */ | |
|     protected function _printintermediatesolution($theheader = null) { | |
|         $this->printsolution($theheader); | |
|     } | |
| } | |
| 
 | |
| /** | |
|  * make seed | |
|  */ | |
| function make_seed() { | |
|     list($usec, $sec) = explode(' ', microtime()); | |
|     return (float) $sec + ((float) $usec * 100000); | |
| }
 | |
| 
 |