[gnome-games/sudoku-tube] Towards split view and model
- From: Zhang Sen <zhangsen src gnome org>
- To: svn-commits-list gnome org
- Subject: [gnome-games/sudoku-tube] Towards split view and model
- Date: Fri, 10 Jul 2009 13:48:40 +0000 (UTC)
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]