[gnome-games/sudoku-tube] Towards split view and model



commit e9bdc9c9cf3d842615b35f58e396c962aa70efa8
Author: Zhang Sen <zh jesse gmail com>
Date:   Fri Jul 10 21:41:47 2009 +0800

    Towards split view and model
    
    * SudokuGameDisplay is splitted into SudokuView and SudokuModel.
    * Much work is still needed, but the rough shape should be seen. Now the game
     can be launched.

 gnome-sudoku/src/lib/gsudoku.py |  302 +++++++++++++++++++++------------------
 gnome-sudoku/src/lib/main.py    |   26 ++--
 2 files changed, 179 insertions(+), 149 deletions(-)
---
diff --git a/gnome-sudoku/src/lib/gsudoku.py b/gnome-sudoku/src/lib/gsudoku.py
index 3ccec58..4af245a 100644
--- a/gnome-sudoku/src/lib/gsudoku.py
+++ b/gnome-sudoku/src/lib/gsudoku.py
@@ -157,28 +157,16 @@ class ParallelDict (dict):
                     # for i, then we delete i from our dictionary
                     dict.__delitem__(self, i)
 
-class SudokuGameDisplay (SudokuNumberGrid, gobject.GObject):
 
-    __gsignals__ = {
-        'puzzle-finished':(gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())
-        }
+class SudokuView(SudokuNumberGrid, gobject.GObject):
 
-    do_highlight_cells = False
-
-    def __init__ (self, grid = None, group_size = 9,
-                  show_impossible_implications = False):
-        group_size = int(group_size)
-        self.hints = 0
-        self.always_show_hints = False
-        self.auto_fills = 0
-        self.show_impossible_implications = show_impossible_implications
-        self.impossible_hints = 0
-        self.impossibilities = []
-        self.trackers = {}
-        self.__trackers_tracking__ = {}
+    def __init__(self, group_size):
+        SudokuNumberGrid.__init__(self, group_size)
         gobject.GObject.__init__(self)
-        SudokuNumberGrid.__init__(self, group_size = group_size)
-        self._setup_grid(grid, group_size)
+
+        self._model = None
+        self._do_highlight_cells = False
+
         for e in self.__entries__.values():
             e.show()
             e.connect('number-changed', self._number_changed_cb)
@@ -186,8 +174,48 @@ class SudokuGameDisplay (SudokuNumberGrid, gobject.GObject):
             e.connect('focus-in-event', self._focus_callback)
             e.connect('key-press-event', self._key_press_cb)
 
-    def _notes_changed_cb(self, box, top_note, bottom_note):
-        box.set_notes((top_note, bottom_note))
+    def connect_to_model(self, model):
+        if not model:
+            return
+        self._model = model
+        model.add_observer(self)
+        for x in range(self.group_size):
+            for y in range(self.group_size):
+                val = model.grid._get_(x, y)
+                if val:
+                    self.set_readonly_appearance(x, y, True)
+                    self.set_value(x, y, val)
+
+    def update(self, values):
+        """Update the GUI, called by a Model
+
+        values should be a list of NumberBoxModel"""
+        for box in values:
+            x, y = box.x, box.y,
+            if box.value is not None:
+                self.set_value(x, y, box.value)
+            if box.top_note is not None or box.bottom_note is not None:
+                self._set_notes(x, y, box.top_note, box.bottom_note)
+            if box.conflict is not None:
+                self._show_conflict(x, y, box.conflict)
+
+    def _show_conflict(self, x, y, conflict):
+        self.__entries__[(x, y)].set_error_highlight(conflict)
+
+    def _set_notes(self, x, y, top_note, bottom_note):
+        self.__entries__[(x, y)].set_notes((top_note, bottom_note))
+
+    def _number_changed_cb(self, widget, new_number):
+        print "user input: number", (widget.x, widget.y, new_number)
+        self._model.set_value(widget.x, widget.y, new_number)
+
+    def _notes_changed_cb(self, widget, top_note, bottom_note):
+        print "user input: notes", (widget.x, widget.y, top_note, bottom_note)
+        self._model.set_notes(widget.x, widget.y, top_note, bottom_note)
+
+    def _focus_callback(self, widget, event):
+        if self._do_highlight_cells:
+            self._highlight_cells()
 
     def _key_press_cb(self, widget, event):
         key = gtk.gdk.keyval_name(event.keyval)
@@ -195,47 +223,8 @@ class SudokuGameDisplay (SudokuNumberGrid, gobject.GObject):
         if dest_x is not None:
             self.set_focus(dest_x, dest_y)
 
-    def _go_around(self, x, y, direction):
-        '''return the coordinate if we should go to the other side of the grid.
-        Or else return None.'''
-        limit_min, limit_max = 0, self.group_size - 1
-        y_edge = [(limit_min, 'Up'), (limit_max, 'Down')]
-        x_edge = [(limit_min, 'Left'), (limit_max, 'Right')]
-        opposite_edge = {limit_min: limit_max, limit_max: limit_min}
-
-        if (y, direction) in y_edge:
-            return x, opposite_edge[y]
-        elif (x, direction) in x_edge:
-            return opposite_edge[x], y
-        else:
-            return None, None
-
-    def _focus_callback(self, e, event):
-        if self.do_highlight_cells:
-            self._highlight_cells()
-
-    def _get_highlight_colors(self):
-        entry = self.__entries__.values()[0]
-        default_color = gtkcolor_to_rgb(entry.style.bg[gtk.STATE_SELECTED])
-        hsv = colors.rgb_to_hsv(*default_color)
-        box_s = hsv[1]
-        box_v = hsv[2]
-        if box_v < 0.5:
-            box_v = box_v * 2
-        if box_s > 0.75:
-            box_s = box_s * 0.5
-        else:
-            box_s = box_s * 1.5
-            if box_s > 1:
-                box_s = 1.0
-        self.box_color = colors.hsv_to_rgb(hsv[0], box_s, box_v)
-        self.box_and_row_color = colors.rotate_hue_rgb(*self.box_color, **{'rotate_by': 0.33 / 2})
-        self.row_color = colors.rotate_hue_rgb(*self.box_color, **{'rotate_by': 0.33})
-        self.col_color = colors.rotate_hue_rgb(*self.box_color, **{'rotate_by': 0.66})
-        self.box_and_col_color = colors.rotate_hue_rgb(*self.box_color, **{'rotate_by': 1.0 - (0.33 / 2)})
-
     def toggle_highlight (self, val):
-        self.do_highlight_cells = val
+        self._do_highlight_cells = val
         if val:
             self._highlight_cells()
         else:
@@ -254,17 +243,14 @@ class SudokuGameDisplay (SudokuNumberGrid, gobject.GObject):
             self._get_highlight_colors()
         my_x, my_y = focused.x, focused.y
 
-        # col_coords can sometimes be null.
-        if not hasattr(self.grid, 'col_coords'):
-            return
-
-        for x, y in self.grid.col_coords[my_x]:
+        grid = self._model.grid
+        for x, y in grid.col_coords[my_x]:
             if (x, y) != (my_x, my_y):
                 self.__entries__[(x, y)].set_background_color(self.col_color)
-        for x, y in self.grid.row_coords[my_y]:
+        for x, y in grid.row_coords[my_y]:
             if (x, y) != (my_x, my_y):
                 self.__entries__[(x, y)].set_background_color(self.row_color)
-        for x, y in self.grid.box_coords[self.grid.box_by_coords[(my_x, my_y)]]:
+        for x, y in grid.box_coords[grid.box_by_coords[(my_x, my_y)]]:
             if (x, y) != (my_x, my_y):
                 e = self.__entries__[(x, y)]
                 if x == my_x:
@@ -274,6 +260,81 @@ class SudokuGameDisplay (SudokuNumberGrid, gobject.GObject):
                 else:
                     e.set_background_color(self.box_color)
 
+    def _get_highlight_colors(self):
+        entry = self.__entries__.values()[0]
+        default_color = gtkcolor_to_rgb(entry.style.bg[gtk.STATE_SELECTED])
+        hsv = colors.rgb_to_hsv(*default_color)
+        box_s = hsv[1]
+        box_v = hsv[2]
+        if box_v < 0.5:
+            box_v = box_v * 2
+        if box_s > 0.75:
+            box_s = box_s * 0.5
+        else:
+            box_s = box_s * 1.5
+            if box_s > 1:
+                box_s = 1.0
+        self.box_color = colors.hsv_to_rgb(hsv[0], box_s, box_v)
+        self.box_and_row_color = colors.rotate_hue_rgb(*self.box_color, **{'rotate_by': 0.33 / 2})
+        self.row_color = colors.rotate_hue_rgb(*self.box_color, **{'rotate_by': 0.33})
+        self.col_color = colors.rotate_hue_rgb(*self.box_color, **{'rotate_by': 0.66})
+        self.box_and_col_color = colors.rotate_hue_rgb(*self.box_color, **{'rotate_by': 1.0 - (0.33 / 2)})
+
+    def _go_around(self, x, y, direction):
+        '''return the coordinate if we should go to the other side of the grid.
+        Or else return None.'''
+        limit_min, limit_max = 0, self.group_size - 1
+        y_edge = [(limit_min, 'Up'), (limit_max, 'Down')]
+        x_edge = [(limit_min, 'Left'), (limit_max, 'Right')]
+        opposite_edge = {limit_min: limit_max, limit_max: limit_min}
+
+        if (y, direction) in y_edge:
+            return x, opposite_edge[y]
+        elif (x, direction) in x_edge:
+            return opposite_edge[x], y
+        else:
+            return None, None
+
+
+class NumberBoxModel:
+    def __init__(self, x, y, value=None, top_note=None, bottom_note=None,
+            conflict=None):
+        self.x = x
+        self.y = y
+        self.value = value
+        self.top_note = top_note
+        self.bottom_note = bottom_note
+        self.conflict = conflict
+
+
+class SudokuModel(gobject.GObject):
+
+    __gsignals__ = {
+        'puzzle-finished':(gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())
+        }
+
+    def __init__ (self, grid, group_size):
+        group_size = int(group_size)
+        self.hints = 0
+        self.always_show_hints = False
+        self.auto_fills = 0
+        self.show_impossible_implications = False
+        self.impossible_hints = 0
+        self.impossibilities = []
+        self.trackers = {}
+        self.__trackers_tracking__ = {}
+        gobject.GObject.__init__(self)
+        self._setup_grid(grid, group_size)
+
+        self._observers = []
+
+    def add_observer(self, observer):
+        self._observers.append(observer)
+
+    def _signal_observers(self, values):
+        for observer in self._observers:
+            observer.update(values)
+
     def show_hint (self):
         entry = self.get_focus()
         if entry:
@@ -376,18 +437,11 @@ class SudokuGameDisplay (SudokuNumberGrid, gobject.GObject):
                         self.add_value(col, row, values[index])
 
     def _setup_grid(self, grid, group_size):
-        self.doing_initial_setup = True
         self.__error_pairs__ = ParallelDict()
         if isinstance(grid, sudoku.SudokuGrid):
             self.grid = sudoku.InteractiveSudoku(grid.grid, group_size = grid.group_size)
         else:
             self.grid = sudoku.InteractiveSudoku(grid, group_size = group_size)
-        for x in range(group_size):
-            for y in range(group_size):
-                val = self.grid._get_(x, y)
-                if val:
-                    self.add_value(x, y, val)
-        self.doing_initial_setup = False
 
     def _number_changed_cb(self, widget, new_value):
         # TODO
@@ -415,16 +469,42 @@ class SudokuGameDisplay (SudokuNumberGrid, gobject.GObject):
         if self.grid.check_for_completeness():
             self.emit('puzzle-finished')
 
-    def _complain_conflicts(self, x, y, value):
-        '''set error highlights on [x, y] and all related box.
+    def set_value(self, x, y, value):
+        result = []
+        # first remove conflicts; grid.add should return conflicts together
+        # with ConflictError
+        old_conflicts = self._remove_related_conflicts(x, y)
+        new_conflicts = []
+        for entry in old_conflicts:
+            change = NumberBoxModel(x=entry[0], y=entry[1], conflict=False)
+            result.append(change)
+
+        try:
+            self.grid.add(x, y, value) # force=True???
+        except sudoku.ConflictError, err:
+            new_conflicts = self.grid.find_conflicts(x, y, value)
+        has_conflict = True if new_conflicts else False
+        result.append(NumberBoxModel(x, y, value, conflict=has_conflict))
+        if new_conflicts:
+            self._record_conflicts(x, y, new_conflicts)
+        for entry in new_conflicts:
+            change = NumberBoxModel(x=entry[0], y=entry[1], conflict=True)
+            result.append(change)
+        self._signal_observers(result)
+
+    def _remove_related_conflicts(self, x, y):
+        result = []
+        if self.__error_pairs__.has_key((x, y)):
+            errors_removed = self.__error_pairs__[(x, y)]
+            del self.__error_pairs__[(x, y)]
+            for coord in errors_removed:
+                # If we're not an error by some other pairing...
+                if not self.__error_pairs__.has_key(coord):
+                    result.append(coord)
+        return result
 
-        We think the error is caused by `value`. But the box at [x, y] could
-        have a different value.'''
-        self.__entries__[x, y].set_error_highlight(True)
-        conflicts = self.grid.find_conflicts(x, y, value)
-        for conflict in conflicts:
-            self.__entries__[conflict].set_error_highlight(True)
-        self.__error_pairs__[(x, y)] = conflicts
+    def _record_conflicts(self, x, y, new_conflicts):
+        self.__error_pairs__[(x, y)] = new_conflicts
 
     def add_value (self, x, y, val, trackers = []):
         """Add value val at position x, y.
@@ -486,19 +566,6 @@ class SudokuGameDisplay (SudokuNumberGrid, gobject.GObject):
             e.set_value(0)
         e.unset_color()
 
-    def _remove_error_highlight(self, x, y):
-        '''remove error highlight from [x, y] and also all errors caused by it'''
-        if self.__error_pairs__.has_key((x, y)):
-            entry = self.__entries__[(x, y)]
-            entry.set_error_highlight(False)
-            errors_removed = self.__error_pairs__[(x, y)]
-            del self.__error_pairs__[(x, y)]
-            for coord in errors_removed:
-                # If we're not an error by some other pairing...
-                if not self.__error_pairs__.has_key(coord):
-                    linked_entry = self.__entries__[coord]
-                    linked_entry.set_error_highlight(False)
-
     def auto_fill (self):
         changed = self.grid.auto_fill()
         retval = []
@@ -613,44 +680,3 @@ class SudokuGameDisplay (SudokuNumberGrid, gobject.GObject):
         if not val:
             val = self.grid._get_(x, y)
         self.trackers[tracker].remove((x, y, val))
-
-if __name__ == '__main__':
-    window = gtk.Window()
-    window.connect('delete-event', gtk.main_quit)
-
-    def test_number_grid ():
-        t = SudokuNumberGrid(4)
-        window.add(t)
-        t.__entries__[(0, 1)].set_color((0.0, 1.0, 0.0))
-        t.__entries__[(0, 1)].set_value(4)
-        t.__entries__[(1, 1)].set_error_highlight(True)
-        t.__entries__[(1, 1)].set_value(1)
-        t.__entries__[(2, 1)].set_color((0.0, 0.0, 1.0))
-        t.__entries__[(2, 1)].set_error_highlight(True)
-        t.__entries__[(2, 1)].set_value(2)
-        t.__entries__[(3, 1)].set_color((0.0, 0.0, 1.0))
-        t.__entries__[(3, 1)].set_error_highlight(True)
-        t.__entries__[(3, 1)].set_error_highlight(False)
-        t.__entries__[(3, 1)].set_value(3)
-        t.__entries__[(3, 1)].set_note_text('234', '12')
-
-    def test_sudoku_game ():
-        game = '''1 8 4 2 0 0 0 0 0
-                  0 6 0 0 0 9 1 2 0
-                  0 2 0 0 8 0 0 0 0
-                  0 1 8 0 5 0 0 0 0
-                  9 0 0 0 0 0 0 0 3
-                  0 0 0 0 1 0 6 5 0
-                  0 0 0 0 9 0 0 8 0
-                  0 5 7 1 0 0 0 9 0
-                  0 0 0 0 0 3 5 4 7'''
-        sgd = SudokuGameDisplay(game)
-        sgd.set_bg_color('black')
-        window.add(sgd)
-        window.show_all()
-
-#    test_number_grid()
-#    reproduce_foobared_rendering()
-    test_sudoku_game()
-    window.show_all()
-    gtk.main()
diff --git a/gnome-sudoku/src/lib/main.py b/gnome-sudoku/src/lib/main.py
index a27b6f7..7f60ac7 100644
--- a/gnome-sudoku/src/lib/main.py
+++ b/gnome-sudoku/src/lib/main.py
@@ -60,6 +60,8 @@ class UI (gconf_wrapper.GConfWrapper):
                                             )
         self.sudoku_maker = sudoku_maker.SudokuMaker()
         self.sudoku_tracker = saver.SudokuTracker()
+        self._main_model = None
+        self._main_grid_vew = None
         self.setup_gui()
 
         self.tube_handler = tube_handler.TubeHandler(self.tube_service,
@@ -116,7 +118,9 @@ class UI (gconf_wrapper.GConfWrapper):
         """Finally enter the puzzle"""
         self._puzzle = puzzle
         if game_type == game_selector.NewOrSavedGameSelector.NEW_GAME:
-            self.gsd.change_grid(puzzle, 9)
+            self._main_model = gsudoku.SudokuModel(puzzle, 9)
+            self._main_grid_vew.connect_to_model(self._main_model)
+#            self._main_grid_vew.connect('puzzle-finished', self.you_win_callback)
         elif game_type == game_selector.NewOrSavedGameSelector.SAVED_GAME:
             saver.open_game(self, puzzle)
         self._post_open_setup()
@@ -157,6 +161,7 @@ class UI (gconf_wrapper.GConfWrapper):
 
         self.setup_color()
         self.setup_actions()
+        return
         self.setup_undo()
         self.setup_autosave()
         #TODO
@@ -188,15 +193,14 @@ class UI (gconf_wrapper.GConfWrapper):
         side_grid_container = self.builder.get_object("side_grid_container")
         tracker_ui_container = self.builder.get_object("tracker_ui_container")
 
-        self.gsd = gsudoku.SudokuGameDisplay()
-        self.gsd.connect('puzzle-finished', self.you_win_callback)
-        main_grid_container.pack_start(self.gsd, padding = 6)
+        self._main_grid_vew = gsudoku.SudokuView(group_size=9)
+        main_grid_container.pack_start(self._main_grid_vew, padding=6)
 
         self.tracker_ui = tracker_box.TrackerBox(self)
         self.tracker_ui.hide()
         tracker_ui_container.pack_start(self.tracker_ui)
 
-        self._side_gsd = gsudoku.SudokuGameDisplay()
+        self._side_gsd = gsudoku.SudokuView(group_size=9)
         self._side_gsd.hide()
         side_grid_container.add(self._side_gsd)
 
@@ -209,7 +213,7 @@ class UI (gconf_wrapper.GConfWrapper):
         else:
             bgcol = None
         if bgcol:
-            self.gsd.set_bg_color(bgcol)
+            self._main_grid_vew.set_bg_color(bgcol)
 
     def setup_actions (self):
         toggle_actions = [
@@ -407,8 +411,8 @@ class UI (gconf_wrapper.GConfWrapper):
 
     def quit_cb (self, *args):
         self.w.hide()
-        if (self.gsd.grid
-            and self.gsd.grid.is_changed()
+        if (self._main_model.grid
+            and self._main_model.grid.is_changed()
             and (not self.won)):
             self.save_game(self)
         # make sure we really go away before doing our saving --
@@ -418,7 +422,7 @@ class UI (gconf_wrapper.GConfWrapper):
         if self.won:
             self.gconf['current_game'] = ''
         if not self.won:
-            if not self.gsd.grid:
+            if not self._main_model.grid:
                 self.gconf['current_game'] = ''
         self.stop_worker_thread()
         # allow KeyboardInterrupts, which calls quit_cb outside the main loop
@@ -529,11 +533,11 @@ class UI (gconf_wrapper.GConfWrapper):
         self.statusbar.push(self.sbid, status)
 
     def update_statusbar (self, *args):
-        if not self.gsd.grid:
+        if not self._main_model:
             self.set_statusbar_value(" ")
             return True
 
-        puzzle = self.gsd.grid.virgin.to_string()
+        puzzle = self._main_model.grid.virgin.to_string()
         puzzle_diff = self.sudoku_maker.get_difficulty(puzzle)
 
         tot_string = _("Playing %(difficulty)s puzzle.") % {'difficulty':puzzle_diff.value_string()}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]