Moved Cell to its own file.
[pseudoku.git] / pseudoku / grid / __init__.py
index e91a1bd..9ba0158 100644 (file)
@@ -1,10 +1,11 @@
 from __future__ import division
 
 from math import sqrt
+from operator import attrgetter
 import re
-from weakref import proxy
 
-from cellgroup import Row, Column, Box
+from cell import Cell
+from constraints import Constraint, Row, Column, Box
 
 symbols = [str(x + 1) for x in range(9)] + [chr(x + 97) for x in xrange(26)]
 
@@ -18,86 +19,6 @@ class GridIntegrityError(Exception):
     """
     pass
 
-class Cell(object):
-    """Represents a single cell/value within a sudoku grid."""
-
-    ### Accessors
-
-    def _get_solved(self):
-        """True iff this cell has been solved."""
-        return len(self._values) == 1
-    solved = property(_get_solved)
-
-    def _get_value(self):
-        """Returns this cell's value, if it has one known."""
-        if self.solved:
-            return self._values[0]
-        return None
-    value = property(_get_value)
-
-    def _get_constraints(self):
-        return self._constraints
-    constraints = property(_get_constraints)
-
-    def __init__(self, grid, row, column):
-        self._grid = proxy(grid)
-        self._row = row
-        self._col = column
-        self._values = range(self._grid.size)
-        self._constraints = []
-        self._normalized = False
-
-    def add_constraint(self, constraint):
-        self._constraints.append(constraint)
-
-    def set(self, value, normalize=True):
-        """Sets the value of this cell.  If `normalize` is True or omitted, the
-        grid will be updated accordingly.
-        """
-        self._values = [value]
-        if normalize:
-            self._normalized = False
-            self.normalize()
-
-
-
-    def normalize(self):
-        """Checks to see if this cell has only one possible value left.  If
-        so, sets that as its value and eliminates it from every related cell.
-        This method is exhaustive; that repeated calls should have no effect.
-        """
-
-        if self._normalized:
-            # Already done
-            return
-
-        # Set this now just in case of infinite looping
-        self._normalized = True
-
-        if not self.solved:
-            # Don't know the value yet
-            return
-
-        # Elimination time
-        for constraint in self.constraints:
-            for cell in constraint.cells:
-                if cell == self:
-                    continue
-                cell.eliminate(self.value)
-
-
-    def eliminate(self, value):
-        """Eliminates the given value as a possibility for this cell."""
-        if value in self._values:
-            self._values.remove(value)
-
-            if len(self._values) == 0:
-                # XXX give me a real exception here
-                raise Exception
-
-            self._normalized = False
-            self.normalize()
-
 
 class Grid(object):
     """Represents a Sudoku grid."""
@@ -106,7 +27,7 @@ class Grid(object):
 
     def _cellidx(self, row, col):
         """Hashes a row and column into a flat array index."""
-        return row * self._size + col
+        return row * self.size + col
 
     @classmethod
     def _infer_box_size(cls, dimension):
@@ -144,21 +65,22 @@ class Grid(object):
 
     ### Accessors
 
-    def _get_box_height(self):
-        return self._box_height
-    box_height = property(_get_box_height)
+    def get_constraints(self, constraint_class=Constraint):
+        """Returns constraints of a certain type.  Returns all of them by
+        default.
+        """
 
-    def _get_box_width(self):
-        return self._box_width
-    box_width = property(_get_box_width)
+        condition = lambda constraint: isinstance(constraint, constraint_class)
+        return filter(condition, self._constraints)
 
-    def _get_size(self):
-        return self._size
-    size = property(_get_size)
+    rows = property(lambda self: self.get_constraints(Row))
+    columns = property(lambda self: self.get_constraints(Column))
+    boxes = property(lambda self: self.get_constraints(Box))
 
-    def _get_constraints(self):
-        return self._constraints
-    constraints = property(_get_constraints)
+    box_height = property(attrgetter('_box_height'))
+    box_width = property(attrgetter('_box_width'))
+    size = property(attrgetter('_size'))
+    constraints = property(attrgetter('_constraints'))
 
     ### Constructors
 
@@ -170,9 +92,9 @@ class Grid(object):
         self._box_width = box_width
         self._size = box_height * box_width
 
-        self._cells = range(self._size ** 2)
-        for row in xrange(self._size):
-            for col in xrange(self._size):
+        self._cells = range(self.size ** 2)
+        for row in xrange(self.size):
+            for col in xrange(self.size):
                 self._cells[self._cellidx(row, col)] \
                     = Cell(self, row, col)
 
@@ -190,8 +112,8 @@ class Grid(object):
 
         self = cls(box_width=box_width, box_height=box_height)
 
-        for row in xrange(self._size):
-            for col in xrange(self._size):
+        for row in xrange(self.size):
+            for col in xrange(self.size):
                 value = rows[row][col]
                 if not value:
                     continue
@@ -229,8 +151,8 @@ class Grid(object):
 
         self = cls(box_width=box_width, box_height=box_height)
 
-        for row in xrange(self._size):
-            for col in xrange(self._size):
+        for row in xrange(self.size):
+            for col in xrange(self.size):
                 ch = grid[ self._cellidx(row, col) ]
                 if ch == '0':
                     continue
@@ -246,7 +168,7 @@ class Grid(object):
             cell.add_constraint(constraint)
 
     def add_default_constraints(self):
-        for i in xrange(self._size):
+        for i in xrange(self.size):
             self.add_constraint(Row(self, i))
             self.add_constraint(Column(self, i))
             self.add_constraint(Box(self, i))