# gcompris - sudoku # # Copyright (C) 2005 Bruno Coudoin # # This program 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. # # This program 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 this program; if not, see . # import gnomecanvas import gcompris import gcompris.utils import gcompris.skin import gcompris.bonus import gcompris.score import gtk import gtk.gdk import random from gcompris import gcompris_gettext as _ class Gcompris_sudoku: """Sudoku game""" def __init__(self, gcomprisBoard): self.gcomprisBoard = gcomprisBoard self.gcomprisBoard.disable_im_context = True # These are used to let us restart only after the bonus is displayed. # When the bonus is displayed, it call us first with pause(1) and then with pause(0) self.board_paused = 0; self.gamewon = 0; # It holds the canvas items for each square self.sudo_square = [] # The square Rect Item self.sudo_number = [] # The square Text Item self.sudo_symbol = [] # The square Symbol Item self.valid_chars = [] # The valid chars for the sudoku are calculated from the dataset # Holds the coordinate of the current square self.cursqre = None self.normal_square_color = 0xbebbc9ffL self.highl_square_color = 0x8b83a7ffL self.focus_square_color = 0x555555ffL self.fixed_square_color = 0x8bAAa7ffL self.error_square_color = 0xff77a7ffL self.lines_color = 0xebe745ffL # These colors must be different or it won't work self.fixed_number_color = 0x000cffffL self.user_number_color = 0x000bffffL self.root_sudo = None self.sudoku = None # The current sudoku data self.sudo_size = 0 # the size of the current sudoku self.sudo_region = None # the modulo region in the current sudoku self.timer = 0 # The timer that highlights errors self.symbolize_level_max = 4 # Last level in which we set symbols self.symbols = [ gcompris.utils.load_pixmap("images/rectangle.png"), gcompris.utils.load_pixmap("images/circle.png"), gcompris.utils.load_pixmap("images/rhombus.png"), gcompris.utils.load_pixmap("images/triangle.png"), gcompris.utils.load_pixmap("images/star.png") ] def start(self): # Init the sudoku dataset self.sudoku = self.init_item_list() self.gcomprisBoard.level=1 self.gcomprisBoard.maxlevel=len(self.sudoku) self.gcomprisBoard.sublevel=1 pixmap = gcompris.utils.load_pixmap(gcompris.skin.image_to_skin("button_reload.png")) if(pixmap): gcompris.bar_set_repeat_icon(pixmap) gcompris.bar_set(gcompris.BAR_LEVEL|gcompris.BAR_REPEAT_ICON) else: gcompris.bar_set(gcompris.BAR_LEVEL|gcompris.BAR_REPEAT) gcompris.set_background(self.gcomprisBoard.canvas.root(), gcompris.skin.image_to_skin("gcompris-bg.jpg")) gcompris.bar_set_level(self.gcomprisBoard) # Create our rootitem. We put each canvas item in it so at the end we # only have to kill it. The canvas deletes all the items it contains automaticaly. self.rootitem = self.gcomprisBoard.canvas.root().add( gnomecanvas.CanvasGroup, x=0.0, y=0.0 ) self.next_level() self.pause(0); def end(self): # Remove the root item removes all the others inside it self.rootitem.destroy() self.rootitem = None gcompris.score.end() def ok(self): print("Gcompris_sudoku ok.") def repeat(self): self.display_sudoku(self.sudoku[self.gcomprisBoard.level-1][self.gcomprisBoard.sublevel-1]) def config(self): print("Gcompris_sudoku config.") def key_press(self, keyval, commit_str, preedit_str): if(self.cursqre == None): return False utf8char = gtk.gdk.keyval_to_unicode(keyval) strn = u'%c' % utf8char if(strn in self.valid_chars): if self.is_legal(strn): self.sudo_number[self.cursqre[0]][self.cursqre[1]].set( text = strn.encode('UTF-8'), ) # Maybe it's all done if self.is_solved(): self.cursqre = None self.gamewon = 1 gcompris.bonus.display(gcompris.bonus.WIN, gcompris.bonus.FLOWER) else: # Erase the old number there if ((keyval == gtk.keysyms.BackSpace) or (keyval == gtk.keysyms.Delete) or (keyval == gtk.keysyms.space)): self.sudo_number[self.cursqre[0]][self.cursqre[1]].set( text = "", ) else: # No key processing done return False # Return True if you did process a key # Return False if you did not processed a key # (gtk need to send it to next widget) return True def pause(self, pause): self.board_paused = pause # When the bonus is displayed, it call us first # with pause(1) and then with pause(0) # the game is won if(self.gamewon == 1 and pause == 0): self.gamewon = 0 if(self.increment_level()): self.next_level() return def set_level(self, level): self.gcomprisBoard.level = level; self.gcomprisBoard.sublevel = 1; gcompris.bar_set_level(self.gcomprisBoard) self.next_level() # ---- End of Initialisation # Code that increments the sublevel and level # And bail out if no more levels are available # return True if continue, False if bail out def next_level(self): # Randomize symbols for j in range(0, len(self.symbols)): # Select a random new position to set the J symbol old_symbol = self.symbols[j] new_pos = random.randint(0,len(self.symbols)-1) self.symbols[j] = self.symbols[new_pos] self.symbols[new_pos] = old_symbol self.display_sudoku(self.sudoku[self.gcomprisBoard.level-1][self.gcomprisBoard.sublevel-1]) gcompris.score.start(gcompris.score.STYLE_NOTE, 610, 485, len(self.sudoku[self.gcomprisBoard.level-1])) gcompris.score.set(self.gcomprisBoard.sublevel) return True # Code that increments the sublevel and level # And bail out if no more levels are available # return True if continue, False if bail out def increment_level(self): self.gcomprisBoard.sublevel += 1 if(self.gcomprisBoard.sublevel > len(self.sudoku[self.gcomprisBoard.level-1])): # Try the next level self.gcomprisBoard.sublevel=1 self.gcomprisBoard.level += 1 # Set the level in the control bar gcompris.bar_set_level(self.gcomprisBoard); if(self.gcomprisBoard.level>self.gcomprisBoard.maxlevel): # the last board is finished : bail out gcompris.bonus.board_finished(gcompris.bonus.FINISHED_RANDOM) return False return True # # Set a symbol in the sudoku # def set_sudoku_symbol(self, text, x, y): pixmap = self.get_pixmap_symbol(self.valid_chars, text) self.sudo_symbol[x][y].set( pixbuf = pixmap ) self.sudo_symbol[x][y].show() self.sudo_number[x][y].set( text = text ) # # Event on a placed symbol. Means that we remove it # def hide_symbol_event(self, item, event, data): if(self.gamewon): return False if event.type == gtk.gdk.BUTTON_PRESS: item.hide() self.sudo_number[data[0]][data[1]].set( text = "" ) # # This function is being called uppon a click on a symbol on the left # If a square has the focus, then the clicked square is assigned there # def symbol_item_event(self, item, event, text): if(self.gamewon): return False # # MOTION EVENT # ------------ if event.type == gtk.gdk.MOTION_NOTIFY: if event.state & gtk.gdk.BUTTON1_MASK: x=event.x y=event.y item.set( x= x, y= y) return True # # MOUSE DRAG STOP # --------------- if event.type == gtk.gdk.BUTTON_RELEASE: if event.button == 1: x=event.x y=event.y cx = float(item.get_data("orig_x")) cy = float(item.get_data("orig_y")) item.set( x= cx, y= cy) target_item = self.gcomprisBoard.canvas.get_item_at(x, y) if target_item: # Get it's sudo coord x = target_item.get_data("sudo_x") if x: x = int(x) y = target_item.get_data("sudo_y") if y: y = int(y) self.cursqre = (x, y) if self.is_legal(text): self.set_sudoku_symbol(text, x, y) # Maybe it's all done if self.is_solved(): self.gamewon = 1 gcompris.bonus.display(gcompris.bonus.WIN, gcompris.bonus.FLOWER) return True return False # # This function is being called uppon a click on any little square # def square_item_event(self, widget, event, square): if(self.gamewon): return False # Check it's a user editable square oldcolor = self.sudo_number[square[0]][square[1]].get_property('fill_color_rgba') if oldcolor == self.fixed_number_color: return False if event.type == gtk.gdk.ENTER_NOTIFY: if self.cursqre != square: self.sudo_square[square[0]][square[1]].set( fill_color_rgba = self.highl_square_color, ) elif event.type == gtk.gdk.LEAVE_NOTIFY: if self.cursqre == square: self.sudo_square[square[0]][square[1]].set( fill_color_rgba = self.focus_square_color, ) else: self.sudo_square[square[0]][square[1]].set( fill_color_rgba = self.normal_square_color, ) elif event.type == gtk.gdk.BUTTON_PRESS: if(self.cursqre): self.sudo_square[self.cursqre[0]][self.cursqre[1]].set( fill_color_rgba = self.normal_square_color, ) self.cursqre = square self.sudo_square[square[0]][square[1]].set( fill_color_rgba = self.focus_square_color, ) return True return False # Highlight the given item to show it's the one that causes the resufal # to pose a number def set_on_error(self, items): for item in items: item.set( fill_color_rgba= self.error_square_color ) self.timer = gtk.timeout_add(3000, self.unset_on_error, items) def unset_on_error(self, items): if(self.rootitem): for item in items: item.set( fill_color_rgba= self.fixed_square_color ) # Return True or False if the given number is possible # def is_legal(self, number): if((self.cursqre[0] == None) or (self.cursqre[1] == None)): return possible = True bad_square = [] if(self.cursqre == None): return possible # Check this number is not already in a row for x in range(0,self.sudo_size): if x == self.cursqre[0]: continue item = self.sudo_number[x][self.cursqre[1]] othernumber = item.get_property('text').decode('UTF-8') if(number == othernumber): bad_square.append(self.sudo_square[x][self.cursqre[1]]) possible = False # Check this number is not already in a column for y in range(0,self.sudo_size): if y == self.cursqre[1]: continue item = self.sudo_number[self.cursqre[0]][y] othernumber = item.get_property('text').decode('UTF-8') if(number == othernumber): bad_square.append(self.sudo_square[self.cursqre[0]][y]) possible = False # # Check this number is in a region # # Region is the modulo place to set region if defined region = None if(self.sudo_region.has_key(self.sudo_size)): region=self.sudo_region[self.sudo_size] if(region): # First, find the top-left square of the region top_left=[self.cursqre[0]/region*region, self.cursqre[1]/region*region] for x in range(0,region): for y in range(0,region): # Do not check the current square if (top_left[0] + x == self.cursqre[0] and top_left[1] + y == self.cursqre[1]): continue item = self.sudo_number[top_left[0] + x][top_left[1] + y] othernumber = item.get_property('text').decode('UTF-8') if(number == othernumber): bad_square.append(self.sudo_square[top_left[0] + x][top_left[1] + y]) possible = False if not possible: self.set_on_error(bad_square) return possible # Return True or False if the given sudoku is solved # We don't really check it's solved, only that all squares # have a value. This works because only valid numbers can # be entered up front. # def is_solved(self): for x in range(0,self.sudo_size): for y in range(0,self.sudo_size): item = self.sudo_number[x][y] number = item.get_property('text').decode('UTF-8') if(number == ""): return False return True # # Display valid number (or chars) # def display_valid_chars(self, sudo_size, valid_chars): square_width = (gcompris.BOARD_HEIGHT-50)/sudo_size square_height = square_width x_init = 20 y_init = (gcompris.BOARD_HEIGHT - square_height*sudo_size)/2 for y in range(0,sudo_size): # For low levels, we symbolize the letters if(self.gcomprisBoard.level