# gcompris - lightsoff.py # # Copyright (C) 2010 Bruno and Clement 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 . # # lightsoff Board module import gtk import gtk.gdk import gcompris import gcompris.utils import gcompris.skin import gcompris.bonus import goocanvas from gcompris import gcompris_gettext as _ class Gcompris_lightsoff: """Empty gcompris python class""" def __init__(self, gcomprisBoard): # Save the gcomprisBoard, it defines everything we need # to know from the core self.gcomprisBoard = gcomprisBoard self.rootitem = None # 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 = False; self.gamewon = False; self.data = [ [[0,0,0,0,0], [0,0,1,0,0], [0,1,1,1,0], [0,0,1,0,0], [0,0,0,0,0]], [[1,1,0,0,0], [1,0,0,0,0], [0,0,0,0,0], [0,0,0,0,0], [0,0,0,0,0]], [[0,0,0,0,0], [0,0,0,0,0], [0,0,0,0,0], [0,0,1,0,0], [0,1,1,1,0]], [[1,1,0,1,1], [1,0,0,0,1], [0,0,0,0,0], [0,0,0,1,0], [0,0,1,1,1]], [[0,1,1,1,0], [1,0,1,0,1], [1,1,0,1,1], [1,0,1,0,1], [0,1,1,1,0]], [[1,1,0,1,1], [1,0,1,0,1], [0,1,1,1,0], [1,0,1,0,1], [1,1,0,1,1]], [[0,1,0,1,0], [1,1,0,1,1], [0,1,0,1,0], [1,0,1,0,1], [1,0,1,0,1]], [[1,0,0,0,1], [1,1,0,1,1], [0,0,1,0,0], [1,0,1,0,0], [1,0,1,1,0]], [[1,1,0,1,1], [0,0,0,0,0], [1,1,0,1,1], [0,0,0,0,1], [1,1,0,0,0]], # 5 [[1,1,1,1,1], [1,1,1,1,1], [1,1,1,1,1], [1,1,1,1,1], [1,1,1,1,1]], # 6 [[0,0,0,1,1], [0,0,0,1,1], [0,0,0,0,0], [1,1,0,0,0], [1,1,0,0,0]], # 7 [[0,0,0,0,0], [0,1,1,1,0], [1,1,1,1,1], [0,1,1,1,0], [0,0,0,0,0]], # 8 [[0,0,0,0,0], [0,1,1,1,0], [0,1,1,1,0], [0,1,1,1,0], [0,0,0,0,0]], # 9 [[1,1,0,1,1], [1,1,0,1,1], [0,0,0,0,0], [1,1,0,1,1], [1,1,0,1,1]], # 10 [[1,1,1,1,1], [0,1,1,1,0], [0,0,1,0,0], [0,1,1,1,0], [1,1,1,1,1]], # 11 [[1,1,1,1,1], [1,0,0,0,1], [1,0,0,0,1], [1,0,0,0,1], [1,1,1,1,1]], # 12 [[0,0,1,1,1], [0,0,0,1,1], [1,0,0,0,1], [1,1,0,0,0], [1,1,1,0,0]], # 13 [[1,0,0,0,1], [0,1,0,1,0], [0,0,1,0,0], [0,1,0,1,0], [1,0,0,0,1]], # 14 [[1,1,1,1,1], [1,0,1,0,1], [1,1,1,1,1], [1,0,1,0,1], [1,1,1,1,1]], # 15 [[1,0,0,0,0], [1,1,1,1,0], [1,1,1,1,0], [1,1,1,1,0], [1,1,1,1,1]], # 16 [[1,1,1,1,1], [1,1,1,1,1], [1,1,0,1,1], [1,1,1,1,1], [1,1,1,1,1]], # 17 [[1,0,1,0,1], [0,1,0,1,0], [0,0,1,0,0], [0,1,0,1,0], [1,0,1,0,1]], # 18 [[1,1,1,0,1], [1,1,1,0,1], [0,0,0,0,0], [1,0,1,1,1], [1,0,1,1,1]], # 19 [[1,1,0,1,1], [1,1,0,1,1], [1,1,0,1,1], [1,1,0,1,1], [1,1,0,1,1]], # 20 [[1,1,1,1,1], [1,0,0,0,1], [1,1,0,1,1], [1,1,0,1,1], [1,1,1,1,1]], # 21 [[1,1,1,1,1], [1,1,1,1,1], [0,0,0,1,1], [0,0,0,1,1], [0,0,0,1,1]], # 22 [[1,1,1,0,1], [1,1,1,0,0], [1,1,1,0,0], [1,1,1,0,0], [1,1,1,0,1]], # 23 [[1,1,1,1,1], [0,0,1,0,0], [0,0,1,0,0], [0,0,1,0,0], [1,1,1,1,1]], # 24 [[0,0,0,0,0], [0,1,1,0,0], [0,1,1,1,0], [0,0,1,1,0], [0,0,0,0,0]], # 25 [[0,0,0,1,1], [0,0,1,1,1], [0,0,1,0,0], [1,1,1,0,0], [1,1,0,0,0]], # 26 [[0,0,0,1,1], [1,1,0,1,1], [1,1,0,0,0], [1,1,0,0,0], [1,1,0,1,1]], # 27 [[1,0,0,0,1], [0,1,1,1,0], [0,1,1,1,0], [0,1,1,1,0], [1,0,0,0,1]], # 28 [[1,0,1,0,1], [0,1,0,1,0], [1,0,1,0,1], [0,1,0,1,0], [1,0,1,0,1]], # 29 [[0,0,1,0,0], [1,0,0,1,0], [0,1,1,1,1], [1,0,0,0,0], [1,1,0,1,0]], # 30 [[0,0,0,0,1], [0,0,0,1,1], [0,0,1,0,1], [0,1,0,0,1], [1,1,1,1,1]], # 31 [[1,1,0,1,1], [0,1,0,1,0], [1,1,1,1,1], [1,1,0,1,1], [1,0,0,0,1]], # 32 [[0,1,1,0,0], [0,1,1,0,1], [0,1,0,0,1], [1,1,0,0,0], [1,1,1,1,0]], # 33 [[0,0,0,0,1], [1,1,1,0,0], [1,0,1,1,1], [1,1,1,1,0], [1,0,0,1,0]], # 34 [[1,0,1,1,1], [0,0,1,0,1], [0,0,0,0,0], [1,1,1,1,0], [1,1,0,1,0]], # 35 [[1,1,0,1,1], [0,1,0,1,1], [0,0,0,1,0], [1,1,0,0,0], [1,1,1,1,0]], # 36 [[1,1,1,1,1], [0,0,0,1,0], [0,1,0,1,1], [1,1,1,0,1], [1,0,1,0,0]], # 37 [[0,0,0,1,1], [1,0,1,1,0], [0,0,1,0,0], [0,1,1,1,1], [1,0,0,1,0]], # 38 [[0,0,0,0,1], [0,0,1,1,1], [1,1,0,0,1], [1,1,1,0,0], [0,0,1,0,0]], # 39 [[0,0,1,1,1], [1,0,1,1,1], [1,1,1,0,0], [0,0,1,0,0], [1,1,0,1,1]], # 40 [[0,1,1,1,1], [0,0,1,1,1], [0,0,1,1,1], [1,1,1,1,0], [0,0,0,1,0]], # 41 [[1,1,1,1,1], [1,0,0,0,0], [0,1,0,0,1], [0,0,0,1,1], [1,1,1,1,1]], # 42 [[1,1,1,1,1], [1,0,0,0,0], [0,0,1,0,0], [0,1,1,1,0], [0,1,0,0,1]], # 43 [[0,0,0,0,0], [0,0,0,1,0], [1,1,0,1,1], [0,1,1,0,0], [1,1,1,1,1]], # 44 [[0,0,0,1,1], [0,1,1,0,0], [0,1,0,0,0], [1,1,1,1,0], [1,1,1,1,0]], # 45 [[0,0,0,1,0], [1,1,1,1,1], [0,0,0,0,0], [0,0,1,0,0], [1,1,1,1,0]], # 46 [[0,1,0,1,0], [0,0,0,1,0], [0,1,0,1,0], [0,0,1,0,0], [0,1,1,1,0]], # 47 [[1,0,0,1,0], [0,0,0,0,1], [0,1,0,0,0], [0,0,0,0,0], [1,0,1,0,0]], # 48 [[1,1,0,0,1], [0,1,0,0,1], [0,1,1,1,1], [0,1,0,1,0], [1,1,1,1,1]], # 49 [[1,1,1,1,1], [0,0,1,0,0], [0,1,1,0,0], [0,0,1,1,0], [1,1,1,0,1]], # 50 [[1,0,1,0,1], [1,0,1,0,0], [0,0,0,1,1], [0,1,0,1,0], [1,0,0,1,0]], # 51 [[0,1,0,1,0], [1,0,0,1,0], [0,1,1,1,1], [1,0,1,0,0], [0,1,1,0,0]], # 52 [[1,1,1,1,1], [1,1,0,0,0], [0,0,0,1,1], [0,1,1,1,0], [0,0,1,0,0]], ] def start(self): self.gcomprisBoard.level=1 self.gcomprisBoard.maxlevel=len(self.data) self.gcomprisBoard.sublevel=1 self.gcomprisBoard.number_of_sublevel=1 # 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.backroot = goocanvas.Group(parent = \ self.gcomprisBoard.canvas.get_root_item()) # A color changing background self.background = goocanvas.Rect( parent = self.backroot, x = 0, y = 0, width = gcompris.BOARD_WIDTH, height = 200, fill_color_rgba = 0xFFFFFFFFL ) svghandle = gcompris.utils.load_svg("lightsoff/back.svgz") goocanvas.Svg( parent = self.backroot, svg_handle = svghandle, svg_id = "#BACKGROUND", pointer_events = goocanvas.EVENTS_NONE ) # The Sun self.sunitem = goocanvas.Svg( parent = self.backroot, svg_handle = svghandle, svg_id = "#SUN", ) self.sunitem_offset = 0 # TUX self.tuxitem = goocanvas.Svg( parent = self.backroot, svg_handle = svghandle, svg_id = "#TUX", ) self.tuxitem.connect("button_press_event", self.solve_event) gcompris.utils.item_focus_init(self.tuxitem, None) # The Buble self.bubbleitem = goocanvas.Svg( parent = self.backroot, svg_handle = svghandle, svg_id = "#BUBBLE1", ) self.textitem = goocanvas.Text( parent = self.backroot, x = gcompris.BOARD_WIDTH/2 + 80, y = gcompris.BOARD_HEIGHT - 80, width = 400, font = gcompris.skin.get_font("gcompris/content"), text = _("Switch off all the lights, I have to go to sleep.\n" "If you need help, click on me."), fill_color = "black", anchor = gtk.ANCHOR_CENTER ) # The Tipi self.tipiitem = goocanvas.Svg( parent = self.backroot, svg_handle = svghandle, svg_id = "#TIPI", ) # The image foreground goocanvas.Svg( parent = self.backroot, svg_handle = svghandle, svg_id = "#FOREGROUND", pointer_events = goocanvas.EVENTS_NONE ) # Set the buttons we want in the bar gcompris.bar_set(gcompris.BAR_LEVEL|gcompris.BAR_REPEAT) gcompris.bar_location(gcompris.BOARD_WIDTH/2 - 90, -1, 0.6) gcompris.bar_set_level(self.gcomprisBoard) self.display_game() def end(self): self.backroot.remove() self.backroot = None self.rootitem = None def ok(self): print("lightsoff ok.") def repeat(self): self.display_game() def config(self): print("lightsoff config.") def key_press(self, keyval, commit_str, preedit_str): pass 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 == True and pause == False): self.gamewon = False if(self.increment_level()): self.display_game() def set_level(self, level): self.gcomprisBoard.level = level gcompris.bar_set_level(self.gcomprisBoard) self.display_game() # Code that increments the sublevel and level # And bail out if no more levels are available # return 1 if continue, 0 if bail out def increment_level(self): self.gcomprisBoard.sublevel += 1 if(self.gcomprisBoard.sublevel>self.gcomprisBoard.number_of_sublevel): # Try the next level self.gcomprisBoard.sublevel=1 self.gcomprisBoard.level += 1 gcompris.bar_set_level(self.gcomprisBoard) if(self.gcomprisBoard.level>self.gcomprisBoard.maxlevel): self.gcomprisBoard.level = self.gcomprisBoard.maxlevel return 1 def create_empty_list(self): items = [] for x in range(5): items.append(range(5)) for y in range(len(items)): for x in range(len(items[0])): items[y][x] = 0 return items # Display the board game def display_game(self): # The grid we display # It contains all the graphic items self.items = self.create_empty_list() # The grid of hints items self.hints = self.create_empty_list() # Do we display the hints self.hints_mode = False if self.rootitem: self.rootitem.remove() self.tipiitem.props.visibility = goocanvas.ITEM_INVISIBLE self.textitem.props.visibility = goocanvas.ITEM_VISIBLE self.tuxitem.props.visibility = goocanvas.ITEM_VISIBLE self.bubbleitem.props.visibility = goocanvas.ITEM_VISIBLE # 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 = \ goocanvas.Group(parent = self.backroot) svghandle = gcompris.utils.load_svg("lightsoff/onoff.svgz") iwidth = svghandle.props.width iheight = svghandle.props.height gap = 10 x_start = (gcompris.BOARD_WIDTH - len(self.items) * (iwidth + gap) ) / 2 y_start = (gcompris.BOARD_HEIGHT - len(self.items[0]) \ * (iheight + gap) ) / 2 - 40 goocanvas.Rect( parent = self.rootitem, x = x_start - gap, y = y_start - gap, width = len(self.items) * (iwidth + gap) + gap, height = len(self.items[0]) * (iheight + gap) + gap, fill_color_rgba = 0x445533AAL, stroke_color_rgba = 0xC0C0C0AAL, radius_x = 10, radius_y = 10, line_width = 2 ) data = self.data[self.gcomprisBoard.level - 1] for y in range(len(self.items)): for x in range(len(self.items[0])): item = goocanvas.Rect( parent = self.rootitem, x = x_start + (iwidth + gap) * x - gap/2, y = y_start + (iheight + gap) * y - gap/2, width = iwidth + gap, height = iheight + gap, stroke_color_rgba = 0xC0C0C0FAL, fill_color_rgba = 0x5050509AL, line_width = 2, radius_x = 10, radius_y = 10, ) self.hints[y][x] = item item.props.visibility = goocanvas.ITEM_INVISIBLE state = data[y][x] svg_id = "#off" if state: svg_id = "#on" item = goocanvas.Svg( parent = self.rootitem, svg_handle = svghandle, svg_id = svg_id, ) item.set_data("state", state) item.translate(x_start + (iwidth + gap) * x, y_start + (iheight + gap) * y) item.connect("button_press_event", self.button_press_event, [y,x]) self.items[y][x] = item self.solve_it() def is_on(self, item): return item.get_data("state") def switch(self, item): if not item: return mystate = self.is_on(item) if mystate == False: item.set_properties(svg_id = "#on") else: item.set_properties(svg_id = "#off") item.set_data("state", not mystate) def get_item_up(self, y, x): if y == 0: return None return self.items[y-1][x] def get_item_down(self, y, x): if y == len(self.items[0])-1: return None return self.items[y+1][x] def get_item_left(self, y, x): if x == 0: return None return self.items[y][x-1] def get_item_right(self, y, x): if x == len(self.items)-1: return None return self.items[y][x+1] # Returns True when complete def is_done(self): for y in range(len(self.items)): for x in range(len(self.items[0])): if self.is_on(self.items[y][x]): return False return True def button_press_event(self, widget, target, event, spot): self.switch(target) self.switch(self.get_item_up(spot[0], spot[1])) self.switch(self.get_item_left(spot[0], spot[1])) self.switch(self.get_item_right(spot[0], spot[1])) self.switch(self.get_item_down(spot[0], spot[1])) self.solve_it() if self.is_done(): self.tipiitem.props.visibility = goocanvas.ITEM_VISIBLE self.textitem.props.visibility = goocanvas.ITEM_INVISIBLE self.tuxitem.props.visibility = goocanvas.ITEM_INVISIBLE self.bubbleitem.props.visibility = goocanvas.ITEM_INVISIBLE self.gamewon = True gcompris.bonus.display(gcompris.bonus.WIN, gcompris.bonus.FLOWER) def solution_switch(self, items, clicks, y, x): items[y][x] = not items[y][x] clicks[y][x] = not clicks[y][x] if y >= 1: items[y-1][x] = not items[y-1][x] if y <= 3: items[y+1][x] = not items[y+1][x] if x >= 1: items[y][x-1] = not items[y][x-1] if x <= 3: items[y][x+1] = not items[y][x+1] def chase_light(self, items, clicks): for y in range(1, len(items)): for x in range(len(items[0])): if items[y-1][x]: self.solution_switch(items, clicks, y, x) def is_solution_pattern(self, s, a, b, c, d, e): if s[4][0] == a and \ s[4][1] == b and \ s[4][2] == c and \ s[4][3] == d and \ s[4][4] == e: return True return False # Return False if the is no solution def solution_wrap(self, solution, clicks): if self.is_solution_pattern(solution, 1, 0, 0 , 0, 1): self.solution_switch(solution, clicks, 0, 0) self.solution_switch(solution, clicks, 0, 1) elif self.is_solution_pattern(solution, 0, 1, 0, 1, 0): self.solution_switch(solution, clicks, 0, 0) self.solution_switch(solution, clicks, 0, 3) elif self.is_solution_pattern(solution, 1, 1, 1, 0, 0): self.solution_switch(solution, clicks, 0, 1) elif self.is_solution_pattern(solution, 0, 0, 1, 1 , 1): self.solution_switch(solution, clicks, 0, 3) elif self.is_solution_pattern(solution, 1, 0, 1, 1, 0): self.solution_switch(solution, clicks, 0, 4) elif self.is_solution_pattern(solution, 0, 1, 1, 0, 1): self.solution_switch(solution, clicks, 0, 0) elif self.is_solution_pattern(solution, 1, 1, 0, 1, 1): self.solution_switch(solution, clicks, 0, 2) else: return False return True def items2list(self, items): list = [] for y in range(len(items[0])): line = [] for x in range(len(items)): if self.is_on(items[y][x]): line.append(1) else: line.append(0) list.append(line) return list # We check only the last line def solution_found(self, solution): for x in range(len(solution[0])): if solution[4][x]: return False return True def solution_length(self, clicks): click = 0 for y in range(0, len(clicks)): for x in range(len(clicks[0])): if clicks[y][x]: click += 1 return click def solve_one(self, solution, clicks): found = False for index in range(0, 5): self.chase_light( solution, clicks ) if self.solution_found(solution): found = True break if not self.solution_wrap(solution, clicks): break if found: return clicks else: return None # Solving algorithm is the one described here: # http://www.haar.clara.co.uk/Lights/solving.html To begin, you turn # out all the lights on the top row, by pressing the buttons on the # second row that are directly underneath any lit buttons on the top # row. The top row will then have all it's lights off. Repeat this # step for the second, third and fourth row. (i.e. chase the lights # all the way down to the bottom row). This may have solved the # puzzle already ( click here for an example of this ), but is more # likely that there will now be some lights left on in the bottom # row. If so, there are only 7 posible configurations. Depending on # which configuration you are left with, you need to press some # buttons in the top row. You can determine which buttons you need # to press from the following table. # Light on bottom row Press on this on top row # 10001 11000 # 01010 10010 # 11100 01000 # 00111 00010 # 10110 00001 # 01101 10000 # 11011 00100 def solve_it(self): clicks = None # Our solving algorithm does not find the shortest solution. We # don't really care but we'd like to keep the proposed solution # stable (not propose a complete new solution when one light # changes). To achieve this (closely), we test here all the # combination of the first line, trying to find the shortest # solution. for x in range(64): solution = self.items2list(self.items) clicks2 = self.create_empty_list() if x & 1: self.solution_switch(solution, clicks2, 0, 0) if x & 2: self.solution_switch(solution, clicks2, 0, 1) if x & 4: self.solution_switch(solution, clicks2, 0, 2) if x & 8: self.solution_switch(solution, clicks2, 0, 3) if x & 16: self.solution_switch(solution, clicks2, 0, 4) clicks2 = self.solve_one(solution, clicks2) if clicks == None and clicks2: clicks = clicks2 elif clicks2 and \ self.solution_length(clicks2) < self.solution_length(clicks): clicks = clicks2 if self.hints_mode: self.show_hints(clicks) self.update_background(clicks) def solve_event(self, widget, target, event): clicks = self.create_empty_list() self.hints_mode = not self.hints_mode if not self.hints_mode: self.show_hints(clicks) else: self.solve_it() def update_background(self, clicks): length = self.solution_length(clicks) c = int(length * 0xFF / 18.0) color = 0X33 << 24 | 0x11 << 16 | c << 8 | 0xFFL self.background.set_properties(fill_color_rgba = color) self.sunitem.translate(0, self.sunitem_offset) self.sunitem_offset = length * 10 self.sunitem.translate(0, self.sunitem_offset * -1) def show_hints(self, clicks): for y in range(len(clicks)): for x in range(len(clicks[0])): if clicks[y][x]: self.hints[y][x].props.visibility = goocanvas.ITEM_VISIBLE else: self.hints[y][x].props.visibility = goocanvas.ITEM_INVISIBLE def print_sol(self, clicks): for y in range(len(clicks)): s = "" for x in range(len(clicks[0])): if clicks[y][x]: s += "1" else: s += "0" print s print ""