class Game(): def __init__(self, canvas, parent=None, colors=['#A0FFA0', '#FF8080']): self._activity = parent self._colors = [colors[0]] self._colors.append(colors[1]) self._colors.append('#D0D0D0') self._colors.append('#000000') self._canvas = canvas if parent is not None: parent.show_all() self._parent = parent self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.connect("draw", self.__draw_cb) self._canvas.connect("button-press-event", self._button_press_cb) self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - (GRID_CELL_SIZE * 1.5) self._scale = self._width / (10 * DOT_SIZE * 1.2) self._dot_size = int(DOT_SIZE * self._scale) self._space = int(self._dot_size / 5.) self.we_are_sharing = False self._edge = 4 self._move_list = [] # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) self._dots = [] self._generate_grid() def _generate_grid(self): ''' Make a new set of dots for a grid of size edge ''' i = 0 for y in range(self._edge): for x in range(self._edge): xoffset = int((self._width - self._edge * self._dot_size - (self._edge - 1) * self._space) / 2.) if i < len(self._dots): self._dots[i].move( (xoffset + x * (self._dot_size + self._space), y * (self._dot_size + self._space))) else: self._dots.append( Sprite(self._sprites, xoffset + x * (self._dot_size + self._space), y * (self._dot_size + self._space), self._new_dot(self._colors[0]))) self._dots[i].type = 0 self._dots[-1].set_label_attributes(40) i += 1 # and initialize a few variables we'll need. self._all_clear() def _all_clear(self): ''' Things to reinitialize when starting up a new game. ''' self._move_list = [] # Clear dots for dot in self._dots: dot.type = 0 dot.set_shape(self._new_dot(self._colors[0])) dot.set_label('') def _initiating(self): return self._activity.initiating def more_dots(self): ''' Enlarge the grid ''' if self._edge < MAX: self._edge += 1 self._generate_grid() self.new_game() def new_game(self): ''' Start a new game. ''' self._all_clear() # Fill in a few dots to start for i in range(MAX * 2): self._flip_them(int(uniform(0, self._edge * self._edge))) if self.we_are_sharing: _logger.debug('sending a new game') self._parent.send_new_game() def restore_game(self, dot_list, move_list): ''' Restore a game from the Journal or share ''' edge = int(sqrt(len(dot_list))) if edge > MAX: edge = MAX while self._edge < edge: self.more_dots() for i, dot in enumerate(dot_list): self._dots[i].type = dot self._dots[i].set_shape(self._new_dot( self._colors[self._dots[i].type])) if move_list is not None: self._move_list = move_list[:] def save_game(self): ''' Return dot list, move_list for saving to Journal or sharing ''' dot_list = [] for dot in self._dots: dot_list.append(dot.type) return (dot_list, self._move_list) def _set_label(self, string): ''' Set the label in the toolbar or the window frame. ''' self._activity.status.set_label(string) def _button_press_cb(self, win, event): win.grab_focus() x, y = map(int, event.get_coords()) spr = self._sprites.find_sprite((x, y)) if spr is None: return if spr.type is not None: self._flip_them(self._dots.index(spr)) self._test_game_over() if self.we_are_sharing: _logger.debug('sending a click to the share') self._parent.send_dot_click(self._dots.index(spr)) return True def solve(self): ''' Solve the puzzle by undoing moves ''' if self._move_list == []: return self._flip_them(self._move_list.pop(), append=False) GObject.timeout_add(750, self.solve) def _flip_them(self, dot, append=True): ''' flip the dot and its neighbors ''' if append: self._move_list.append(dot) x, y = self._dot_to_grid(dot) self._flip(self._dots[dot]) if x > 0: self._flip(self._dots[dot - 1]) if y > 0: self._flip(self._dots[dot - self._edge]) if x < self._edge - 1: self._flip(self._dots[dot + 1]) if y < self._edge - 1: self._flip(self._dots[dot + self._edge]) def _flip(self, spr): ''' flip a dot ''' spr.type += 1 spr.type %= 2 spr.set_shape(self._new_dot(self._colors[spr.type])) def remote_button_press(self, dot): ''' Receive a button press from a sharer ''' self._flip_them(dot) self._test_game_over() def set_sharing(self, share=True): _logger.debug('enabling sharing') self.we_are_sharing = share def _smile(self): for dot in self._dots: dot.set_label(':)') def _test_game_over(self): ''' Check to see if game is over: all dots the same color ''' match = self._dots[0].type for y in range(self._edge): for x in range(self._edge): if self._dots[y * self._edge + x].type != match: self._set_label(_('keep trying')) return False self._set_label(_('good work')) self._smile() GObject.timeout_add(2000, self.more_dots) return True def _grid_to_dot(self, pos): ''' calculate the dot index from a column and row in the grid ''' return pos[0] + pos[1] * self._edge def _dot_to_grid(self, dot): ''' calculate the grid column and row for a dot ''' return [dot % self._edge, int(dot / self._edge)] def game_over(self, msg=_('Game over')): ''' Nothing left to do except show the results. ''' self._set_label(msg) def __draw_cb(self, canvas, cr): self._sprites.redraw_sprites(cr=cr) def do_expose_event(self, event): ''' Handle the expose-event by drawing ''' # Restrict Cairo to the exposed area cr = self._canvas.window.cairo_create() cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): Gtk.main_quit() def _new_dot(self, color): ''' generate a dot of a color color ''' self._dot_cache = {} if color not in self._dot_cache: self._stroke = color self._fill = color self._svg_width = self._dot_size self._svg_height = self._dot_size pixbuf = svg_str_to_pixbuf( self._header() + self._circle(self._dot_size / 2., self._dot_size / 2., self._dot_size / 2.) + self._footer()) surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self._svg_width, self._svg_height) context = cairo.Context(surface) Gdk.cairo_set_source_pixbuf(context, pixbuf, 0, 0) context.rectangle(0, 0, self._svg_width, self._svg_height) context.fill() self._dot_cache[color] = surface return self._dot_cache[color] def _header(self): return '<svg\n' + 'xmlns:svg="http://www.w3.org/2000/svg"\n' + \ 'xmlns="http://www.w3.org/2000/svg"\n' + \ 'xmlns:xlink="http://www.w3.org/1999/xlink"\n' + \ 'version="1.1"\n' + 'width="' + str(self._svg_width) + '"\n' + \ 'height="' + str(self._svg_height) + '">\n' def _circle(self, r, cx, cy): return '<circle style="fill:' + str(self._fill) + ';stroke:' + \ str(self._stroke) + ';" r="' + str(r - 0.5) + '" cx="' + \ str(cx) + '" cy="' + str(cy) + '" />\n' def _footer(self): return '</svg>\n'
class Game(): def __init__(self, canvas, parent=None, colors=['#A0FFA0', '#FF8080']): self._activity = parent self._colors = [colors[0]] self._colors.append(colors[1]) self._colors.append('#FFFFFF') self._colors.append('#000000') self._colors.append('#FF0000') self._colors.append('#FF8000') self._colors.append('#FFFF00') self._colors.append('#00FF00') self._colors.append('#00FFFF') self._colors.append('#0000FF') self._colors.append('#FF00FF') self._canvas = canvas if parent is not None: parent.show_all() self._parent = parent self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK) self._canvas.add_events(Gdk.EventMask.POINTER_MOTION_MASK) self._canvas.connect("draw", self.__draw_cb) self._canvas.connect("button-press-event", self._button_press_cb) self._canvas.connect("button-release-event", self._button_release_cb) self._canvas.connect("motion-notify-event", self._mouse_move_cb) self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - GRID_CELL_SIZE scale = [self._width / (10 * DOT_SIZE * 1.2), self._height / (6 * DOT_SIZE * 1.2)] self._scale = min(scale) self._dot_size = int(DOT_SIZE * self._scale) self._space = int(self._dot_size / 5.) self._orientation = 'horizontal' self.we_are_sharing = False self.playing_with_robot = False self._press = False self.last_spr = None self._timer = None self.roygbiv = False # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) self._dots = [] for y in range(SIX): for x in range(TEN): xoffset = int((self._width - TEN * self._dot_size - \ (TEN - 1) * self._space) / 2.) self._dots.append( Sprite(self._sprites, xoffset + x * (self._dot_size + self._space), y * (self._dot_size + self._space), self._new_dot(self._colors[2]))) self._dots[-1].type = 2 # not set self._dots[-1].set_label_attributes(40) self.vline = Sprite(self._sprites, int(self._width / 2.) - 1, 0, self._line(vertical=True)) n = SIX / 2. self.hline = Sprite( self._sprites, 0, int(self._dot_size * n + self._space * (n - 0.5)) - 1, self._line(vertical=False)) self.hline.hide() # and initialize a few variables we'll need. self._all_clear() def _all_clear(self): ''' Things to reinitialize when starting up a new game. ''' for dot in self._dots: dot.type = 2 dot.set_shape(self._new_dot(self._colors[2])) dot.set_label('') self._set_orientation() def _set_orientation(self): ''' Set bar and message for current orientation ''' if self._orientation == 'horizontal': self.hline.hide() self.vline.set_layer(1000) elif self._orientation == 'vertical': self.hline.set_layer(1000) self.vline.hide() else: self.hline.set_layer(1000) self.vline.set_layer(1000) ''' if self._orientation == 'horizontal': self._set_label( _('Click on the dots to make a horizontal reflection.')) elif self._orientation == 'vertical': self._set_label( _('Click on the dots to make a vertical reflection.')) else: self._set_label( _('Click on the dots to make a bilateral reflection.')) ''' def _initiating(self): return self._activity.initiating def new_game(self, orientation='horizontal'): ''' Start a new game. ''' self._orientation = orientation self._all_clear() # Fill in a few dots to start for i in range(int(TEN * SIX / 2)): n = int(uniform(0, TEN * SIX)) if self.roygbiv: self._dots[n].type = int(uniform(2, len(self._colors))) else: self._dots[n].type = int(uniform(0, 4)) self._dots[n].set_shape(self._new_dot( self._colors[self._dots[n].type])) if self.we_are_sharing: _logger.debug('sending a new game') self._parent.send_new_game() def restore_game(self, dot_list, orientation): ''' Restore a game from the Journal or share ''' for i, dot in enumerate(dot_list): self._dots[i].type = dot self._dots[i].set_shape(self._new_dot( self._colors[self._dots[i].type])) self._orientation = orientation self._set_orientation() def save_game(self): ''' Return dot list and orientation for saving to Journal or sharing ''' dot_list = [] for dot in self._dots: dot_list.append(dot.type) return [dot_list, self._orientation] def _set_label(self, string): ''' Set the label in the toolbar or the window frame. ''' self._activity.status.set_label(string) def _button_press_cb(self, win, event): win.grab_focus() x, y = map(int, event.get_coords()) self._press = True spr = self._sprites.find_sprite((x, y)) if spr == None: return True self.last_spr = spr if spr.type is not None: if not self._timer is None: GObject.source_remove(self._timer) self._increment_dot(spr) return True def _button_release_cb(self, win, event): self._press = False if not self._timer is None: GObject.source_remove(self._timer) def _increment_dot(self, spr): spr.type += 1 if self.roygbiv: if spr.type >= len(self._colors): spr.type = 2 else: spr.type %= 4 spr.set_shape(self._new_dot(self._colors[spr.type])) if self.playing_with_robot: self._robot_play(spr) self._test_game_over() if self.we_are_sharing: _logger.debug('sending a click to the share') self._parent.send_dot_click(self._dots.index(spr), spr.type) self._timer = GObject.timeout_add(1000, self._increment_dot, spr) def _mouse_move_cb(self, win, event): """ Drag a tile with the mouse. """ if not self._press: return x, y = map(int, event.get_coords()) spr = self._sprites.find_sprite((x, y)) if spr == self.last_spr: return True if spr is None: return True if spr.type is not None: self.last_spr = spr if not self._timer is None: GObject.source_remove(self._timer) self._increment_dot(spr) def _robot_play(self, dot): ''' Robot reflects dot clicked. ''' x, y = self._dot_to_grid(self._dots.index(dot)) if self._orientation == 'horizontal': x = TEN - x - 1 i = self._grid_to_dot((x, y)) self._dots[i].type = dot.type self._dots[i].set_shape(self._new_dot(self._colors[dot.type])) if self.we_are_sharing: _logger.debug('sending a robot click to the share') self._parent.send_dot_click(i, dot.type) elif self._orientation == 'vertical': y = SIX - y - 1 i = self._grid_to_dot((x, y)) self._dots[i].type = dot.type self._dots[i].set_shape(self._new_dot(self._colors[dot.type])) if self.we_are_sharing: _logger.debug('sending a robot click to the share') self._parent.send_dot_click(i, dot.type) else: x = TEN - x - 1 i = self._grid_to_dot((x, y)) self._dots[i].type = dot.type self._dots[i].set_shape(self._new_dot(self._colors[dot.type])) if self.we_are_sharing: _logger.debug('sending a robot click to the share') self._parent.send_dot_click(i, dot.type) y = SIX - y - 1 i = self._grid_to_dot((x, y)) self._dots[i].type = dot.type self._dots[i].set_shape(self._new_dot(self._colors[dot.type])) if self.we_are_sharing: _logger.debug('sending a robot click to the share') self._parent.send_dot_click(i, dot.type) x = TEN - x - 1 i = self._grid_to_dot((x, y)) self._dots[i].type = dot.type self._dots[i].set_shape(self._new_dot(self._colors[dot.type])) if self.we_are_sharing: _logger.debug('sending a robot click to the share') self._parent.send_dot_click(i, dot.type) def remote_button_press(self, dot, color): ''' Receive a button press from a sharer ''' self._dots[dot].type = color self._dots[dot].set_shape(self._new_dot(self._colors[color])) def set_sharing(self, share=True): _logger.debug('enabling sharing') self.we_are_sharing = share def _smile(self): for dot in self._dots: dot.set_label(':)') def _test_game_over(self): ''' Check to see if game is over ''' if self._orientation == 'horizontal': for y in range(SIX): for x in range(SIX): if self._dots[y * TEN + x].type != \ self._dots[y * TEN + TEN - x - 1].type: self._set_label(_('keep trying')) return False self._set_label(_('good work')) self._smile() return True if self._orientation == 'vertical': for y in range(int(SIX / 2)): for x in range(TEN): if self._dots[y * TEN + x].type != \ self._dots[(SIX - y - 1) * TEN + x].type: self._set_label(_('keep trying')) return False self._set_label(_('good work')) else: for y in range(SIX): for x in range(SIX): if self._dots[y * TEN + x].type != \ self._dots[y * TEN + TEN - x - 1].type: self._set_label(_('keep trying')) return False for y in range(int(SIX / 2)): for x in range(TEN): if self._dots[y * TEN + x].type != \ self._dots[(SIX - y - 1) * TEN + x].type: self._set_label(_('keep trying')) return False self._set_label(_('good work')) self._smile() return True def __draw_cb(self,canvas,cr): self._sprites.redraw_sprites(cr=cr) def _grid_to_dot(self, pos): ''' calculate the dot index from a column and row in the grid ''' return pos[0] + pos[1] * TEN def _dot_to_grid(self, dot): ''' calculate the grid column and row for a dot ''' return [dot % TEN, int(dot / TEN)] def _expose_cb(self, win, event): self.do_expose_event(event) def do_expose_event(self, event): ''' Handle the expose-event by drawing ''' # Restrict Cairo to the exposed area cr = self._canvas.window.cairo_create() cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): Gtk.main_quit() def _new_dot(self, color): ''' generate a dot of a color color ''' self._dot_cache = {} if not color in self._dot_cache: self._stroke = color self._fill = color self._svg_width = self._dot_size self._svg_height = self._dot_size pixbuf = svg_str_to_pixbuf( self._header() + \ self._circle(self._dot_size / 2., self._dot_size / 2., self._dot_size / 2.) + \ self._footer()) surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self._svg_width, self._svg_height) context = cairo.Context(surface) Gdk.cairo_set_source_pixbuf(context, pixbuf, 0, 0) context.rectangle(0, 0, self._svg_width, self._svg_height) context.fill() self._dot_cache[color] = surface return self._dot_cache[color] def _line(self, vertical=True): ''' Generate a center line ''' if vertical: self._svg_width = 3 self._svg_height = self._height return svg_str_to_pixbuf( self._header() + \ self._rect(3, self._height, 0, 0) + \ self._footer()) else: self._svg_width = self._width self._svg_height = 3 return svg_str_to_pixbuf( self._header() + \ self._rect(self._width, 3, 0, 0) + \ self._footer()) def _header(self): return '<svg\n' + 'xmlns:svg="http://www.w3.org/2000/svg"\n' + \ 'xmlns="http://www.w3.org/2000/svg"\n' + \ 'xmlns:xlink="http://www.w3.org/1999/xlink"\n' + \ 'version="1.1"\n' + 'width="' + str(self._svg_width) + '"\n' + \ 'height="' + str(self._svg_height) + '">\n' def _rect(self, w, h, x, y): svg_string = ' <rect\n' svg_string += ' width="%f"\n' % (w) svg_string += ' height="%f"\n' % (h) svg_string += ' rx="%f"\n' % (0) svg_string += ' ry="%f"\n' % (0) svg_string += ' x="%f"\n' % (x) svg_string += ' y="%f"\n' % (y) svg_string += 'style="fill:#000000;stroke:#000000;"/>\n' return svg_string def _circle(self, r, cx, cy): return '<circle style="fill:' + str(self._fill) + ';stroke:' + \ str(self._stroke) + ';" r="' + str(r - 0.5) + '" cx="' + \ str(cx) + '" cy="' + str(cy) + '" />\n' def _footer(self): return '</svg>\n'
class Game(): def __init__(self, canvas, parent=None, path=None, colors=['#A0FFA0', '#FF8080']): self._canvas = canvas self._parent = parent self._parent.show_all() self._path = path self.level = 1 self._colors = ['#FFFFFF'] self._colors.append(colors[0]) self._colors.append(colors[1]) self._colors.append(colors[0]) self._colors.append('#FF0000') self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.connect("draw", self.__draw_cb) self._canvas.connect("button-press-event", self._button_press_cb) self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - GRID_CELL_SIZE if self._width < self._height: self.portrait = True self.grid_height = TEN self.grid.width = SEVEN else: self.portrait = False self.grid_height = SEVEN self.grid_width = TEN self._scale = min(self._width / (self.grid_width * DOT_SIZE * 1.2), self._height / (self.grid_height * DOT_SIZE * 1.2)) self._dot_size = int(DOT_SIZE * self._scale) self._space = int(self._dot_size / 5.) self.we_are_sharing = False # '-1' Workaround for showing 'second 0' self._game_time_seconds = -1 self._game_time = "00:00" self._timeout_id = None # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) self._dots = [] for y in range(self.grid_height): for x in range(self.grid_width): xoffset = int((self._width - self.grid_width * self._dot_size - (self.grid_width - 1) * self._space) / 2.) self._dots.append( Sprite(self._sprites, xoffset + x * (self._dot_size + self._space), y * (self._dot_size + self._space), self._new_dot(self._colors[0]))) self._dots[-1].type = 0 # not set self._dots[-1].set_label_attributes(40) self._all_clear() Gdk.Screen.get_default().connect('size-changed', self._configure_cb) def _configure_cb(self, event): dot_list = self.save_game() self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - GRID_CELL_SIZE if self._width < self._height: self.portrait = True self.grid_height = TEN self.grid_width = SEVEN else: self.portrait = False self.grid_height = SEVEN self.grid_width = TEN i = 0 for y in range(self.grid_height): for x in range(self.grid_width): xoffset = int((self._width - self.grid_width * self._dot_size - (self.grid_width - 1) * self._space) / 2.) self._dots[i].move( (xoffset + x * (self._dot_size + self._space), y * (self._dot_size + self._space))) i += 1 self.restore_game(dot_list) def __draw_cb(self, canvas, cr): self._sprites.redraw_sprites(cr=cr) def _all_clear(self): ''' Things to reinitialize when starting up a new game. ''' for dot in self._dots: if dot.type != 1: dot.type = 1 dot.set_shape(self._new_dot(self._colors[dot.type])) dot.set_label('') self._stop_timer() def new_game(self): ''' Start a new game. ''' self._all_clear() # Fill in a few dots to start for i in range(self.level): n = int(uniform(0, self.grid_width * self.grid_height)) while True: if self._dots[n].type == 1: self._dots[n].type = 2 self._dots[n].set_shape(self._new_dot(self._colors[1])) break else: n = int(uniform(0, self.grid_width * self.grid_height)) if self.we_are_sharing: _logger.debug('sending a new game') self._parent.send_new_game() self._start_timer() def restore_game(self, dot_list): ''' Restore a game from the Journal or share ''' for i, dot in enumerate(dot_list): self._dots[i].type = dot if dot in [4]: # marked by user self._dots[i].set_shape(self._new_dot(self._colors[2])) elif dot in [1, 2]: # unmarked self._dots[i].set_shape(self._new_dot(self._colors[1])) else: # revealed by user self._dots[i].set_shape(self._new_dot(self._colors[0])) for i, dot in enumerate(dot_list): if dot == 0: # label with count count = self._count([2, 4], self._dots[i]) if count > 0: self._dots[i].set_label(count) self._counter() def save_game(self): ''' Return dot list for saving to Journal or sharing ''' dot_list = [] for dot in self._dots: dot_list.append(dot.type) return dot_list def _set_label(self, gametime): ''' Set the label in the toolbar or the window frame. ''' self._parent.status.set_label(_('Level') + ' ' + str(self.level) + ' / ' + gametime) def _neighbors(self, spr): ''' Return the list of surrounding dots ''' neighbors = [] x, y = self._dot_to_grid(self._dots.index(spr)) if x > 0 and y > 0: neighbors.append(self._dots[self._grid_to_dot((x - 1, y - 1))]) if x > 0: neighbors.append(self._dots[self._grid_to_dot((x - 1, y))]) if x > 0 and y < self.grid_height - 1: neighbors.append(self._dots[self._grid_to_dot((x - 1, y + 1))]) if y > 0: neighbors.append(self._dots[self._grid_to_dot((x, y - 1))]) if y < self.grid_height - 1: neighbors.append(self._dots[self._grid_to_dot((x, y + 1))]) if x < self.grid_width - 1 and y > 0: neighbors.append(self._dots[self._grid_to_dot((x + 1, y - 1))]) if x < self.grid_width - 1: neighbors.append(self._dots[self._grid_to_dot((x + 1, y))]) if x < self.grid_width - 1 and y < self.grid_height - 1: neighbors.append(self._dots[self._grid_to_dot((x + 1, y + 1))]) return neighbors def _count(self, count_type, spr): ''' Count the number of surrounding dots of type count_type ''' counter = 0 for dot in self._neighbors(spr): if dot.type in count_type: counter += 1 return counter def _floodfill(self, old_type, spr): if spr.type not in old_type: return spr.type = 0 spr.set_shape(self._new_dot(self._colors[spr.type])) if self.we_are_sharing: _logger.debug('sending a click to the share') self._parent.send_dot_click(self._dots.index(spr), spr.type) counter = self._count([2, 4], spr) if counter > 0: spr.set_label(str(counter)) else: spr.set_label('') for dot in self._neighbors(spr): self._floodfill(old_type, dot) def _button_press_cb(self, win, event): win.grab_focus() x, y = map(int, event.get_coords()) spr = self._sprites.find_sprite((x, y)) if spr is None: return if event.button > 1: # right click if spr.type != 0: self._flip_the_cookie(spr) return True else: if spr.type != 0: red, green, blue, alpha = spr.get_pixel((x, y)) if red > 190 and red < 215: # clicked the cookie self._flip_the_cookie(spr) return True if spr.type in [2, 4]: spr.set_shape(self._new_dot(self._colors[4])) self._frown() return True if spr.type is not None: self._floodfill([1, 3], spr) self._test_game_over() return True def _flip_the_cookie(self, spr): if spr.type in [1, 2]: spr.set_shape(self._new_dot(self._colors[2])) spr.type += 2 else: # elif spr.type in [3, 4]: spr.set_shape(self._new_dot(self._colors[1])) spr.type -= 2 self._test_game_over() def remote_button_press(self, dot, color): ''' Receive a button press from a sharer ''' self._dots[dot].type = color self._dots[dot].set_shape(self._new_dot(self._colors[color])) def set_sharing(self, share=True): _logger.debug('enabling sharing') self.we_are_sharing = share def _counter(self): ''' Display game_time as hours:minutes:seconds. ''' self._game_time_seconds += 1 self._game_time = convert_seconds_to_minutes(self._game_time_seconds) self._set_label(self._game_time) self._timeout_id = GLib.timeout_add(1000, self._counter) def _start_timer(self): ''' Start/reset the timer ''' # '-1' Workaround for showing 'second 0' self._game_time_seconds = -1 self._game_time = "00:00" self._timeout_id = None self._counter() def _stop_timer(self): if self._timeout_id is not None: GLib.source_remove(self._timeout_id) self._timeout_id = None def _smile(self): self._stop_timer() self.game_won = True for dot in self._dots: if dot.type == 0: dot.set_label('☻') self._new_game_alert() def _frown(self): self._stop_timer() self.game_won = False for dot in self._dots: if dot.type == 0: dot.set_label('☹') self._new_game_alert() def _test_game_over(self): ''' Check to see if game is over ''' for dot in self._dots: if dot.type == 1 or dot.type == 2: return False self._parent.all_scores.append(self._game_time) _logger.debug(self._parent.all_scores) self._smile() return True def _grid_to_dot(self, pos): ''' calculate the dot index from a column and row in the grid ''' return pos[0] + pos[1] * self.grid_width def _dot_to_grid(self, dot): ''' calculate the grid column and row for a dot ''' return [dot % self.grid_width, int(dot / self.grid_width)] def _new_game_alert(self): alert = Alert() alert.props.title = _('New game') alert.props.msg = _('Do you want to play a new game?') icon = Icon(icon_name='dialog-cancel') alert.add_button(Gtk.ResponseType.CANCEL, _('Cancel'), icon) icon.show() ok_icon = Icon(icon_name='dialog-ok') alert.add_button(Gtk.ResponseType.OK, _('New game'), ok_icon) ok_icon.show() alert.connect('response', self.__game_alert_response_cb) self._parent.add_alert(alert) alert.show() def __game_alert_response_cb(self, alert, response_id): self._parent.remove_alert(alert) if response_id is Gtk.ResponseType.OK: if self.game_won == False: self.level = 1 elif self.grid_height * self.grid_width - self.level > 1: self.level += 1 self.new_game() def _expose_cb(self, win, event): self.do_expose_event(event) def do_expose_event(self, event): ''' Handle the expose-event by drawing ''' # Restrict Cairo to the exposed area cr = self._canvas.window.cairo_create() cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): Gtk.main_quit() def _new_dot(self, color): ''' generate a dot of a color color ''' self._dot_cache = {} if color not in self._dot_cache: self._stroke = color self._fill = color self._svg_width = self._dot_size self._svg_height = self._dot_size i = self._colors.index(color) if PATHS[i] is False: pixbuf = svg_str_to_pixbuf( self._header() + self._circle(self._dot_size / 2., self._dot_size / 2., self._dot_size / 2.) + self._footer()) else: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._path, PATHS[i]), self._svg_width, self._svg_height) surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self._svg_width, self._svg_height) context = cairo.Context(surface) Gdk.cairo_set_source_pixbuf(context, pixbuf, 0, 0) context.rectangle(0, 0, self._svg_width, self._svg_height) context.fill() self._dot_cache[color] = surface return self._dot_cache[color] def _line(self, vertical=True): ''' Generate a center line ''' if vertical: self._svg_width = 3 self._svg_height = self._height return svg_str_to_pixbuf( self._header() + self._rect(3, self._height, 0, 0) + self._footer()) else: self._svg_width = self._width self._svg_height = 3 return svg_str_to_pixbuf( self._header() + self._rect(self._width, 3, 0, 0) + self._footer()) def _header(self): return '<svg\n' + 'xmlns:svg="http://www.w3.org/2000/svg"\n' + \ 'xmlns="http://www.w3.org/2000/svg"\n' + \ 'xmlns:xlink="http://www.w3.org/1999/xlink"\n' + \ 'version="1.1"\n' + 'width="' + str(self._svg_width) + '"\n' + \ 'height="' + str(self._svg_height) + '">\n' def _rect(self, w, h, x, y): svg_string = ' <rect\n' svg_string += ' width="%f"\n' % (w) svg_string += ' height="%f"\n' % (h) svg_string += ' rx="%f"\n' % (0) svg_string += ' ry="%f"\n' % (0) svg_string += ' x="%f"\n' % (x) svg_string += ' y="%f"\n' % (y) svg_string += 'style="fill:#000000;stroke:#000000;"/>\n' return svg_string def _circle(self, r, cx, cy): return '<circle style="fill:' + str(self._fill) + ';stroke:' + \ str(self._stroke) + ';" r="' + str(r - 0.5) + '" cx="' + \ str(cx) + '" cy="' + str(cy) + '" />\n' def _footer(self): return '</svg>\n'
class Game(): def __init__(self, canvas, parent=None, path=None): self._canvas = canvas self._parent = parent self._parent.show_all() self._path = path self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.connect("draw", self.__draw_cb) self._canvas.connect("button-press-event", self._button_press_cb) self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() self._scale = self._width / 1200. self._target = 0 self._tries = 0 self.level = 0 self._picture_cards = [] self._small_picture_cards = [] self.food_cards = [] self._group_cards = [] self._quantity_cards = [] self._balance_cards = [] self._last_twenty = [] self._background = None # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) self._background = Sprite( self._sprites, 0, 0, GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._path, 'images','background.png'), self._width, self._height)) self._background.set_layer(0) self._background.type = None self._background.hide() self.pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._path, 'images', 'word-box.png'), int(350 * self._scale), int(100 * self._scale)) for i in range(len(FOOD_DATA) / 4): FOOD.append([FOOD_DATA[i * 4 + NAME], FOOD_DATA[i * 4 + CALS], FOOD_DATA[i * 4 + GROUP], FOOD_DATA[i * 4 + IMAGE]]) self.food_cards.append(None) self._picture_cards.append(None) for j in range(6): self._small_picture_cards.append(None) self.allocate_food(0) x = 10 dx, dy = self.food_cards[0].get_dimensions() y = 10 for i in range(len(MYPLATE)): self.word_card_append(self._group_cards, self.pixbuf) self._group_cards[-1].type = i self._group_cards[-1].set_label(MYPLATE[i][0]) self._group_cards[-1].move((x, y)) y += int(dy * 1.25) y = 10 for i in range(len(QUANTITIES)): self.word_card_append(self._quantity_cards, self.pixbuf) self._quantity_cards[-1].type = i self._quantity_cards[-1].set_label(QUANTITIES[i]) self._quantity_cards[-1].move((x, y)) y += int(dy * 1.25) y = 10 for i in range(len(BALANCE)): self.word_card_append(self._balance_cards, self.pixbuf) self._balance_cards[-1].type = i self._balance_cards[-1].set_label(BALANCE[i]) self._balance_cards[-1].move((x, y)) y += int(dy * 1.25) self._smile = Sprite(self._sprites, int(self._width / 4), int(self._height / 4), GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._path, 'images', 'correct.png'), int(self._width / 2), int(self._height / 2))) self._smile.set_label_attributes(36) self._smile.set_margins(10, 0, 10, 0) self._frown = Sprite(self._sprites, int(self._width / 4), int(self._height / 4), GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._path, 'images', 'wrong.png'), int(self._width / 2), int(self._height / 2))) self._frown.set_label_attributes(36) self._frown.set_margins(10, 0, 10, 0) self.build_food_groups() self._all_clear() def allocate_food(self, i): self.picture_append(os.path.join(self._path, 'images', FOOD_DATA[i * 4 + IMAGE]), i) self.small_picture_append(os.path.join(self._path, 'images', FOOD_DATA[i * 4 + IMAGE]), i) self.word_card_append(self.food_cards, self.pixbuf, i) self.food_cards[i].type = i self.food_cards[i].set_label(FOOD_DATA[i * 4 + NAME]) def word_card_append(self, card_list, pixbuf, i=-1): if i == -1: card_list.append(Sprite(self._sprites, 10, 10, pixbuf)) else: card_list[i] = Sprite(self._sprites, 10, 10, pixbuf) card_list[i].set_label_attributes(36) card_list[i].set_margins(10, 0, 10, 0) card_list[i].hide() def picture_append(self, path, i=-1): spr = Sprite( self._sprites, int(self._width / 2.), int(self._height / 4.), GdkPixbuf.Pixbuf.new_from_file_at_size( path, int(self._width / 3.), int(9 * self._width / 12.))) if i == -1: self._picture_cards.append(spr) else: self._picture_cards[i] = spr self._picture_cards[i].type = 'picture' self._picture_cards[i].hide() def small_picture_append(self, path, i=-1): x = int(self._width / 3.) y = int(self._height / 6.) pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( path, int(self._width / 6.), int(3 * self._width / 8.)) for j in range(6): # up to 6 of each card if i == -1: self._small_picture_cards.append(Sprite( self._sprites, x, y, pixbuf)) self._small_picture_cards[-1].type = 'picture' self._small_picture_cards[-1].hide() else: self._small_picture_cards[i * 6 + j] = Sprite( self._sprites, x, y, pixbuf) self._small_picture_cards[i * 6 + j].type = 'picture' self._small_picture_cards[i * 6 + j].hide() x += int(self._width / 6.) if j == 2: x = int(self._width / 3.) y += int(3 * self._width / 16.) def _all_clear(self): ''' Things to reinitialize when starting up a new game. ''' for p in self._picture_cards: if p is not None: p.hide() for p in self._small_picture_cards: if p is not None: p.hide() for i, w in enumerate(self.food_cards): if w is not None: w.set_label_color('black') w.set_label(FOOD[i][NAME]) w.hide() for i, w in enumerate(self._group_cards): w.set_label_color('black') w.set_label(MYPLATE[i][0]) w.hide() for i, w in enumerate(self._quantity_cards): w.set_label_color('black') w.set_label(QUANTITIES[i]) w.hide() for i, w in enumerate(self._balance_cards): w.set_label_color('black') w.set_label(BALANCE[i]) w.hide() self._smile.hide() self._frown.hide() self._background.set_layer(1) def build_food_groups(self): self._my_plate = [[], [], [], []] for i, food in enumerate(FOOD): self._my_plate[MYPLATE[food[GROUP]][QUANT]].append(i) def new_game(self): ''' Start a new game. ''' games = {0: self._name_that_food, 1: self._name_that_food_group, 2: self._compare_calories, 3: self._how_much_to_eat, 4: self._balanced_meal} self._all_clear() games[self.level]() self._frown.set_label('') self._smile.set_label('') self._tries = 0 def _name_that_food(self): ''' Choose food cards and one matching food picture ''' x = 10 y = 10 dx, dy = self.food_cards[0].get_dimensions() # Select some cards word_list = [] for i in range(NCARDS): j = int(uniform(0, len(FOOD))) while j in word_list: j = int(uniform(0, len(FOOD))) word_list.append(j) # Show the word cards from the list for i in word_list: if self.food_cards[i] is None: self.allocate_food(i) self.food_cards[i].set_layer(100) self.food_cards[i].move((x, y)) y += int(dy * 1.25) # Choose a random food image from the list and show it. self._target = self.food_cards[ word_list[int(uniform(0, NCARDS))]].type while self._target in self._last_twenty: self._target = self.food_cards[ word_list[int(uniform(0, NCARDS))]].type self._last_twenty.append(self._target) if len(self._last_twenty) > 20: self._last_twenty.remove(self._last_twenty[0]) self._picture_cards[self._target].set_layer(100) def _name_that_food_group(self): ''' Show group cards and one food picture ''' for i in range(len(MYPLATE)): self._group_cards[i].set_layer(100) # Choose a random food image and show it. self._target = int(uniform(0, len(FOOD))) if self.food_cards[self._target] is None: self.allocate_food(self._target) self._picture_cards[self._target].set_layer(100) def _compare_calories(self): ''' Choose food cards and compare the calories ''' x = 10 y = 10 dx, dy = self.food_cards[0].get_dimensions() # Select some cards word_list = [] for i in range(6): j = int(uniform(0, len(FOOD))) while j in word_list: j = int(uniform(0, len(FOOD))) word_list.append(j) if self.food_cards[j] is None: self.allocate_food(j) # Show the word cards from the list for i in word_list: self.food_cards[i].set_layer(100) self.food_cards[i].move((x, y)) y += int(dy * 1.25) # Show food images self._target = word_list[0] for i in range(5): if FOOD[word_list[i + 1]][CALS] > FOOD[self._target][CALS]: self._target = word_list[i + 1] self._small_picture_cards[word_list[0] * 6].set_layer(100) self._small_picture_cards[word_list[1] * 6 + 1].set_layer(100) self._small_picture_cards[word_list[2] * 6 + 2].set_layer(100) self._small_picture_cards[word_list[3] * 6 + 3].set_layer(100) self._small_picture_cards[word_list[4] * 6 + 4].set_layer(100) self._small_picture_cards[word_list[5] * 6 + 5].set_layer(100) def _how_much_to_eat(self): ''' Show quantity cards and one food picture ''' for i in range(len(QUANTITIES)): self._quantity_cards[i].set_layer(100) # Choose a random image from the list and show it. self._target = int(uniform(0, len(FOOD))) if self.food_cards[self._target] is None: self.allocate_food(self._target) self._picture_cards[self._target].set_layer(100) def _balanced_meal(self): ''' A well-balanced meal ''' for i in range(2): self._balance_cards[i].set_layer(100) # Determine how many foods from each group n = [0, 0, 0, 0] n[0] = int(uniform(0, 2.5)) n[1] = int(uniform(0, 3 - n[0])) n[2] = 3 - n[0] - n[1] n[3] = 6 - n[0] - n[1] - n[2] # Fill a plate with foods from different groups meal = [] for i in range(n[0]): # Sweets j = int(uniform(0, len(self._my_plate[0]))) meal.append(self._my_plate[0][j]) for i in range(n[1]): # Dairy j = int(uniform(0, len(self._my_plate[1]))) meal.append(self._my_plate[1][j]) for i in range(n[2]): # Protein and Fruits j = int(uniform(0, len(self._my_plate[2]))) meal.append(self._my_plate[2][j]) for i in range(n[3]): # Veggies and Grains j = int(uniform(0, len(self._my_plate[3]))) meal.append(self._my_plate[3][j]) if n[0] < 2 and n[1] < 2 and n[2] < n[3]: self._target = 0 # Balanced meal else: self._target = 1 for i in range(6): if self.food_cards[meal[i]] is None: self.allocate_food(meal[i]) # Randomly position small cards self._small_picture_cards[meal[3] * 6].set_layer(100) self._small_picture_cards[meal[4] * 6 + 1].set_layer(100) self._small_picture_cards[meal[1] * 6 + 2].set_layer(100) self._small_picture_cards[meal[2] * 6 + 3].set_layer(100) self._small_picture_cards[meal[5] * 6 + 4].set_layer(100) self._small_picture_cards[meal[0] * 6 + 5].set_layer(100) def _button_press_cb(self, win, event): win.grab_focus() x, y = map(int, event.get_coords()) spr = self._sprites.find_sprite((x, y)) if spr == None: return # We only care about clicks on word cards if type(spr.type) != int: return # Which card was clicked? Set its label to red. spr.set_label_color('red') label = spr.labels[0] spr.set_label(label) if self.level == 0: if spr.type == self._target: self._smile.set_layer(200) self._tries = 3 else: self._frown.set_layer(200) self._tries += 1 if self._tries == 3: self.food_cards[self._target].set_label_color('blue') label = self.food_cards[self._target].labels[0] self.food_cards[self._target].set_label(label) elif self.level == 1: i = FOOD[self._target][GROUP] if spr.type == i: self._smile.set_layer(200) self._tries = 3 else: self._frown.set_layer(200) self._tries += 1 if self._tries == 3: self._group_cards[i].set_label_color('blue') label = self._group_cards[i].labels[0] self._group_cards[i].set_label(label) elif self.level == 2: if spr.type == self._target: self._smile.set_layer(200) self._tries = 3 else: self._frown.set_layer(200) self._tries += 1 if self._tries == 3: self.food_cards[self._target].set_label_color('blue') label = self.food_cards[self._target].labels[0] self.food_cards[self._target].set_label(label) elif self.level == 3: i = MYPLATE[FOOD[self._target][GROUP]][QUANT] if spr.type == i: self._smile.set_layer(200) self._tries = 3 else: self._frown.set_layer(200) self._tries += 1 if self._tries == 3: self._quantity_cards[i].set_label_color('blue') label = self._quantity_cards[i].labels[0] self._quantity_cards[i].set_label(label) elif self.level == 4: if self._target == spr.type: self._smile.set_layer(200) self._tries = 3 else: self._frown.set_layer(200) self._tries += 1 if self._tries == 3: self._balance_cards[self._target].set_label_color('blue') label = self._balance_cards[self._target].labels[0] self._balance_cards[self._target].set_label(label) else: _logger.debug('unknown play level %d' % (self.level)) # Play again if self._tries == 3: GObject.timeout_add(2000, self.new_game) else: GObject.timeout_add(1000, self._reset_game) return True def _reset_game(self): self._frown.hide() if self.level in [0, 2]: for i, w in enumerate(self.food_cards): w.set_label_color('black') w.set_label(FOOD[i][NAME]) elif self.level == 1: for i, w in enumerate(self._group_cards): w.set_label_color('black') w.set_label(MYPLATE[i][0]) elif self.level == 3: for i, w in enumerate(self._quantity_cards): w.set_label_color('black') w.set_label(QUANTITIES[i]) elif self.level == 4: for i, w in enumerate(self._balance_cards): w.set_label_color('black') w.set_label(BALANCE[i]) def __draw_cb(self, canvas, cr): self._sprites.redraw_sprites(cr=cr) def do_expose_event(self, event): ''' Handle the expose-event by drawing ''' # Restrict Cairo to the exposed area cr = self._canvas.window.cairo_create() cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): Gtk.main_quit()
class Game(): def __init__(self, canvas, parent=None, path=None): self._canvas = canvas self._parent = parent self._parent.show_all() self._path = path self._canvas.connect("draw", self.__draw_cb) self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.connect("button-press-event", self._button_press_cb) self._canvas.add_events(Gdk.EventMask.POINTER_MOTION_MASK) self._canvas.connect("motion-notify-event", self._mouse_move_cb) self._canvas.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK) self._canvas.connect('button-release-event', self._button_release_cb) self._canvas.add_events(Gdk.EventMask.KEY_PRESS_MASK) self._canvas.connect('key-press-event', self._keypress_cb) self._canvas.set_can_focus(True) self._canvas.grab_focus() self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() self._scale_x = self._width / 1200.0 self._scale_y = self._height / 900.0 self._first_time = True self._loco_pos = (0, 0) self._loco_dim = (0, 0) self._loco_quadrant = 3 self._drag_pos = [0, 0] self._counter = 0 self._correct = 0 self._timeout_id = None self._pause = 200 self._press = None self._clicked = False self._dead_key = None self._waiting_for_delete = False self._waiting_for_enter = False self._seconds = 0 self._timer_id = None self.level = 0 self.score = 0 # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) self._BG = [ 'background0.jpg', 'background0.jpg', 'background0.jpg', 'background1.jpg', 'background2.jpg', 'background2.jpg', 'background2.jpg' ] self._backgrounds = [] for bg in self._BG: pixbuf = GdkPixbuf.Pixbuf.new_from_file( os.path.join(self._path, 'images', bg)) pixbuf = pixbuf.scale_simple(self._width, self._height, GdkPixbuf.InterpType.BILINEAR) self._backgrounds.append(Sprite(self._sprites, 0, 0, pixbuf)) self._backgrounds[-1].type = 'background' self._backgrounds[-1].hide() self._panel = Sprite( self._sprites, int(400 * self._scale_x), int(400 * self._scale_y), GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._path, 'images', 'ventana.png'), int(720 * self._scale_x), int(370 * self._scale_y))) self._panel.type = 'panel' self._panel.set_label(LABELS[0]) self._panel.set_label_attributes(20) self._panel.hide() self._LOCOS = glob.glob(os.path.join(self._path, 'images', 'loco*.png')) self._loco_cards = [] for loco in self._LOCOS: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( loco, int(150 * self._scale_x), int(208 * self._scale_y)) self._loco_cards.append(Sprite(self._sprites, 0, 0, pixbuf)) self._loco_cards[-1].type = 'loco' self._loco_dim = (int(150 * self._scale_x), int(208 * self._scale_y)) self._MEN = glob.glob(os.path.join(self._path, 'images', 'man*.png')) self._man_cards = [] for loco in self._MEN: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( loco, int(150 * self._scale_x), int(208 * self._scale_y)) self._man_cards.append(Sprite(self._sprites, 0, 0, pixbuf)) self._man_cards[-1].type = 'loco' self._TAUNTS = glob.glob( os.path.join(self._path, 'images', 'taunt*.png')) self._taunt_cards = [] for loco in self._TAUNTS: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( loco, int(150 * self._scale_x), int(208 * self._scale_y)) self._taunt_cards.append(Sprite(self._sprites, 0, 0, pixbuf)) self._taunt_cards[-1].type = 'loco' self._GHOSTS = glob.glob( os.path.join(self._path, 'images', 'ghost*.png')) self._ghost_cards = [] for loco in self._GHOSTS: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( loco, int(150 * self._scale_x), int(208 * self._scale_y)) self._ghost_cards.append(Sprite(self._sprites, 0, 0, pixbuf)) self._ghost_cards[-1].type = 'loco' self._sticky_cards = [] self._loco_pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( self._LOCOS[0], int(150 * self._scale_x), int(208 * self._scale_y)) self._man_pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( self._MEN[0], int(150 * self._scale_x), int(208 * self._scale_y)) self._ghost_pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( self._GHOSTS[0], int(150 * self._scale_x), int(208 * self._scale_y)) for i in range(len(MSGS[1])): # Check re i18n self._sticky_cards.append( Sprite(self._sprites, 0, 0, self._loco_pixbuf)) self._sticky_cards[-1].type = 'loco' self._sticky_cards[-1].set_label_attributes(24, vert_align='bottom') self._all_clear() def _time_increment(self): ''' Track seconds since start_time. ''' self._seconds = int(GLib.get_current_time() - self._start_time) self.timer_id = GLib.timeout_add(1000, self._time_increment) def _timer_reset(self): ''' Reset the timer for each level ''' self._start_time = GLib.get_current_time() if self._timer_id is not None: GLib.source_remove(self._timer_id) self._timer_id = None self.score += self._seconds self._time_increment() def _all_clear(self): ''' Things to reinitialize when starting up a new game. ''' for p in self._loco_cards: p.hide() for p in self._man_cards: p.hide() for p in self._taunt_cards: p.hide() for p in self._ghost_cards: p.hide() for p in self._sticky_cards: p.set_shape(self._loco_pixbuf) p.set_label('') p.set_label_color('white') p.hide() self._backgrounds[self.level].set_layer(BG_LAYER) def _show_time(self): self.level = 0 self._all_clear() x = int(self._width / 4.) y = int(self._height / 8.) for i in range(len(str(self.score))): self._sticky_cards[i].move((x, y)) self._sticky_cards[i].set_layer(LOCO_LAYER) self._sticky_cards[i].set_label(str(self.score)[i]) x += int(self._loco_dim[0] / 2.) self.score = 0 self._parent.fullscreen() aplay.play(os.path.join(self._path, 'sounds', 'sonar.ogg')) GLib.timeout_add(5000, self.new_game, True) def new_game(self, first_time): ''' Start a new game at the current level. ''' self._first_time = first_time self._clicked = False # It may be time to advance to the next level. if (self.level == 6 and self._counter == len(MSGS)) or \ self._counter > 4: self._first_time = True self.level += 1 self._counter = 0 self._correct = 0 self._pause = 200 if self.level == len(self._backgrounds): self._show_time() return self._all_clear() if self._first_time: # Every game starts by putting up a panel with instructions # The panel disappears on mouse movement self._panel.set_label(LABELS[self.level]) self._panel.set_layer(PANEL_LAYER) aplay.play(os.path.join(self._path, 'sounds', 'drip.ogg')) self._timer_reset() if self.level == 0: # Choose a random location for the Loco self._loco_quadrant += int(uniform(1, 4)) self._loco_quadrant %= 4 x, y = self._quad_to_xy(self._loco_quadrant) aplay.play(os.path.join(self._path, 'sounds', 'bark.ogg')) self._loco_cards[0].move((x, y)) self._loco_pos = (x, y) elif self.level == 1: aplay.play(os.path.join(self._path, 'sounds', 'glass.ogg')) elif self.level == 2: aplay.play(os.path.join(self._path, 'sounds', 'glass.ogg')) # Place some Locos on the canvas for i in range(self._counter + 1): self._loco_quadrant += int(uniform(1, 4)) self._loco_quadrant %= 4 x, y = self._quad_to_xy(self._loco_quadrant) self._sticky_cards[i].move((x, y)) self._sticky_cards[i].type = 'loco' self._sticky_cards[i].set_layer(LOCO_LAYER) elif self.level == 3: aplay.play(os.path.join(self._path, 'sounds', 'bark.ogg')) # Place some Locos on the left-side of the canvas for i in range(self._counter + 1): self._loco_quadrant = int(uniform(2, 4)) x, y = self._quad_to_xy(self._loco_quadrant) self._sticky_cards[i].move((x, y)) self._sticky_cards[i].type = 'loco' self._sticky_cards[i].set_layer(LOCO_LAYER) elif self.level == 4: # Place some Locos on the canvas with letters as labels # Just lowercase for i in range(self._counter + 1): self._loco_quadrant = int(uniform(0, 4)) x, y = self._quad_to_xy(self._loco_quadrant) self._sticky_cards[i].move((x, y)) self._sticky_cards[i].type = 'loco' self._sticky_cards[i].set_layer(LOCO_LAYER) self._sticky_cards[i].set_label(ALPHABETLC[int( uniform(0, len(ALPHABETLC)))]) elif self.level == 5: # Place some Locos on the canvas with letters as labels # Uppercase for i in range(self._counter + 1): self._loco_quadrant = int(uniform(0, 4)) x, y = self._quad_to_xy(self._loco_quadrant) self._sticky_cards[i].move((x, y)) self._sticky_cards[i].type = 'loco' self._sticky_cards[i].set_layer(LOCO_LAYER) self._sticky_cards[i].set_label(ALPHABETUC[int( uniform(0, len(ALPHABETUC)))]) elif self.level == 6: x = 0 y = 0 c = 0 for i in range(len(MSGS[self._counter])): if MSGS[self._counter][i] == ' ': y += self._loco_dim[1] x = 0 else: self._sticky_cards[c].move((x, y)) self._sticky_cards[c].type = i self._sticky_cards[c].set_layer(LOCO_LAYER) self._sticky_cards[c].set_label(MSGS[self._counter][i]) c += 1 x += int(self._loco_dim[0] / 2.) if self.level in [0, 1]: self._loco_quadrant += int(uniform(1, 4)) self._loco_quadrant %= 4 x, y = self._quad_to_xy(self._loco_quadrant) if self.level == 0: self._move_loco(x, y, 0) else: self._taunt(x, y, 0) def _quad_to_xy(self, q): x = int(max(0, (self._width / 2.) * uniform(0, 1) - self._loco_dim[0])) if q in [0, 1]: x += int(self._width / 2.) y = int(max(0, (self._height / 2.) * uniform(0, 1) - self._loco_dim[1])) if q in [1, 2]: y += int(self._height / 2.) return x, y def _taunt(self, x, y, i): n = len(self._taunt_cards) self._taunt_cards[(i + 1) % n].hide() if self._clicked: self._timeout_id = None return True else: self._taunt_cards[i % n].move((x, y)) self._taunt_cards[i % n].set_layer(LOCO_LAYER) self._timeout_id = GLib.timeout_add(200, self._taunt, x, y, i + 1) def _move_loco(self, x, y, i): j = (i + 1) % len(self._loco_cards) cx, cy = self._loco_cards[i].get_xy() dx = cx - x dy = cy - y if dx * dx + dy * dy < 100: self._loco_cards[j].move((x, y)) self._loco_pos = (x, y) self._loco_cards[j].hide() self._loco_cards[i].hide() self._man_cards[0].move((x, y)) self._man_cards[0].set_layer(LOCO_LAYER) self._timeout_id = None if self._pause > 50: self._pause -= 10 return True else: if dx > 0: cx -= 5 elif dx < 0: cx += 5 if dy > 0: cy -= 5 elif dy < 0: cy += 5 self._loco_cards[j].move((cx, cy)) self._loco_pos = (cx, cy) self._loco_cards[j].set_layer(LOCO_LAYER) self._loco_cards[i].hide() self._timeout_id = GLib.timeout_add(self._pause, self._move_loco, x, y, j) def _keypress_cb(self, area, event): ''' Keypress ''' # Games 4, 5, and 6 use the keyboard if self.level not in [4, 5, 6]: return True k = Gdk.keyval_name(event.keyval) if self._waiting_for_enter: if k == 'Return': self._waiting_for_enter = False self._panel.hide() self._counter += 1 self._correct = 0 GLib.timeout_add(1000, self.new_game, False) return if k in NOISE_KEYS or k in WHITE_SPACE: return True if self.level == 6 and self._waiting_for_delete: if k in ['BackSpace', 'Delete']: self._waiting_for_delete = False self._sticky_cards[self._correct].set_label_color('white') self._sticky_cards[self._correct].set_label(MSGS[ self._counter][self._sticky_cards[self._correct].type]) self._panel.hide() self._panel.set_label_color('black') return if k[0:5] == 'dead_': self._dead_key = k[5:] return if self.level == 6: n = len(MSGS[self._counter]) else: n = self._counter + 1 if self.level == 6: i = self._correct if self._dead_key is not None: k = DEAD_DICTS[DEAD_KEYS.index(self._dead_key)][k] self._dead_key = None elif k in PUNCTUATION: k = PUNCTUATION[k] elif k in SPECIAL: k = SPECIAL[k] elif len(k) > 1: return True if self._sticky_cards[i].labels[0] == k: self._sticky_cards[i].set_label_color('blue') self._sticky_cards[i].set_label(k) self._correct += 1 else: self._sticky_cards[i].set_label_color('red') self._sticky_cards[i].set_label(k) self._panel.set_label_color('red') self._panel.set_label(ALERTS[1]) self._panel.set_layer(PANEL_LAYER) self._waiting_for_delete = True aplay.play(os.path.join(self._path, 'sounds', 'glass.ogg')) else: for i in range(n): if self._sticky_cards[i].labels[0] == k: self._sticky_cards[i].set_label('') self._sticky_cards[i].hide() break # Test for end condition if self.level == 6 and \ self._correct == len(MSGS[self._counter]) - \ MSGS[self._counter].count(' '): c = 0 for i in range(len(MSGS[self._counter])): if MSGS[self._counter][i] == ' ': continue elif MSGS[self._counter][i] != self._sticky_cards[c].labels[0]: return True c += 1 self._panel.set_label(ALERTS[0]) self._panel.set_layer(PANEL_LAYER) self._waiting_for_enter = True aplay.play(os.path.join(self._path, 'sounds', 'drip.ogg')) return else: for i in range(n): if len(self._sticky_cards[i].labels[0]) > 0: return True self._counter += 1 self._correct = 0 GLib.timeout_add(1000, self.new_game, False) def _mouse_move_cb(self, win, event): ''' Move the mouse. ''' # Games 0, 3, 4, and 5 use move events x, y = list(map(int, event.get_coords())) if self._seconds > 1: self._panel.hide() if not self._clicked and self.level == 0: # For Game 0, see if the mouse is on the Loco dx = x - self._loco_pos[0] - self._loco_dim[0] / 2. dy = y - self._loco_pos[1] - self._loco_dim[1] / 2. if dx * dx + dy * dy < 200: self._clicked = True if self._timeout_id is not None: GLib.source_remove(self._timeout_id) # Play again self._all_clear() self._man_cards[0].move((x - int(self._loco_dim[0] / 2.), y - int(self._loco_dim[1] / 2.))) self._man_cards[0].set_layer(LOCO_LAYER) self._correct += 1 self._counter += 1 GLib.timeout_add(1000, self.new_game, False) elif self.level in [4, 5]: # For Game 4 and 5, we allow dragging if self._press is None: self._drag_pos = [0, 0] return True dx = x - self._drag_pos[0] dy = y - self._drag_pos[1] self._press.move_relative((dx, dy)) self._drag_pos = [x, y] elif self.level == 3: # For Game 3, we are dragging if self._press is None: self._drag_pos = [0, 0] return True dx = x - self._drag_pos[0] dy = y - self._drag_pos[1] self._press.move_relative((dx, dy)) self._drag_pos = [x, y] if x > self._width / 2.: self._press.set_shape(self._man_pixbuf) if self._press.type == 'loco': self._correct += 1 self._press.type = 'man' return True def _button_release_cb(self, win, event): # Game 3 uses release if self.level == 3: # Move to release if self._correct == self._counter + 1: self._counter += 1 self._correct = 0 GLib.timeout_add(2000, self.new_game, False) self._press = None self._drag_pos = [0, 0] return True def _button_press_cb(self, win, event): self._press = None x, y = list(map(int, event.get_coords())) if self.level == 0: return spr = self._sprites.find_sprite((x, y)) if spr is None: return if spr.type != 'loco': return if self.level < 2 and self._timeout_id is None: return if self._clicked: return # Games 1, 2, and 3 involve clicks; Games 4 and 5 allow click to drag if self.level == 1: self._all_clear() self._man_cards[0].move((x - int(self._loco_dim[0] / 2.), y - int(self._loco_dim[1] / 2.))) self._man_cards[0].set_layer(LOCO_LAYER) self._clicked = True self._counter += 1 self._correct += 1 if self._timeout_id is not None: GLib.source_remove(self._timeout_id) GLib.timeout_add(2000, self.new_game, False) elif self.level == 2: spr.set_shape(self._ghost_pixbuf) spr.type = 'ghost' if self._correct == self._counter: self._counter += 1 self._correct = 0 GLib.timeout_add(2000, self.new_game, False) else: self._correct += 1 elif self.level in [3, 4, 5]: # In Games 4 and 5, dragging is used to remove overlaps self._press = spr self._drag_pos = [x, y] return True def __draw_cb(self, canvas, cr): self._sprites.redraw_sprites(cr=cr) def do_expose_event(self, event): ''' Handle the expose-event by drawing ''' # Restrict Cairo to the exposed area cr = self._canvas.window.cairo_create() cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): Gtk.main_quit()
class Game(): ''' OLPC XO man color changer designed in memory of Nat Jacobson ''' def __init__(self, canvas, parent=None, mycolors=['#A0FFA0', '#FF8080']): self._activity = parent self.colors = [mycolors[0]] self.colors.append(mycolors[1]) self._canvas = canvas if parent is not None: parent.show_all() self._parent = parent self._canvas.connect("draw", self.__draw_cb) self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.connect("button-press-event", self._button_press_cb) self._canvas.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK) self._canvas.connect('button-release-event', self._button_release_cb) self._canvas.add_events(Gdk.EventMask.POINTER_MOTION_MASK) self._canvas.connect("motion-notify-event", self._mouse_move_cb) self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - GRID_CELL_SIZE self._scale = self._width / 1200. self.press = None self.dragpos = [0, 0] self.startpos = [0, 0] self._dot_cache = {} self._xo_cache = {} self._radius = 22.5 self._stroke_width = 9.5 # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) self._sprites.set_delay(True) self._dots = [] self._xo_man = None self._generate_bg('#FFF') # First dot, starting angle self._cxy = [self._width / 2, self._height / 2] self._xy = [self._width / 2 + 120 * self._scale, self._height / 2 - self._radius * self._scale] self._angle = 0 self._dot_size_plus = self._radius * 3 * self._scale self._min = -self._dot_size_plus / 3 self._max = self._height - (self._dot_size_plus / 2.2) self._zones = [] self._calc_zones() self._generate_spiral() self._sprites.draw_all() def _calc_zones(self): for color in colors: rgb1 = _from_hex(color[0]) rgb2 = _from_hex(color[1]) dv = _contrast(rgb1, rgb2) dh = _delta_hue(rgb1, rgb2) self._zones.append(_zone(dv, dh)) def _calc_next_dot_position(self): ''' calculate spiral coordinates ''' dx = self._xy[0] - self._cxy[0] dy = self._xy[1] - self._cxy[1] r = sqrt(dx * dx + dy * dy) c = 2 * r * pi a = atan2(dy, dx) da = (self._dot_size_plus / c) * 2 * pi a += da r += self._dot_size_plus / (c / self._dot_size_plus) self._xy[0] = r * cos(a) + self._cxy[0] self._xy[1] = r * sin(a) + self._cxy[1] if self._xy[1] < self._min or self._xy[1] > self._max: self._calc_next_dot_position() def _generate_spiral(self): ''' Make a new set of dots for a sprial ''' for z in range(4): for i in range(len(colors)): if self._zones[i] == z: self._dots.append( Sprite(self._sprites, self._xy[0], self._xy[1], self._new_dot(colors[i]))) self._dots[-1].type = i self._calc_next_dot_position() if self._xo_man is None: x = 510 * self._scale y = 280 * self._scale self._xo_man = Sprite(self._sprites, x, y, self._new_xo_man(self.colors)) self._xo_man.type = None def move_dot(self, i, x, y): self._dots[i].move((x, y)) def get_dot_xy(self, i): return self._dots[i].get_xy() def move_xo_man(self, x, y): self._xo_man.move((x, y)) def get_xo_man_xy(self): return self._xo_man.get_xy() def rotate(self): x, y = self._dots[0].get_xy() for i in range(len(colors) - 1): self._dots[i].move(self._dots[i + 1].get_xy()) self._dots[-1].move((x, y)) def _generate_bg(self, color): ''' a background color ''' self._bg = Sprite(self._sprites, 0, 0, self._new_background(color)) self._bg.set_layer(0) self._bg.type = None def adj_background(self, color): ''' Change background ''' self._bg.set_image(self._new_background(color)) self._bg.set_layer(0) def _button_press_cb(self, win, event): win.grab_focus() x, y = map(int, event.get_coords()) self.dragpos = [x, y] spr = self._sprites.find_sprite((x, y)) if spr == None or spr == self._bg: return self.startpos = spr.get_xy() self.press = spr def _mouse_move_cb(self, win, event): """ Drag a rule with the mouse. """ if self.press is None: self.dragpos = [0, 0] return True win.grab_focus() x, y = map(int, event.get_coords()) dx = x - self.dragpos[0] dy = y - self.dragpos[1] self.press.move_relative((dx, dy)) self.dragpos = [x, y] def _button_release_cb(self, win, event): if self.press == None: return True if _distance(self.press.get_xy(), self.startpos) < 20: if type(self.press.type) == int: self.i = self.press.type self._new_surface() self.press.move(self.startpos) self.press = None def _new_surface(self): self.colors[0] = colors[self.i][0] self.colors[1] = colors[self.i][1] self._xo_man.set_image(self._new_xo_man(colors[self.i])) self._xo_man.set_layer(100) def __draw_cb(self, canvas, cr): self._sprites.redraw_sprites(cr=cr) def do_expose_event(self, event): ''' Handle the expose-event by drawing ''' # Restrict Cairo to the exposed area cr = self._canvas.window.cairo_create() cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): Gtk.main_quit() def _new_dot(self, color): ''' generate a dot of a color color ''' if True: # not color in self._dot_cache: self._stroke = color[0] self._fill = color[1] self._svg_width = int(60 * self._scale) self._svg_height = int(60 * self._scale) pixbuf = svg_str_to_pixbuf( self._header() + \ '<circle cx="%f" cy="%f" r="%f" stroke="%s" fill="%s" \ stroke-width="%f" visibility="visible" />' % ( 30 * self._scale, 30 * self._scale, self._radius * self._scale, self._stroke, self._fill, self._stroke_width * self._scale) + \ self._footer()) surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self._svg_width, self._svg_height) context = cairo.Context(surface) Gdk.cairo_set_source_pixbuf(context, pixbuf, 0, 0) context.rectangle(0, 0, self._svg_width, self._svg_height) context.fill() # self._dot_cache[color] = surface return surface # self._dot_cache[color] def _new_background(self, color): ''' Background color ''' self._svg_width = int(self._width) self._svg_height = int(self._height) string = \ self._header() + \ '<rect width="%f" height="%f" x="%f" \ y="%f" fill="%s" stroke="none" visibility="visible" />' % ( self._width, self._height, 0, 0, color) + \ self._footer() pixbuf = svg_str_to_pixbuf(string) surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self._svg_width, self._svg_height) context = cairo.Context(surface) Gdk.cairo_set_source_pixbuf(context, pixbuf, 0, 0) context.rectangle(0, 0, self._svg_width, self._svg_height) context.fill() return surface def _new_xo_man(self, color): ''' generate a xo-man of a color color ''' if True: # not color in self._xo_cache: self._stroke = color[0] self._fill = color[1] self._svg_width = int(240. * self._scale) self._svg_height = int(260. * self._scale) string = \ self._header() + \ '<g>' + \ '<g id="XO">' + \ '<path id="Line1" d="M%f,%f C%f,%f %f,%f %f,%f" stroke="%s" \ stroke-width="%f" stroke-linecap="round" fill="none" visibility="visible" />' \ % ( 165.5 * self._scale, 97 * self._scale, 120 * self._scale, 140.5 * self._scale, 120 * self._scale, 140.5 * self._scale, 74.5 * self._scale, 188 * self._scale, self._stroke, 37 * self._scale) + \ '<path id="Line2" d="M%f,%f C%f,%f %f,%f %f,%f" stroke="%s" \ stroke-width="%f" stroke-linecap="round" fill="none" visibility="visible" />' \ % ( 165.5 * self._scale, 188 * self._scale, 120 * self._scale, 140.5 * self._scale, 120 * self._scale, 140.5 * self._scale, 74.5 * self._scale, 97 * self._scale, self._stroke, 37 * self._scale) + \ '<path id="Fill1" d="M%f,%f C%f,%f %f,%f %f,%f" stroke="%s" \ stroke-width="%f" stroke-linecap="round" fill="none" visibility="visible" />' \ % ( 165.5 * self._scale, 97 * self._scale, 120 * self._scale, 140.5 * self._scale, 120 * self._scale, 140.5 * self._scale, 74.5 * self._scale, 188 * self._scale, self._fill, 17 * self._scale) + \ '<path id="Fill2" d="M%f,%f C%f,%f %f,%f %f,%f" stroke="%s" \ stroke-width="%f" stroke-linecap="round" fill="none" visibility="visible" />' \ % ( 165.5 * self._scale, 188 * self._scale, 120 * self._scale, 140.5 * self._scale, 120 * self._scale, 140.5 * self._scale, 74.5 * self._scale, 97 * self._scale, self._fill, 17 * self._scale) + \ '<circle id="Circle" cx="%f" cy="%f" r="%f" \ fill="%s" stroke="%s" stroke-width="%f" visibility="visible" />' % ( 120 * self._scale, 61.5 * self._scale, 27.5 * self._scale, self._fill, self._stroke, 11 * self._scale) + \ '</g></g>' + \ self._footer() pixbuf = svg_str_to_pixbuf(string) surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self._svg_width, self._svg_height) context = cairo.Context(surface) Gdk.cairo_set_source_pixbuf(context, pixbuf, 0, 0) context.rectangle(0, 0, self._svg_width, self._svg_height) context.fill() # self._xo_cache[color] = surface return surface # self._xo_cache[color] def _header(self): return '<svg\n' + 'xmlns:svg="http:#www.w3.org/2000/svg"\n' + \ 'xmlns="http://www.w3.org/2000/svg"\n' + \ 'xmlns:xlink="http://www.w3.org/1999/xlink"\n' + \ 'version="1.1"\n' + 'width="' + str(self._svg_width) + '"\n' + \ 'height="' + str(self._svg_height) + '">\n' def _footer(self): return '</svg>\n'
class Game(): def __init__(self, canvas, parent=None, colors=['#A0FFA0', '#FF8080']): self._activity = parent self._colors = [colors[0]] self._colors.append(colors[1]) self._colors.append('#FFFFFF') self._colors.append('#000000') self._colors.append('#FF0000') self._colors.append('#FF8080') self._colors.append('#FFa0a0') self._colors.append('#FFc0c0') self._colors.append('#FFFF00') self._colors.append('#FFFF80') self._colors.append('#FFFFa0') self._colors.append('#FFFFe0') self._colors.append('#0000FF') self._colors.append('#8080FF') self._colors.append('#80a0FF') self._colors.append('#c0c0FF') self._canvas = canvas if parent is not None: parent.show_all() self._parent = parent self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK) self._canvas.connect("draw", self.__draw_cb) self._canvas.connect("button-press-event", self._button_press_cb) self._canvas.connect("button-release-event", self._button_release_cb) self._global_scale = 1 self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - (GRID_CELL_SIZE * 1.5) self._scale = self._width / (10 * DOT_SIZE * 1.2) self._dot_size = int(DOT_SIZE * self._scale) self._space = int(self._dot_size / 5.) self._press = False self._release = None self._rubbing = False self._tapped = None self._pausing = False self._count = 0 self._targets = None # click target self._shake = None # accelerometer target self._next = None self.last_spr = None self._timer = None self.roygbiv = False # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) self._svg_width = self._width self._svg_height = self._height self._lightbg = Sprite(self._sprites, 0, 0, svg_str_to_pixbuf( self._header() + \ self._rect(self._width, self._height, 0, 0) + \ self._footer())) self._lightbg.set_label_attributes(24) self._lightbg._vert_align = ["bottom"] self._darkbg = Sprite(self._sprites, 0, 0, svg_str_to_pixbuf( self._header() + \ self._rect(self._width, self._height, 0, 0, color='#000000') + \ self._footer())) self._darkbg.set_label_attributes(24) self._darkbg._vert_align = ["bottom"] self._darkbg.set_label_color('yellow') self._darkbg.set_layer(0) self._dots = [] for y in range(FIVE): for x in range(NINE): xoffset = int((self._width - NINE * self._dot_size - \ (NINE - 1) * self._space) / 2.) self._dots.append( Sprite(self._sprites, xoffset + x * (self._dot_size + self._space), y * (self._dot_size + self._space), self._new_dot(self._colors[WHITE]))) self._dots[-1].type = DOT self._dots[-1].set_label_attributes(40) n = FIVE / 2. # and initialize a few variables we'll need. self._yellow_dot() def _clear_pause(self): self._pausing = False if self._rubbing and self._release is not None: if self._next is not None: self._next() def _yellow_dot(self): for y in range(FIVE): for x in range(NINE): xoffset = int((self._width - NINE * self._dot_size - \ (NINE - 1) * self._space) / 2.) self._dots[x + y * NINE].move( (xoffset + x * (self._dot_size + self._space), y * (self._dot_size + self._space))) self._dots[x + y * NINE].set_shape( self._new_dot(self._colors[WHITE])) self._dots[x + y * NINE].type = DOT self._dots[x + y * NINE].set_layer(100) self._lightbg.set_label(_('Tap on the yellow dot.')) self._targets = [int(uniform(0, NINE * FIVE))] self._next = self._yellow_dot_too self._dots[self._targets[0]].set_shape( self._new_dot(self._colors[YELLOW])) self._dots[self._targets[0]].type = YELLOW self._rubbing = False self._shake = None def _yellow_dot_too(self, append=True): ''' Things to reinitialize when starting up a new game. ''' if append: i = self._targets[0] while i in self._targets: i = int(uniform(0, NINE * FIVE)) self._targets.append(i) self._lightbg.set_label( _('Well done! \ Now tap on the other yellow dot.')) self._next = self._yellow_dots_three self._dots[self._targets[1]].set_shape( self._new_dot(self._colors[YELLOW])) self._dots[self._targets[1]].type = YELLOW self._rubbing = False def _yellow_dots_three(self): ''' Things to reinitialize when starting up a new game. ''' if self._release == self._targets[0]: self._yellow_dot_too(append=False) self._lightbg.set_label(_('The other yellow dot!')) return i = self._targets[0] while i in self._targets: i = int(uniform(0, NINE * FIVE)) self._targets.append(i) self._lightbg.set_label(_('Great! Now rub on one of the yellow dots.')) self._next = self._red_dot self._dots[self._targets[2]].set_shape( self._new_dot(self._colors[YELLOW])) self._dots[self._targets[2]].type = YELLOW self._rubbing = True def _red_dot(self): if self._release is None: return self._lightbg.set_label( _('Good job! \ Now rub on another one of the yellow dots.')) self._next = self._blue_dot self._dots[self._release].set_shape(self._new_dot(self._colors[RED])) self._dots[self._release].type = RED self._rubbing = True def _blue_dot(self): if self._release is None: return if self._dots[self._release].type != YELLOW: return self._lightbg.set_label( _('Now gently tap on the yellow dot five times.')) self._next = self._yellow_tap self._dots[self._release].set_shape(self._new_dot(self._colors[BLUE])) self._dots[self._release].type = BLUE self._rubbing = False self._count = 0 def _yellow_tap(self): if self._dots[self._release].type != YELLOW: if self._count == 0: self._lightbg.set_label( _('Now gently tap on the yellow dot five times.')) else: self._lightbg.set_label(_('Tap on a yellow dot.')) return self._count += 1 if self._count > 4: self._count = 0 self._next = self._red_tap self._lightbg.set_label( _('Now gently tap on the red dot five times.')) else: self._lightbg.set_label(_('Keep tapping.')) i = self._targets[0] while i in self._targets: i = int(uniform(0, NINE * FIVE)) self._targets.append(i) self._dots[i].set_shape(self._new_dot(self._colors[YELLOW])) self._dots[i].type = YELLOW def _red_tap(self): if self._dots[self._release].type != RED: if self._count == 0: self._lightbg.set_label( _('Now gently tap on the red dot five times.')) else: self._lightbg.set_label(_('Tap on a red dot.')) return self._count += 1 if self._count > 4: self._count = 0 self._next = self._blue_tap self._lightbg.set_label( _('Now gently tap on the blue dot five times.')) else: self._lightbg.set_label(_('Keep tapping.')) i = self._targets[0] while i in self._targets: i = int(uniform(0, NINE * FIVE)) self._targets.append(i) self._dots[i].set_shape(self._new_dot(self._colors[RED])) self._dots[i].type = RED def _blue_tap(self): if self._dots[self._release].type != BLUE: if self._count == 0: self._lightbg.set_label( _('Now gently tap on the blue dot five times.')) else: self._lightbg.set_label(_('Tap on a blue dot.')) return self._count += 1 if self._count > 4: self._count = 0 self._next = self._shake_it self._lightbg.set_label('') # Since we don't end up in the button press GObject.timeout_add(500, self._next) else: self._lightbg.set_label(_('Keep tapping.')) i = self._targets[0] while i in self._targets: i = int(uniform(0, NINE * FIVE)) self._targets.append(i) self._dots[i].set_shape(self._new_dot(self._colors[BLUE])) self._dots[i].type = BLUE def _shake_it(self): self._lightbg.set_label(_('OK. Now, shake the computer!!')) for dot in self._dots: if dot.type in [RED, YELLOW, BLUE]: dot.set_layer(200) self._next = self._shake_it_more self._shake = 'random' self._pausing = True GObject.timeout_add(5000, self._clear_pause) GObject.timeout_add(100, read_accelerometer, self) def _shake_it_more(self): self._lightbg.set_label(_('Shake it harder!!')) self._next = self._turn_left self._shake = 'random2' self._pausing = True GObject.timeout_add(5000, self._clear_pause) def _turn_left(self): self._lightbg.set_label( _('See what happens if you turn it to the left.')) self._next = self._turn_right self._shake = 'left' self._pausing = True def _turn_right(self): self._lightbg.set_label(_('Now turn it to the right.')) self._next = self._align self._pausing = True self._shake = 'right' def _align(self): self._lightbg.set_label(_('Shake it some more.')) self._next = self._tap_six self._pausing = True self._shake = 'align' def _tap_six(self): self._shake = None self._lightbg.set_label(_('OK. Now press each of the yellow dots.')) if self._tapped == None: self._tapped = [] if self._dots[self._release].type != YELLOW: self._lightbg.set_label(_('Press the yellow dots.')) return else: if not self._release in self._tapped: self._tapped.append(self._release) self._dots[self._release].set_label(':)') if len(self._tapped) == 6: self._darkbg.set_layer(100) self._lightbg.set_layer(0) for dot in self._dots: if dot.type != YELLOW: dot.set_layer(0) self._darkbg.set_label(_('Press all of the yellow dots again!')) self._tapped = None self._next = self._tap_six_too def _tap_six_too(self): self._shake = None if self._tapped == None: self._tapped = [] if self._dots[self._release].type != YELLOW: return else: if not self._release in self._tapped: self._tapped.append(self._release) self._dots[self._release].set_label('') if len(self._tapped) == 6: self._lightbg.set_layer(100) self._darkbg.set_layer(0) for dot in self._dots: if dot.type in [RED, BLUE]: dot.set_layer(100) pos1 = self._dots[self._targets[1]].get_xy() pos2 = self._dots[self._targets[2]].get_xy() self._dots[self._targets[1]].move(pos2) self._dots[self._targets[2]].move(pos1) self._lightbg.set_label( _('Tap on the two dots that switched positions.')) self._tapped = None self._next = self._tap_two def _tap_two(self): self._shake = None if self._tapped == None: self._tapped = [] if not self._release in [self._targets[1], self._targets[2]]: self._lightbg.set_label(_('Keep trying.')) return else: if not self._release in self._tapped: self._tapped.append(self._release) self._dots[self._release].set_label(':)') if len(self._tapped) == 2: pos1 = self._dots[self._targets[1]].get_xy() pos2 = self._dots[self._targets[2]].get_xy() self._dots[self._targets[1]].move(pos2) self._dots[self._targets[2]].move(pos1) self._lightbg.set_label(_("Good job! Now let's shake again.")) self._shake = 'random2' self._next = self._shake_three # Since we don't end up in the button press GObject.timeout_add(500, self._next) for i in self._tapped: self._dots[i].set_label('') elif len(self._tapped) == 1: self._lightbg.set_label( _('You found one. Now find the other one.')) def _shake_three(self): self._next = self._fade_it self._shake = 'random2' GObject.timeout_add(100, read_accelerometer, self) self._pausing = True GObject.timeout_add(2000, self._clear_pause) def _fade_it(self): for dot in self._dots: if dot.type in [RED, YELLOW, BLUE]: self._fade_dot(dot, 1) self._lightbg.set_label(_('Going')) self._shake = 'random2' self._next = self._fade_it_again self._pausing = True GObject.timeout_add(2000, self._clear_pause) def _fade_it_again(self): for dot in self._dots: if dot.type in [RED, YELLOW, BLUE]: self._fade_dot(dot, 2) self._lightbg.set_label(_('Going') + '..') self._shake = 'random2' self._next = self._and_again self._pausing = True GObject.timeout_add(2000, self._clear_pause) def _and_again(self): for dot in self._dots: if dot.type in [RED, YELLOW, BLUE]: self._fade_dot(dot, 3) self._lightbg.set_label(_('Going') + '...') self._shake = 'random2' self._next = self._one_last_time self._pausing = True GObject.timeout_add(2000, self._clear_pause) def _one_last_time(self): for dot in self._dots: if dot.type in [RED, YELLOW, BLUE]: self._fade_dot(dot, 4) self._lightbg.set_label(_('Gone!')) self._shake = None self._next = self._yellow_dot GObject.timeout_add(500, self._next) def _fade_dot(self, dot, i): if i == 4: dot.set_shape(self._new_dot(self._colors[WHITE])) else: dot.set_shape(self._new_dot(self._colors[dot.type + i])) def _set_label(self, string): ''' Set the label in the toolbar or the window frame. ''' self._activity.status.set_label(string) def motion_cb(self, x, y, z): if read_accelerometer.device_path is None: jiggle_factor = 5 else: jiggle_factor = 3 if self._shake is None: return elif self._shake in ['random', 'random2']: if self._shake == 'random2': jiggle_factor *= 2 for dot in self._dots: if dot.type in [RED, YELLOW, BLUE]: x += int(uniform(-jiggle_factor, jiggle_factor)) z += int(uniform(-jiggle_factor, jiggle_factor)) # Randomize z drift, which tends toward up... if int(uniform(0, 2)) == 0: z = -z dot.move_relative((x, z)) elif self._shake == 'align': docked = True yellow = 0 red = 0 blue = 0 for dot in self._dots: if dot.type == YELLOW: docked = self._dock_dot(dot, yellow + 1, 1, jiggle_factor, docked) yellow += 1 elif dot.type == RED: docked = self._dock_dot(dot, red + 2, 2, jiggle_factor, docked) red += 1 elif dot.type == BLUE: docked = self._dock_dot(dot, blue + 3, 3, jiggle_factor, docked) blue += 1 if docked: self._lightbg.set_label(_('Interesting.')) self._pausing = False elif self._shake == 'left': right = False for dot in self._dots: if dot.type in [RED, YELLOW, BLUE]: pos = dot.get_xy() if pos[0] < 0: if pos[1] > self._height: z = int(uniform(-20, 0)) elif pos[1] < 0: z = int(uniform(0, 20)) x = int(uniform(0, 10)) dot.move_relative((x, z)) elif x < 0: x += int(uniform(-10, 0)) if pos[1] > self._height: z = int(uniform(-20, 0)) elif pos[1] < 0: z = int(uniform(0, 20)) if pos[0] > -x: dot.move_relative((x, z)) pos = dot.get_xy() if pos[0] > 100: right = True if not right: self._lightbg.set_label(_('Hmm')) self._pausing = False elif self._shake == 'right': left = False for dot in self._dots: if dot.type in [RED, YELLOW, BLUE]: pos = dot.get_xy() if pos[0] > self._width - self._dot_size: if pos[1] > self._height: z = int(uniform(-20, 0)) elif pos[1] < 0: z = int(uniform(0, 20)) x = int(uniform(-10, 0)) dot.move_relative((x, z)) elif x < self._width - self._dot_size: x += int(uniform(0, 10)) if pos[1] > self._height: z = int(uniform(-20, 0)) elif pos[1] < 0: z = int(uniform(0, 20)) if pos[0] < self._width - x - self._dot_size: dot.move_relative((x, z)) pos = dot.get_xy() if pos[0] < self._width - self._dot_size - 100: left = True if not left: self._lightbg.set_label(_('Hmm')) self._pausing = False if not self._pausing: if self._next is not None: GObject.timeout_add(1000, self._next) else: self._lightbg.set_label('') self._shake = None GObject.timeout_add(100, read_accelerometer, self) return def _dock_dot(self, dot, n, m, jiggle_factor, docked): x = (self._dot_size + self._space) * n y = (self._dot_size + self._space) * m pos = dot.get_xy() dx = x - pos[0] dy = y - pos[1] if abs(dx) < 11 and abs(dy) < 11: dot.move((x, y)) return docked else: if dx < 0: dx = max(-10, dx) elif dx > 0: dx = min(10, dx) if dy < 0: dy = max(-10, dy) elif dy > 0: dy = min(10, dy) dx += int(uniform(-jiggle_factor, jiggle_factor)) dy += int(uniform(-jiggle_factor, jiggle_factor)) dot.move_relative((dx, dy)) return False def _button_press_cb(self, win, event): if self._shake is not None: return True win.grab_focus() x, y = map(int, event.get_coords()) self._press = True self._release = None spr = self._sprites.find_sprite((x, y)) if spr == None: return True self.last_spr = spr if self._rubbing: self._pausing = True if spr in self._dots: for target in self._targets: if self._dots.index(spr) == target: self._release = target GObject.timeout_add(1000, self._clear_pause) return True def _button_release_cb(self, win, event): if self._shake is not None: return True self._press = False self._release = None if self._pausing: self._lightbg.set_label(_('Rub a little longer.')) return True x, y = map(int, event.get_coords()) spr = self._sprites.find_sprite((x, y)) if spr.type is not None: if spr in self._dots: for target in self._targets: if self._dots.index(spr) == target: self._release = target if self._next is not None: GObject.timeout_add(200, self._next) def _smile(self): for dot in self._dots: dot.set_label(':)') def __draw_cb(self, canvas, cr): self._sprites.redraw_sprites(cr=cr) def _grid_to_dot(self, pos): ''' calculate the dot index from a column and row in the grid ''' return pos[0] + pos[1] * NINE def _dot_to_grid(self, dot): ''' calculate the grid column and row for a dot ''' return [dot % NINE, int(dot / NINE)] def _destroy_cb(self, win, event): Gtk.main_quit() def _new_dot(self, color): ''' generate a dot of a color color ''' self._dot_cache = {} if not color in self._dot_cache: self._stroke = color self._fill = color self._svg_width = self._dot_size self._svg_height = self._dot_size pixbuf = svg_str_to_pixbuf( self._header() + \ self._circle(self._dot_size / 2., self._dot_size / 2., self._dot_size / 2.) + \ self._footer()) surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self._svg_width, self._svg_height) context = cairo.Context(surface) Gdk.cairo_set_source_pixbuf(context, pixbuf, 0, 0) context.rectangle(0, 0, self._svg_width, self._svg_height) context.fill() self._dot_cache[color] = surface return self._dot_cache[color] def _line(self, vertical=True): ''' Generate a center line ''' if vertical: self._svg_width = 3 self._svg_height = self._height return svg_str_to_pixbuf( self._header() + \ self._rect(3, self._height, 0, 0) + \ self._footer()) else: self._svg_width = self._width self._svg_height = 3 return svg_str_to_pixbuf( self._header() + \ self._rect(self._width, 3, 0, 0) + \ self._footer()) def _header(self): return '<svg\n' + 'xmlns:svg="http://www.w3.org/2000/svg"\n' + \ 'xmlns="http://www.w3.org/2000/svg"\n' + \ 'xmlns:xlink="http://www.w3.org/1999/xlink"\n' + \ 'version="1.1"\n' + 'width="' + str(self._svg_width) + '"\n' + \ 'height="' + str(self._svg_height) + '">\n' def _rect(self, w, h, x, y, color='#ffffff'): svg_string = ' <rect\n' svg_string += ' width="%f"\n' % (w) svg_string += ' height="%f"\n' % (h) svg_string += ' rx="%f"\n' % (0) svg_string += ' ry="%f"\n' % (0) svg_string += ' x="%f"\n' % (x) svg_string += ' y="%f"\n' % (y) svg_string += 'style="fill:%s;stroke:none;"/>\n' % (color) return svg_string def _circle(self, r, cx, cy): return '<circle style="fill:' + str(self._fill) + ';stroke:' + \ str(self._stroke) + ';" r="' + str(r - 0.5) + '" cx="' + \ str(cx) + '" cy="' + str(cy) + '" />\n' def _footer(self): return '</svg>\n'
class Game(): def __init__(self, canvas, path, parent=None): self.activity = parent self.path = path # starting from command line # we have to do all the work that was done in CardSortActivity.py if parent is None: self.sugar = False self.canvas = canvas # starting from Sugar else: self.sugar = True self.canvas = canvas parent.show_all() self.canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self.canvas.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK) self.canvas.connect("draw", self.__draw_cb) self.canvas.connect("button-press-event", self._button_press_cb) self.canvas.connect("button-release-event", self._button_release_cb) self.width = Gdk.Screen.width() self.height = Gdk.Screen.height() - style.GRID_CELL_SIZE self.card_dim = CARD_DIM self.scale = 0.8 * self.height / (self.card_dim * 3) # Initialize the sprite repository self.sprites = Sprites(self.canvas) # Initialize the grid self.grid = Grid(self) # Start solving the puzzle self.press = -1 self.release = -1 self.start_drag = [0, 0] # # Button press # def _button_press_cb(self, win, event): win.grab_focus() x, y = map(int, event.get_coords()) self.start_drag = [x, y] self.grid.hide_masks() spr = self.sprites.find_sprite((x, y)) if spr is None: self.press = -1 self.release = -1 return True # take note of card under button press self.press = int(spr.labels[0]) return True # # Button release # def _button_release_cb(self, win, event): win.grab_focus() self.grid.hide_masks() x, y = map(int, event.get_coords()) spr = self.sprites.find_sprite((x, y)) if spr is None: self.press = -1 self.release = -1 return True # take note of card under button release self.release = int(spr.labels[0]) # if the same card (click) then rotate if self.press == self.release: # check to see if it was an aborted move if self.distance(self.start_drag, [x, y]) < 20: self.grid.card_table[self.press].rotate_ccw() # self.grid.card_table[self.press].print_card() # if different card (drag) then swap else: self.grid.swap(self.press, self.release) # self.grid.print_grid() self.press = -1 self.release = -1 if self.test() is True: if self.sugar is True: self.activity.results_label.set_text( _("You solved the puzzle.")) self.activity.results_label.show() else: self.win.set_title( _("CardSort") + ": " + _("You solved the puzzle.")) else: if self.sugar is True: self.activity.results_label.set_text(_("Keep trying.")) self.activity.results_label.show() else: self.win.set_title(_("CardSort") + ": " + _("Keep trying.")) return True # # Measure length of drag between button press and button release # def distance(self, start, stop): dx = start[0] - stop[0] dy = start[1] - stop[1] return sqrt(dx * dx + dy * dy) # # Repaint # def __draw_cb(self, canvas, cr): self.sprites.redraw_sprites(cr=cr) def do_expose_event(self, event): ''' Handle the expose-event by drawing ''' # Restrict Cairo to the exposed area cr = self.canvas.window.cairo_create() cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list self.sprites.redraw_sprites(cr=cr) # # callbacks # def _destroy_cb(self, win, event): Gtk.main_quit()
class Game(): def __init__(self, canvas, parent=None, colors=['#A0FFA0', '#FF8080']): self._activity = parent self._colors = colors self._canvas = canvas parent.show_all() self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.connect("draw", self.__draw_cb) self._canvas.connect("button-press-event", self._button_press_cb) self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - (GRID_CELL_SIZE * 1.5) self._scale = self._height / (14.0 * DOT_SIZE * 1.2) self._dot_size = int(DOT_SIZE * self._scale) self._turtle_offset = 0 self._space = int(self._dot_size / 5.) self._orientation = 0 self.level = 0 self.custom_strategy = None self.strategies = [BEGINNER_STRATEGY, INTERMEDIATE_STRATEGY, EXPERT_STRATEGY, self.custom_strategy] self.strategy = self.strategies[self.level] self._timeout_id = None # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) self._dots = [] for y in range(THIRTEEN): for x in range(THIRTEEN): xoffset = int((self._width - THIRTEEN * (self._dot_size + \ self._space) - self._space) / 2.) if y % 2 == 1: xoffset += int((self._dot_size + self._space) / 2.) if x == 0 or y == 0 or x == THIRTEEN - 1 or y == THIRTEEN - 1: self._dots.append( Sprite(self._sprites, xoffset + x * (self._dot_size + self._space), y * (self._dot_size + self._space), self._new_dot('#B0B0B0'))) else: self._dots.append( Sprite(self._sprites, xoffset + x * (self._dot_size + self._space), y * (self._dot_size + self._space), self._new_dot(self._colors[FILL]))) self._dots[-1].type = False # not set # Put a turtle at the center of the screen... self._turtle_images = [] self._rotate_turtle(self._new_turtle()) self._turtle = Sprite(self._sprites, 0, 0, self._turtle_images[0]) self._move_turtle(self._dots[int(THIRTEEN * THIRTEEN / 2)].get_xy()) # ...and initialize. self._all_clear() def _move_turtle(self, pos): ''' Move turtle and add its offset ''' self._turtle.move(pos) self._turtle.move_relative( (-self._turtle_offset, -self._turtle_offset)) def _all_clear(self): ''' Things to reinitialize when starting up a new game. ''' # Clear dots for dot in self._dots: if dot.type: dot.type = False dot.set_shape(self._new_dot(self._colors[FILL])) dot.set_label('') # Recenter the turtle self._move_turtle(self._dots[int(THIRTEEN * THIRTEEN / 2)].get_xy()) self._turtle.set_shape(self._turtle_images[0]) self._set_label('') if self._timeout_id is not None: GObject.source_remove(self._timeout_id) def new_game(self, saved_state=None): ''' Start a new game. ''' self._all_clear() # Fill in a few dots to start for i in range(15): n = int(uniform(0, THIRTEEN * THIRTEEN)) if self._dots[n].type is not None: self._dots[n].type = True self._dots[n].set_shape(self._new_dot(self._colors[STROKE])) # Calculate the distances to the edge self._initialize_weights() self.strategy = self.strategies[self.level] def _set_label(self, string): ''' Set the label in the toolbar or the window frame. ''' self._activity.status.set_label(string) def _button_press_cb(self, win, event): win.grab_focus() x, y = map(int, event.get_coords()) spr = self._sprites.find_sprite((x, y), inverse=True) if spr == None: return if spr.type is not None and not spr.type: spr.type = True spr.set_shape(self._new_dot(self._colors[STROKE])) self._weights[self._dots.index(spr)] = 1000 self._test_game_over(self._move_the_turtle()) return True def _find_the_turtle(self): turtle_pos = self._turtle.get_xy() turtle_dot = None for dot in self._dots: pos = dot.get_xy() # Turtle is offset if pos[0] == turtle_pos[0] + self._turtle_offset and \ pos[1] == turtle_pos[1] + self._turtle_offset: turtle_dot = self._dots.index(dot) break if turtle_dot is None: _logger.debug('Cannot find the turtle...') return None return turtle_dot def _move_the_turtle(self): ''' Move the turtle after each click ''' self._turtle_dot = self._find_the_turtle() if self._turtle_dot is None: return # Given the col and row of the turtle, do something new_dot = self._grid_to_dot( self._my_strategy_import(self.strategy, self._dot_to_grid(self._turtle_dot))) self._move_turtle(self._dots[new_dot].get_xy()) # And set the orientation self._turtle.set_shape(self._turtle_images[self._orientation]) return new_dot def _test_game_over(self, new_dot): ''' Check to see if game is over ''' if new_dot is None: return if self._dots[new_dot].type is None: # Game-over feedback self._once_around = False self._happy_turtle_dance() return True c = int(self._turtle_dot / THIRTEEN) % 2 if self._dots[ new_dot + CIRCLE[c][0][0] + THIRTEEN * CIRCLE[c][0][1]].type and \ self._dots[ new_dot + CIRCLE[c][1][0] + THIRTEEN * CIRCLE[c][1][1]].type and \ self._dots[ new_dot + CIRCLE[c][2][0] + THIRTEEN * CIRCLE[c][2][1]].type and \ self._dots[ new_dot + CIRCLE[c][3][0] + THIRTEEN * CIRCLE[c][3][1]].type and \ self._dots[ new_dot + CIRCLE[c][4][0] + THIRTEEN * CIRCLE[c][4][1]].type and \ self._dots[ new_dot + CIRCLE[c][5][0] + THIRTEEN * CIRCLE[c][5][1]].type: # Game-over feedback for dot in self._dots: dot.set_label(':)') return True return False def _grid_to_dot(self, pos): ''' calculate the dot index from a column and row in the grid ''' return pos[0] + pos[1] * THIRTEEN def _dot_to_grid(self, dot): ''' calculate the grid column and row for a dot ''' return [dot % THIRTEEN, int(dot / THIRTEEN)] def _happy_turtle_dance(self): ''' Turtle dances along the edge ''' i = self._find_the_turtle() if i == 0: if self._once_around: return else: self._once_around = True _logger.debug(i) x, y = self._dot_to_grid(i) if y == 0: x += 1 if x == 0: y -= 1 if x == THIRTEEN - 1: y += 1 if y == THIRTEEN - 1: x -= 1 i = self._grid_to_dot((x, y)) self._dots[i].set_label(':)') self._move_turtle(self._dots[i].get_xy()) self._orientation += 1 self._orientation %= 6 self._turtle.set_shape(self._turtle_images[self._orientation]) self._timeout_id = GObject.timeout_add(250, self._happy_turtle_dance) def _ordered_weights(self, pos): ''' Returns the list of surrounding points sorted by their distance to the edge ''' dots = self._surrounding_dots(pos) dots_and_weights = [] for dot in dots: dots_and_weights.append((dot, self._weights[dot])) sorted_dots = sorted(dots_and_weights, key=lambda foo: foo[1]) for i in range(6): dots[i] = sorted_dots[i][0] return dots def _daylight_ahead(self, pos): ''' Returns true if there is a straight path to the edge from the current position/orientation ''' dots = self._surrounding_dots(pos) while True: dot_type = self._dots[dots[self._orientation]].type if dot_type is None: return True elif dot_type: return False else: # keep looking pos = self._dot_to_grid(dots[self._orientation]) dots = self._surrounding_dots(pos) def _surrounding_dots(self, pos): ''' Returns dots surrounding a position in the grid ''' dots = [] evenodd = pos[1] % 2 for i in range(6): col = pos[0] + CIRCLE[evenodd][i][0] row = pos[1] + CIRCLE[evenodd][i][1] dots.append(self._grid_to_dot((col, row))) return dots def _initialize_weights(self): ''' How many steps to an edge? ''' self._weights = [] for d, dot in enumerate(self._dots): if dot.type is None: self._weights.append(0) elif dot.type: self._weights.append(1000) else: pos = self._dot_to_grid(d) pos2 = (THIRTEEN - pos[0], THIRTEEN - pos[1]) self._weights.append(min(min(pos[0], pos2[0]), min(pos[1], pos2[1]))) def _my_strategy_import(self, f, arg): ''' Run Python code passed as argument ''' userdefined = {} try: exec f in globals(), userdefined return userdefined['_turtle_strategy'](self, arg) except ZeroDivisionError, e: self._set_label('Python zero-divide error: %s' % (str(e))) except ValueError, e: self._set_label('Python value error: %s' % (str(e)))
class Bounce(): ''' The Bounce class is used to define the ball and the user interaction. ''' def __init__(self, canvas, path, parent=None): ''' Initialize the canvas and set up the callbacks. ''' self._activity = parent self._fraction = None self._path = path if parent is None: # Starting from command line self._sugar = False else: # Starting from Sugar self._sugar = True self._canvas = canvas self._canvas.grab_focus() self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK) self._canvas.add_events(Gdk.EventMask.POINTER_MOTION_MASK) self._canvas.add_events(Gdk.EventMask.KEY_PRESS_MASK) self._canvas.add_events(Gdk.EventMask.KEY_RELEASE_MASK) self._canvas.connect('draw', self.__draw_cb) self._canvas.connect('button-press-event', self._button_press_cb) self._canvas.connect('button-release-event', self._button_release_cb) self._canvas.connect('key-press-event', self._keypress_cb) self._canvas.connect('key-release-event', self._keyrelease_cb) self._canvas.set_can_focus(True) self._canvas.grab_focus() self._sprites = Sprites(self._canvas) self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - GRID_CELL_SIZE self._scale = Gdk.Screen.height() / 900.0 self._step_sid = None # repeating timeout between steps of ball move self._bounce_sid = None # one-off timeout between bounces self.buddies = [] # used for sharing self._my_turn = False self.select_a_fraction = False self._easter_egg = int(uniform(1, 100)) # Find paths to sound files self._path_to_success = os.path.join(path, LAUGH) self._path_to_failure = os.path.join(path, CRASH) self._path_to_bubbles = os.path.join(path, BUBBLES) self._create_sprites(path) self.mode = 'fractions' self._challenge = 0 self._expert = False self._challenges = [] for challenge in CHALLENGES[self._challenge]: self._challenges.append(challenge) self._fraction = 0.5 # the target of the current challenge self._label = '1/2' # the label self.count = 0 # number of bounces played self._correct = 0 # number of correct answers self._press = None # sprite under mouse click self._new_bounce = False self._n = 0 self._accelerometer = self._check_accelerometer() self._accel_index = 0 self._accel_flip = False self._accel_xy = [0, 0] self._guess_orientation() self._dx = 0. # ball horizontal trajectory # acceleration (with dampening) self._ddy = (6.67 * self._height) / (STEPS * STEPS) self._dy = self._ddy * (1 - STEPS) / 2. # initial step size if self._sugar: if _is_tablet_mode(): self._activity.reset_label( _('Click the ball to start. Rock the computer left ' 'and right to move the ball.')) else: self._activity.reset_label( _('Click the ball to start. Then use the arrow keys to ' 'move the ball.')) self._keyrelease_id = None def _check_accelerometer(self): return os.path.exists(ACCELEROMETER_DEVICE) and _is_tablet_mode() def configure_cb(self, event): self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - GRID_CELL_SIZE self._scale = Gdk.Screen.height() / 900.0 # We need to resize the backgrounds width, height = self._calc_background_size() for bg in list(self._backgrounds.keys()): if bg == 'custom': path = self._custom_dsobject.file_path pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( path, width, height) else: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._path, 'images', bg), width, height) if Gdk.Screen.height() > Gdk.Screen.width(): pixbuf = self._crop_to_portrait(pixbuf) self._backgrounds[bg] = pixbuf self._background = Sprite(self._sprites, 0, 0, self._backgrounds[self._current_bg]) self._background.set_layer(-100) self._background.type = 'background' # and resize and reposition the bars self.bar.resize_all() self.bar.show_bar(2) self._current_bar = self.bar.get_bar(2) # Calculate a new accerlation based on screen height. self._ddy = (6.67 * self._height) / (STEPS * STEPS) self._guess_orientation() def _create_sprites(self, path): ''' Create all of the sprites we'll need ''' self.smiley_graphic = svg_str_to_pixbuf( svg_from_file(os.path.join(path, 'images', 'smiley.svg'))) self.frown_graphic = svg_str_to_pixbuf( svg_from_file(os.path.join(path, 'images', 'frown.svg'))) self.blank_graphic = svg_str_to_pixbuf( svg_header(REWARD_HEIGHT, REWARD_HEIGHT, 1.0) + svg_rect( REWARD_HEIGHT, REWARD_HEIGHT, 5, 5, 0, 0, 'none', 'none') + svg_footer()) self.ball = Ball(self._sprites, os.path.join(path, 'images', 'soccerball.svg')) self._current_frame = 0 self.bar = Bar(self._sprites, self.ball.width(), COLORS) self._current_bar = self.bar.get_bar(2) self.ball_y_max = \ self.bar.bar_y() - self.ball.height() + int(BAR_HEIGHT / 2.) self.ball.move_ball((int( (self._width - self.ball.width()) // 2), self.ball_y_max)) self._backgrounds = {} width, height = self._calc_background_size() pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(path, 'images', 'grass_background.png'), width, height) if Gdk.Screen.height() > Gdk.Screen.width(): pixbuf = self._crop_to_portrait(pixbuf) self._backgrounds['grass_background.png'] = pixbuf self._background = Sprite(self._sprites, 0, 0, pixbuf) self._background.set_layer(-100) self._background.type = 'background' self._current_bg = 'grass_background.png' def _crop_to_portrait(self, pixbuf): tmp = GdkPixbuf.Pixbuf.new(0, True, 8, Gdk.Screen.width(), Gdk.Screen.height()) x = int(Gdk.Screen.height() // 3) pixbuf.copy_area(x, 0, Gdk.Screen.width(), Gdk.Screen.height(), tmp, 0, 0) return tmp def _calc_background_size(self): if Gdk.Screen.height() > Gdk.Screen.width(): height = Gdk.Screen.height() return int(4 * height // 3), height else: width = Gdk.Screen.width() return width, int(3 * width // 4) def new_background_from_image(self, path, dsobject=None): if path is None: path = dsobject.file_path width, height = self._calc_background_size() pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(path, width, height) if Gdk.Screen.height() > Gdk.Screen.width(): pixbuf = self._crop_to_portrait(pixbuf) self._backgrounds['custom'] = pixbuf self.set_background('custom') self._custom_dsobject = dsobject self._current_bg = 'custom' def set_background(self, name): if name not in self._backgrounds: width, height = self._calc_background_size() pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._path, 'images', name), width, height) if Gdk.Screen.height() > Gdk.Screen.width(): pixbuf = self._crop_to_portrait(pixbuf) self._backgrounds[name] = pixbuf self._background.set_image(self._backgrounds[name]) self.bar.mark.hide() self._current_bar.hide() self.ball.ball.hide() self.ball.ball.set_layer(3) self._current_bar.set_layer(2) self.bar.mark.set_layer(1) self._current_bg = name self._canvas.queue_draw() def pause(self): ''' Pause play when visibility changes ''' if self._step_sid is not None: GLib.source_remove(self._step_sid) self._step_sid = None if self._bounce_sid is not None: GLib.source_remove(self._bounce_sid) self._bounce_sid = None def we_are_sharing(self): ''' If there is more than one buddy, we are sharing. ''' if len(self.buddies) > 1: return True def its_my_turn(self): ''' When sharing, it is your turn... ''' GLib.timeout_add(1000, self._take_a_turn) def _take_a_turn(self): ''' On your turn, choose a fraction. ''' self._my_turn = True self.select_a_fraction = True self._activity.set_player_on_toolbar(self._activity.nick, self._activity.key) self._activity.reset_label(_("Click on the bar to choose a fraction.")) def its_their_turn(self, nick, key): ''' When sharing, it is nick's turn... ''' GLib.timeout_add(1000, self._wait_your_turn, nick, key) def _wait_your_turn(self, nick, key): ''' Wait for nick to choose a fraction. ''' self._my_turn = False self._activity.set_player_on_toolbar(nick, key) self._activity.reset_label( _('Waiting for %(buddy)s') % {'buddy': nick}) def play_a_fraction(self, fraction): ''' Play this fraction ''' fraction_is_new = True for i, c in enumerate(self._challenges): if c[0] == fraction: fraction_is_new = False self._n = i break if fraction_is_new: self.add_fraction(fraction) self._n = len(self._challenges) self._choose_a_fraction() self._start_step() def _button_press_cb(self, win, event): ''' Callback to handle the button presses ''' win.grab_focus() x, y = list(map(int, event.get_coords())) self._press = self._sprites.find_sprite((x, y)) return True def _button_release_cb(self, win, event): ''' Callback to handle the button releases ''' win.grab_focus() x, y = list(map(int, event.get_coords())) if self._press is not None: if self.we_are_sharing(): if self.select_a_fraction and self._press == self._current_bar: # Find the fraction closest to the click fraction = self._search_challenges( (x - self.bar.bar_x()) / float(self.bar.width())) self.select_a_fraction = False self._activity.send_a_fraction(fraction) self.play_a_fraction(fraction) else: if self._step_sid is None and \ self._bounce_sid is None and \ self._press == self.ball.ball: self._choose_a_fraction() self._start_step() return True def _search_challenges(self, f): ''' Find the fraction which is closest to f in the list. ''' dist = 1. closest = '1/2' for c in self._challenges: numden = c[0].split('/') delta = abs((float(numden[0]) / float(numden[1])) - f) if delta <= dist: dist = delta closest = c[0] return closest def _guess_orientation(self): if self._accelerometer: fh = open(ACCELEROMETER_DEVICE) string = fh.read() fh.close() xyz = string[1:-2].split(',') x = int(xyz[0]) y = int(xyz[1]) self._accel_xy = [x, y] if abs(x) > abs(y): self._accel_index = 1 # Portrait mode self._accel_flip = x > 0 else: self._accel_index = 0 # Landscape mode self._accel_flip = y < 0 def _defer_bounce(self, ms): ''' Pause and then start the ball again ''' self._bounce_sid = GLib.timeout_add(ms, self._bounce) def _bounce(self): ''' Start the ball again ''' self._accelerometer = self._check_accelerometer() self._start_step() self._bounce_sid = None return False def _start_step(self): ''' Start the ball and keep moving until boundary conditions ''' if self._step(): self._step_sid = GLib.timeout_add(STEP_PAUSE, self._step) def _step(self): ''' Move the ball once and test boundary conditions ''' if self._new_bounce: self.bar.mark.move((0, self._height)) # hide the mark if not self.we_are_sharing(): self._choose_a_fraction() self._new_bounce = False self._dy = self._ddy * (1 - STEPS) / 2 # initial step size if self._accelerometer: self._guess_orientation() self._dx = float(self._accel_xy[self._accel_index]) / 18. if self._accel_flip: self._dx *= -1 if self.ball.ball_x() + self._dx > 0 and \ self.ball.ball_x() + self._dx < self._width - self.ball.width(): self.ball.move_ball_relative((int(self._dx), int(self._dy))) else: self.ball.move_ball_relative((0, int(self._dy))) # speed up ball in x while key is pressed self._dx *= DDX # accelerate in y self._dy += self._ddy # Calculate a new ball_y_max depending on the x position self.ball_y_max = \ self.bar.bar_y() - self.ball.height() + self._wedge_offset() if self.ball.ball_y() >= self.ball_y_max: # hit the bottom self.ball.move_ball((self.ball.ball_x(), self.ball_y_max)) self._test() self._new_bounce = True if self.we_are_sharing(): if self._my_turn: # Let the next player know it is their turn. i = (self.buddies.index( [self._activity.nick, self._activity.key]) + 1) % \ len(self.buddies) [nick, key] = self.buddies[i] self.its_their_turn(nick, key) self._activity.send_event('t', self.buddies[i]) else: if not self.we_are_sharing() and self._easter_egg_test(): self._animate() else: ms = max(STEP_PAUSE, BOUNCE_PAUSE - self.count * STEP_PAUSE) self._defer_bounce(ms) self._step_sid = None return False else: return True def _wedge_offset(self): return int(BAR_HEIGHT * (1 - (self.ball.ball_x() / float(self.bar.width())))) def _mark_offset(self, x): return int(BAR_HEIGHT * (1 - (x / float(self.bar.width())))) - 12 def _animate(self): ''' A little Easter Egg just for fun. ''' if self._new_bounce: self._dy = self._ddy * (1 - STEPS) / 2 # initial step size self._new_bounce = False self._current_frame = 0 self._frame_counter = 0 self.ball.move_frame(self._current_frame, (self.ball.ball_x(), self.ball.ball_y())) self.ball.move_ball((self.ball.ball_x(), self._height)) aplay.play(self._path_to_bubbles) if self._accelerometer: fh = open(ACCELEROMETER_DEVICE) string = fh.read() xyz = string[1:-2].split(',') self._dx = float(xyz[0]) / 18. fh.close() else: self._dx = uniform(-int(DX * self._scale), int(DX * self._scale)) self.ball.move_frame_relative(self._current_frame, (int(self._dx), int(self._dy))) self._dy += self._ddy self._frame_counter += 1 self._current_frame = self.ball.next_frame(self._frame_counter) if self.ball.frame_y(self._current_frame) >= self.ball_y_max: # hit the bottom self.ball.move_ball((self.ball.ball_x(), self.ball_y_max)) self.ball.hide_frames() self._test(easter_egg=True) self._new_bounce = True self._defer_bounce(BOUNCE_PAUSE) else: GLib.timeout_add(STEP_PAUSE, self._animate) def add_fraction(self, string): ''' Add a new challenge; set bar to 2x demominator ''' numden = string.split('/', 2) self._challenges.append([string, int(numden[1]), 0]) def _get_new_fraction(self): ''' Select a new fraction challenge from the table ''' if not self.we_are_sharing(): n = int(uniform(0, len(self._challenges))) else: n = self._n fstr = self._challenges[n][0] if '/' in fstr: # fraction numden = fstr.split('/', 2) fraction = float(numden[0].strip()) / float(numden[1].strip()) elif '%' in fstr: # percentage fraction = float(fstr.strip().strip('%').strip()) / 100. else: # To do: add support for decimals (using locale) _logger.debug('Could not parse challenge (%s)', fstr) fstr = '1/2' fraction = 0.5 return fraction, fstr, n def _choose_a_fraction(self): ''' choose a new fraction and set the corresponding bar ''' # Don't repeat the same fraction twice in a row fraction, fstr, n = self._get_new_fraction() if not self.we_are_sharing(): while fraction == self._fraction: fraction, fstr, n = self._get_new_fraction() self._fraction = fraction self._n = n if self.mode == 'percents': self._label = str(int(self._fraction * 100 + 0.5)) + '%' else: # percentage self._label = fstr if self.mode == 'sectors': self.ball.new_ball_from_fraction(self._fraction) if not Gdk.Screen.width() < 1024: self._activity.reset_label( _('Bounce the ball to a position ' '%(fraction)s of the way from the left side of the bar.') % {'fraction': self._label}) else: self._activity.reset_label( _('Bounce the ball to %(fraction)s') % {'fraction': self._label}) self.ball.ball.set_label(self._label) self.bar.hide_bars() if self._expert: # Show two-segment bar in expert mode nseg = 2 else: if self.mode == 'percents': nseg = 10 else: nseg = self._challenges[self._n][1] # generate new bar on demand self._current_bar = self.bar.get_bar(nseg) self.bar.show_bar(nseg) def _easter_egg_test(self): ''' Test to see if we show the Easter Egg ''' delta = self.ball.width() / 8 x = self.ball.ball_x() + self.ball.width() / 2 f = self.bar.width() * self._easter_egg / 100. if x > f - delta and x < f + delta: return True else: return False def _test(self, easter_egg=False): ''' Test to see if we estimated correctly ''' if self._expert: delta = self.ball.width() / 6 else: delta = self.ball.width() / 3 x = self.ball.ball_x() + self.ball.width() / 2 f = int(self._fraction * self.bar.width()) self.bar.mark.move((int(f - self.bar.mark_width() / 2), int(self.bar.bar_y() + self._mark_offset(f)))) if self._challenges[self._n][2] == 0: # label the column spr = Sprite(self._sprites, 0, 0, self.blank_graphic) spr.set_label(self._label) spr.move((int(self._n * 27), 0)) spr.set_layer(-1) self._challenges[self._n][2] += 1 if x > f - delta and x < f + delta: spr = Sprite(self._sprites, 0, 0, self.smiley_graphic) self._correct += 1 aplay.play(self._path_to_success) else: spr = Sprite(self._sprites, 0, 0, self.frown_graphic) aplay.play(self._path_to_failure) spr.move((int(self._n * 27), int(self._challenges[self._n][2] * 27))) spr.set_layer(-1) # after enough correct answers, up the difficulty if self._correct == len(self._challenges) * 2: self._challenge += 1 if self._challenge < len(CHALLENGES): for challenge in CHALLENGES[self._challenge]: self._challenges.append(challenge) else: self._expert = True self.count += 1 self._dx = 0. # stop horizontal movement between bounces def _keypress_cb(self, area, event): ''' Keypress: moving the slides with the arrow keys ''' k = Gdk.keyval_name(event.keyval) if k in ['KP_Page_Down', 'KP_Home', 'h', 'Left', 'KP_Left']: self._dx = -DX * self._scale elif k in ['KP_Page_Up', 'KP_End', 'l', 'Right', 'KP_Right']: self._dx = DX * self._scale elif k in ['Return']: if self._step_sid: self._dy = -self._ddy * (1 - STEPS) * 2. else: self._dx = 0. if self._accel_flip: self._dx = -self._dx return True def _keyrelease_cb(self, area, event): ''' Keyrelease: stop horizontal movement ''' def timer_cb(): self._dx = 0. self._keyrelease_id = None return False if self._keyrelease_id is not None: GLib.source_remove(self._keyrelease_id) self._keyrelease_id = GLib.timeout_add(100, timer_cb) return True def __draw_cb(self, canvas, cr): self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): ''' Callback to handle quit ''' Gtk.main_quit()
class Game(): def __init__(self, canvas, parent=None, colors=['#A0FFA0', '#FF8080']): self._activity = parent self._colors = [colors[0]] self._colors.append(colors[1]) self._colors.append('#D0D0D0') self._colors.append('#000000') self._canvas = canvas if parent is not None: parent.show_all() self._parent = parent self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.connect("draw", self.__draw_cb) self._canvas.connect("button-press-event", self._button_press_cb) self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - (GRID_CELL_SIZE * 1.5) self._scale = self._width / (10 * DOT_SIZE * 1.2) self._dot_size = int(DOT_SIZE * self._scale) self._space = int(self._dot_size / 5.) self.we_are_sharing = False self._edge = 4 self._move_list = [] self.best_time = self.load_best_time() self.paused_time = 0 self.gameover_flag = None # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) self._dots = [] self._gameover = [] self._your_time = [] self._best_time = [] self._generate_grid() def _generate_grid(self): ''' Make a new set of dots for a grid of size edge ''' i = 0 for y in range(self._edge): for x in range(self._edge): xoffset = int((self._width - self._edge * self._dot_size - (self._edge - 1) * self._space) / 2.) yoffset = int((self._height - self._edge * self._dot_size - (self._edge - 1) * self._space) / 2.) if i < len(self._dots): self._dots[i].move( (xoffset + x * (self._dot_size + self._space), yoffset + y * (self._dot_size + self._space))) else: self._dots.append( Sprite(self._sprites, xoffset + x * (self._dot_size + self._space), yoffset + y * (self._dot_size + self._space), self._new_dot(self._colors[0]))) self._dots[i].type = 0 self._dots[-1].set_label_attributes(40) i += 1 # and initialize a few variables we'll need. self._all_clear() def _all_clear(self): ''' Things to reinitialize when starting up a new game. ''' self._move_list = [] # Clear dots for gameover_shape in self._gameover: gameover_shape.hide() for your_time_shape in self._your_time: your_time_shape.hide() for best_time_shape in self._best_time: best_time_shape.hide() for dot in self._dots: dot.type = 0 dot.set_shape(self._new_dot(self._colors[0])) dot.set_label('') dot.set_layer(100) def _initiating(self): return self._activity._collab.props.leader def more_dots(self): ''' Enlarge the grid ''' if self._edge < MAX: self._edge += 0 self._generate_grid() self.new_game() def new_game(self): ''' Start a new game. ''' self._all_clear() self.gameover_flag = False # Fill in a few dots to start for i in range(MAX * 2): self._flip_them(int(uniform(0, self._edge * self._edge))) if self.we_are_sharing: _logger.debug('sending a new game') self._parent.send_new_game() self.game_start_time = time.time() def restore_game(self, dot_list, move_list, paused_time): ''' Restore a game from the Journal or share ''' edge = int(sqrt(len(dot_list))) if edge > MAX: edge = MAX while self._edge < edge: self.more_dots() for i, dot in enumerate(dot_list): self._dots[i].type = dot self._dots[i].set_shape( self._new_dot(self._colors[self._dots[i].type])) if move_list is not None: self._move_list = move_list[:] self.game_start_time = time.time() self.paused_time = paused_time def save_game(self): ''' Return dot list, move_list for saving to Journal or sharing ''' dot_list = [] for dot in self._dots: dot_list.append(dot.type) self.game_stop_time = time.time() paused_time = abs(int(self.game_stop_time - self.game_start_time)) return (dot_list, self._move_list, paused_time) def gameover(self): best_seconds = self.best_time % 60 best_minutes = self.best_time // 60 self.elapsed_time = int(self.game_stop_time - self.game_start_time) + self.paused_time second = self.elapsed_time % 60 minute = self.elapsed_time // 60 for dot in self._dots: dot.hide() yoffset = int(self._space / 4.) xoffset = int( (self._width - 6 * self._dot_size - 5 * self._space) / 2.) y = 1 i = 0 for x in range(2, 6): self._gameover.append( Sprite(self._sprites, xoffset + (x - 0.25) * (self._dot_size - 10), y * (self._dot_size - 90 + self._space) + yoffset, self._new_dot(color=self._colors[0]))) self._gameover[-1].type = -1 # No image self._gameover[-1].set_label_attributes(72) i += 1 text = ["☻", " Game ", " Over ", "☻"] self.rings(len(text), text, self._gameover) y = 2 for x in range(2, 5): self._your_time.append( Sprite(self._sprites, xoffset + (x + 0.25) * (self._dot_size - 10), y * (self._dot_size - 30 + self._space) + yoffset, self._new_dot(color=self._colors[0]))) self._your_time[-1].type = -1 # No image self._your_time[-1].set_label_attributes(72) text = [ " your ", " time: ", (' {:02d}:{:02d} '.format(minute, second)) ] self.rings(len(text), text, self._your_time) y = 3 for x in range(2, 5): self._best_time.append( Sprite(self._sprites, xoffset + (x + 0.25) * (self._dot_size - 10), y * (self._dot_size - 20 + self._space) + yoffset, self._new_dot(color=self._colors[0]))) self._best_time[-1].type = -1 self._best_time[-1].set_label_attributes(72) if self.elapsed_time <= self.best_time: self.best_time = self.elapsed_time best_seconds = second best_minutes = minute text = [ " best ", " time: ", (' {:02d}:{:02d} '.format(best_minutes, best_seconds)) ] self.rings(len(text), text, self._best_time) self.save_best_time() self.paused_time = 0 GObject.timeout_add(3000, self.more_dots) def rings(self, num, text, shape): i = 0 for x in range(num): shape[x].type = -1 shape[x].set_shape(self._new_dot(self._colors[0])) shape[x].set_label(text[i]) shape[x].set_layer(100) i += 1 def _set_label(self, string): ''' Set the label in the toolbar or the window frame. ''' self._activity.status.set_label(string) def _button_press_cb(self, win, event): win.grab_focus() x, y = list(map(int, event.get_coords())) spr = self._sprites.find_sprite((x, y)) if spr is None: return if spr.type is not None: self._flip_them(self._dots.index(spr)) self._test_game_over() if self.we_are_sharing: _logger.debug('sending a click to the share') self._parent.send_dot_click(self._dots.index(spr)) return True def solve(self): ''' Solve the puzzle by undoing moves ''' if self._move_list == []: return self._flip_them(self._move_list.pop(), append=False) GObject.timeout_add(750, self.solve) def _flip_them(self, dot, append=True): ''' flip the dot and its neighbors ''' if append: self._move_list.append(dot) x, y = self._dot_to_grid(dot) self._flip(self._dots[dot]) if x > 0: self._flip(self._dots[dot - 1]) if y > 0: self._flip(self._dots[dot - self._edge]) if x < self._edge - 1: self._flip(self._dots[dot + 1]) if y < self._edge - 1: self._flip(self._dots[dot + self._edge]) def _flip(self, spr): ''' flip a dot ''' spr.type += 1 spr.type %= 2 spr.set_shape(self._new_dot(self._colors[spr.type])) def remote_button_press(self, dot): ''' Receive a button press from a sharer ''' self._flip_them(dot) self._test_game_over() def set_sharing(self, share=True): _logger.debug('enabling sharing') self.we_are_sharing = share def _smile(self): for dot in self._dots: dot.set_label(':)') def _test_game_over(self): ''' Check to see if game is over: all dots the same color ''' match = self._dots[0].type for y in range(self._edge): for x in range(self._edge): if self._dots[y * self._edge + x].type != match: self._set_label(_('keep trying')) return False self._set_label(_('good work')) self._smile() self.game_stop_time = time.time() self.gameover_flag = True GObject.timeout_add(2000, self.gameover) return True def _grid_to_dot(self, pos): ''' calculate the dot index from a column and row in the grid ''' return pos[0] + pos[1] * self._edge def _dot_to_grid(self, dot): ''' calculate the grid column and row for a dot ''' return [dot % self._edge, int(dot / self._edge)] def __draw_cb(self, canvas, cr): self._sprites.redraw_sprites(cr=cr) def do_expose_event(self, event): ''' Handle the expose-event by drawing ''' # Restrict Cairo to the exposed area cr = self._canvas.window.cairo_create() cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): Gtk.main_quit() def _new_dot(self, color): ''' generate a dot of a color color ''' self._dot_cache = {} if color not in self._dot_cache: self._stroke = color self._fill = color self._svg_width = self._dot_size self._svg_height = self._dot_size pixbuf = svg_str_to_pixbuf(self._header() + self._circle(self._dot_size / 2., self._dot_size / 2., self._dot_size / 2.) + self._footer()) surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self._svg_width, self._svg_height) context = cairo.Context(surface) Gdk.cairo_set_source_pixbuf(context, pixbuf, 0, 0) context.rectangle(0, 0, self._svg_width, self._svg_height) context.fill() self._dot_cache[color] = surface return self._dot_cache[color] def _header(self): return '<svg\n' + 'xmlns:svg="http://www.w3.org/2000/svg"\n' + \ 'xmlns="http://www.w3.org/2000/svg"\n' + \ 'xmlns:xlink="http://www.w3.org/1999/xlink"\n' + \ 'version="1.1"\n' + 'width="' + str(self._svg_width) + '"\n' + \ 'height="' + str(self._svg_height) + '">\n' def _circle(self, r, cx, cy): return '<circle style="fill:' + str(self._fill) + ';stroke:' + \ str(self._stroke) + ';" r="' + str(r - 0.5) + '" cx="' + \ str(cx) + '" cy="' + str(cy) + '" />\n' def _footer(self): return '</svg>\n' def read_best_time(self): best_time = [180] file_path = os.path.join(get_activity_root(), 'data', 'best-time') if os.path.exists(file_path): with open(file_path, "r") as fp: best_time = fp.readlines() return int(best_time[0]) def save_best_time(self): file_path = os.path.join(get_activity_root(), 'data', 'best-time') int_best_time = self.read_best_time() if not int_best_time <= self.elapsed_time: int_best_time = self.elapsed_time with open(file_path, "w") as fp: fp.write(str(int_best_time)) def load_best_time(self): best_time = self.read_best_time() try: return best_time except (ValueError, IndexError) as e: logging.exception(e) return 0 return 0
class Bounce(): ''' The Bounce class is used to define the ball and the user interaction. ''' def __init__(self, canvas, path, parent=None): ''' Initialize the canvas and set up the callbacks. ''' self.activity = parent if parent is None: # Starting from command line self.sugar = False self.canvas = canvas else: # Starting from Sugar self.sugar = True self.canvas = canvas parent.show_all() self.canvas.grab_focus() if os.path.exists(ACCELEROMETER_DEVICE): self.accelerometer = True else: self.accelerometer = False self.canvas.set_flags(gtk.CAN_FOCUS) self.canvas.add_events(gtk.gdk.BUTTON_PRESS_MASK) self.canvas.add_events(gtk.gdk.BUTTON_RELEASE_MASK) self.canvas.add_events(gtk.gdk.POINTER_MOTION_MASK) self.canvas.add_events(gtk.gdk.KEY_PRESS_MASK) self.canvas.add_events(gtk.gdk.KEY_RELEASE_MASK) self.canvas.connect('expose-event', self._expose_cb) self.canvas.connect('button-press-event', self._button_press_cb) self.canvas.connect('button-release-event', self._button_release_cb) self.canvas.connect('key_press_event', self._keypress_cb) self.canvas.connect('key_release_event', self._keyrelease_cb) self.width = gtk.gdk.screen_width() self.height = gtk.gdk.screen_height() - GRID_CELL_SIZE self.sprites = Sprites(self.canvas) self.scale = gtk.gdk.screen_height() / 900.0 self.timeout = None self.buddies = [] # used for sharing self.my_turn = False self.select_a_fraction = False self.easter_egg = int(uniform(1, 100)) # Find paths to sound files self.path_to_success = os.path.join(path, LAUGH) self.path_to_failure = os.path.join(path, CRASH) self.path_to_bubbles = os.path.join(path, BUBBLES) self._create_sprites(path) self.challenge = 0 self.expert = False self.challenges = [] for challenge in CHALLENGES[self.challenge]: self.challenges.append(challenge) self.fraction = 0.5 # the target of the current challenge self.label = '1/2' # the label self.count = 0 # number of bounces played self.correct = 0 # number of correct answers self.press = None # sprite under mouse click self.mode = 'fractions' self.new_bounce = False self.n = 0 self.dx = 0. # ball horizontal trajectory # acceleration (with dampening) self.ddy = (6.67 * self.height) / (STEPS * STEPS) self.dy = self.ddy * (1 - STEPS) / 2. # initial step size def _create_sprites(self, path): ''' Create all of the sprites we'll need ''' self.smiley_graphic = svg_str_to_pixbuf( svg_from_file(os.path.join(path, 'smiley.svg'))) self.frown_graphic = svg_str_to_pixbuf( svg_from_file(os.path.join(path, 'frown.svg'))) self.egg_graphic = svg_str_to_pixbuf( svg_from_file(os.path.join(path, 'Easter_egg.svg'))) self.blank_graphic = svg_str_to_pixbuf( svg_header(REWARD_HEIGHT, REWARD_HEIGHT, 1.0) + \ svg_rect(REWARD_HEIGHT, REWARD_HEIGHT, 5, 5, 0, 0, '#C0C0C0', '#282828') + \ svg_footer()) self.ball = Ball(self.sprites, os.path.join(path, 'basketball.svg')) self.current_frame = 0 self.bar = Bar(self.sprites, self.width, self.height, self.scale, self.ball.width()) self.current_bar = self.bar.get_bar(2) self.ball_y_max = self.bar.bar_y() - self.ball.height() self.ball.move_ball((int( (self.width - self.ball.width()) / 2), self.ball_y_max)) def pause(self): ''' Pause play when visibility changes ''' if self.timeout is not None: gobject.source_remove(self.timeout) self.timeout = None def we_are_sharing(self): ''' If there is more than one buddy, we are sharing. ''' if len(self.buddies) > 1: return True def its_my_turn(self): ''' When sharing, it is your turn... ''' gobject.timeout_add(1000, self._take_a_turn) def _take_a_turn(self): ''' On your turn, choose a fraction. ''' self.my_turn = True self.select_a_fraction = True self.activity.set_player_on_toolbar(self.activity.nick) self.activity.challenge.set_label( _("Click on the bar to choose a fraction.")) def its_their_turn(self, nick): ''' When sharing, it is nick's turn... ''' gobject.timeout_add(1000, self._wait_your_turn, nick) def _wait_your_turn(self, nick): ''' Wait for nick to choose a fraction. ''' self.my_turn = False self.activity.set_player_on_toolbar(nick) self.activity.challenge.set_label( _('Waiting for %(buddy)s') % {'buddy': nick}) def play_a_fraction(self, fraction): ''' Play this fraction ''' fraction_is_new = True for i, c in enumerate(self.challenges): if c[0] == fraction: fraction_is_new = False self.n = i break if fraction_is_new: self.add_fraction(fraction) self.n = len(self.challenges) self._choose_a_fraction() self._move_ball() def _button_press_cb(self, win, event): ''' Callback to handle the button presses ''' win.grab_focus() x, y = map(int, event.get_coords()) self.press = self.sprites.find_sprite((x, y)) return True def _button_release_cb(self, win, event): ''' Callback to handle the button releases ''' win.grab_focus() x, y = map(int, event.get_coords()) if self.press is not None: if self.we_are_sharing(): if self.select_a_fraction and self.press == self.current_bar: # Find the fraction closest to the click fraction = self._search_challenges( (x - self.bar.bar_x()) / float(self.bar.width())) self.select_a_fraction = False self.activity.send_a_fraction(fraction) self.play_a_fraction(fraction) else: if self.timeout is None and self.press == self.ball.ball: self._choose_a_fraction() self._move_ball() return True def _search_challenges(self, f): ''' Find the fraction which is closest to f in the list. ''' dist = 1. closest = '1/2' for c in self.challenges: numden = c[0].split('/') delta = abs((float(numden[0]) / float(numden[1])) - f) if delta <= dist: dist = delta closest = c[0] return closest def _move_ball(self): ''' Move the ball and test boundary conditions ''' if self.new_bounce: self.bar.mark.move((0, self.height)) # hide the mark if not self.we_are_sharing(): self._choose_a_fraction() self.new_bounce = False self.dy = self.ddy * (1 - STEPS) / 2 # initial step size if self.accelerometer: fh = open(ACCELEROMETER_DEVICE) string = fh.read() xyz = string[1:-2].split(',') self.dx = float(xyz[0]) / 18. fh.close() if self.ball.ball_x() + self.dx > 0 and \ self.ball.ball_x() + self.dx < self.width - self.ball.width(): self.ball.move_ball_relative((int(self.dx), int(self.dy))) else: self.ball.move_ball_relative((0, int(self.dy))) # speed up ball in x while key is pressed self.dx *= DDX # accelerate in y self.dy += self.ddy if self.ball.ball_y() >= self.ball_y_max: # hit the bottom self.ball.move_ball((self.ball.ball_x(), self.ball_y_max)) self._test() self.new_bounce = True if self.we_are_sharing(): if self.my_turn: # Let the next player know it is their turn. i = (self.buddies.index(self.activity.nick) + 1) % \ len(self.buddies) self.its_their_turn(self.buddies[i]) self.activity.send_event('t|%s' % (self.buddies[i])) else: if self._easter_egg_test(): self._animate() else: self.timeout = gobject.timeout_add( max(STEP_PAUSE, BOUNCE_PAUSE - self.count * STEP_PAUSE), self._move_ball) else: self.timeout = gobject.timeout_add(STEP_PAUSE, self._move_ball) def _animate(self): ''' A little Easter Egg just for fun. ''' if self.new_bounce: self.dy = self.ddy * (1 - STEPS) / 2 # initial step size self.new_bounce = False self.current_frame = 0 self.frame_counter = 0 self.ball.move_frame(self.current_frame, (self.ball.ball_x(), self.ball.ball_y())) self.ball.move_ball((self.ball.ball_x(), self.height)) gobject.idle_add(play_audio_from_file, self, self.path_to_bubbles) if self.accelerometer: fh = open(ACCELEROMETER_DEVICE) string = fh.read() xyz = string[1:-2].split(',') self.dx = float(xyz[0]) / 18. fh.close() else: self.dx = uniform(-int(DX * self.scale), int(DX * self.scale)) self.ball.move_frame_relative(self.current_frame, (int(self.dx), int(self.dy))) self.dy += self.ddy self.frame_counter += 1 self.current_frame = self.ball.next_frame(self.frame_counter) if self.ball.frame_y(self.current_frame) >= self.ball_y_max: # hit the bottom self.ball.move_ball((self.ball.ball_x(), self.ball_y_max)) self.ball.hide_frames() self._test(easter_egg=True) self.new_bounce = True self.timeout = gobject.timeout_add(BOUNCE_PAUSE, self._move_ball) else: gobject.timeout_add(STEP_PAUSE, self._animate) def add_fraction(self, string): ''' Add a new challenge; set bar to 2x demominator ''' numden = string.split('/', 2) self.challenges.append([string, int(numden[1]), 0]) def _choose_a_fraction(self): ''' Select a new fraction challenge from the table ''' if not self.we_are_sharing(): self.n = int(uniform(0, len(self.challenges))) fstr = self.challenges[self.n][0] saw_a_fraction = False if '/' in fstr: # fraction numden = fstr.split('/', 2) self.fraction = float(numden[0].strip()) / float(numden[1].strip()) saw_a_fraction = True elif '%' in fstr: # percentage self.fraction = float(fstr.strip().strip('%').strip()) / 100. else: # To do: add support for decimals (using locale) _logger.debug('Could not parse challenge (%s)', fstr) fstr = '1/2' self.fraction = 0.5 saw_a_fraction = True if self.mode == 'fractions': if saw_a_fraction: self.label = fstr else: self.label = fstr.strip().strip('%').strip() + '/100' else: # percentage if not saw_a_fraction: self.label = fstr else: self.label = str(int(self.fraction * 100 + 0.5)) + '%' self.activity.reset_label(self.label) self.ball.ball.set_label(self.label) self.bar.hide_bars() if self.expert: # Show two-segment bar in expert mode self.current_bar = self.bar.get_bar(2) else: if self.mode == 'fractions': nseg = self.challenges[self.n][1] else: nseg = 10 # percentages # generate new bar on demand self.current_bar = self.bar.get_bar(nseg) self.current_bar.move((self.bar.bar_x(), self.bar.bar_y())) self.current_bar.set_layer(0) def _easter_egg_test(self): ''' Test to see if we show the Easter Egg ''' delta = self.ball.width() / 8 x = self.ball.ball_x() + self.ball.width() / 2 f = self.bar.width() * self.easter_egg / 100. if x > f - delta and x < f + delta: return True else: return False def _test(self, easter_egg=False): ''' Test to see if we estimated correctly ''' self.timeout = None delta = self.ball.width() / 4 x = self.ball.ball_x() + self.ball.width() / 2 f = self.ball.width() / 2 + int(self.fraction * self.bar.width()) self.bar.mark.move( (int(f - self.bar.mark_width() / 2), self.bar.bar_y() - 2)) if self.challenges[self.n][2] == 0: # label the column spr = Sprite(self.sprites, 0, 0, self.blank_graphic) spr.set_label(self.label) spr.move((int(self.n * 25), 0)) spr.set_layer(-1) self.challenges[self.n][2] += 1 if x > f - delta and x < f + delta: if not easter_egg: spr = Sprite(self.sprites, 0, 0, self.smiley_graphic) self.correct += 1 gobject.idle_add(play_audio_from_file, self, self.path_to_success) else: if not easter_egg: spr = Sprite(self.sprites, 0, 0, self.frown_graphic) gobject.idle_add(play_audio_from_file, self, self.path_to_failure) if easter_egg: spr = Sprite(self.sprites, 0, 0, self.egg_graphic) spr.move((int(self.n * 25), int(self.challenges[self.n][2] * 25))) spr.set_layer(-1) # after enough correct answers, up the difficulty if self.correct == len(self.challenges) * 2: self.challenge += 1 if self.challenge < len(CHALLENGES): for challenge in CHALLENGES[self.challenge]: self.challenges.append(challenge) else: self.expert = True self.count += 1 self.dx = 0. # stop horizontal movement between bounces def _keypress_cb(self, area, event): ''' Keypress: moving the slides with the arrow keys ''' k = gtk.gdk.keyval_name(event.keyval) if k in ['h', 'Left', 'KP_Left']: self.dx = -DX * self.scale elif k in ['l', 'Right', 'KP_Right']: self.dx = DX * self.scale elif k in ['KP_Page_Up', 'Return']: self._choose_a_fraction() self._move_ball() else: self.dx = 0. return True def _keyrelease_cb(self, area, event): ''' Keyrelease: stop horizontal movement ''' self.dx = 0. return True def _expose_cb(self, win, event): ''' Callback to handle window expose events ''' self.sprites.redraw_sprites(event.area) return True def _destroy_cb(self, win, event): ''' Callback to handle quit ''' gtk.main_quit()
class Game(): ''' OLPC XO man color changer designed in memory of Nat Jacobson ''' def __init__(self, canvas, parent=None, mycolors=['#A0FFA0', '#FF8080']): self._activity = parent self.colors = [mycolors[0]] self.colors.append(mycolors[1]) self._canvas = canvas if parent is not None: parent.show_all() self._parent = parent self._canvas.connect("draw", self.__draw_cb) self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.connect("button-press-event", self._button_press_cb) self._canvas.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK) self._canvas.connect('button-release-event', self._button_release_cb) self._canvas.add_events(Gdk.EventMask.POINTER_MOTION_MASK) self._canvas.connect("motion-notify-event", self._mouse_move_cb) self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - GRID_CELL_SIZE self._scale = self._width / 1200. self.press = None self.dragpos = [0, 0] self.startpos = [0, 0] self._dot_cache = {} self._xo_cache = {} self._radius = 22.5 self._stroke_width = 9.5 # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) self._dots = [] self._xo_man = None self._generate_bg('#FFF') # First dot, starting angle self._cxy = [self._width / 2, self._height / 2] self._xy = [ self._width / 2 + 120 * self._scale, self._height / 2 - self._radius * self._scale ] self._angle = 0 self._dot_size_plus = self._radius * 3 * self._scale self._min = -self._dot_size_plus / 3 self._max = self._height - (self._dot_size_plus / 2.2) self._zones = [] self._calc_zones() self._generate_spiral() def _calc_zones(self): for color in colors: rgb1 = _from_hex(color[0]) rgb2 = _from_hex(color[1]) dv = _contrast(rgb1, rgb2) dh = _delta_hue(rgb1, rgb2) self._zones.append(_zone(dv, dh)) def _calc_next_dot_position(self): ''' calculate spiral coordinates ''' dx = self._xy[0] - self._cxy[0] dy = self._xy[1] - self._cxy[1] r = sqrt(dx * dx + dy * dy) c = 2 * r * pi a = atan2(dy, dx) da = (self._dot_size_plus / c) * 2 * pi a += da r += self._dot_size_plus / (c / self._dot_size_plus) self._xy[0] = r * cos(a) + self._cxy[0] self._xy[1] = r * sin(a) + self._cxy[1] if self._xy[1] < self._min or self._xy[1] > self._max: self._calc_next_dot_position() def _generate_spiral(self): ''' Make a new set of dots for a sprial ''' for z in range(4): for i in range(len(colors)): if self._zones[i] == z: self._dots.append( Sprite(self._sprites, self._xy[0], self._xy[1], self._new_dot(colors[i]))) self._dots[-1].type = i self._calc_next_dot_position() if self._xo_man is None: x = 510 * self._scale y = 280 * self._scale self._xo_man = Sprite(self._sprites, x, y, self._new_xo_man(self.colors)) self._xo_man.type = None def move_dot(self, i, x, y): self._dots[i].move((x, y)) def get_dot_xy(self, i): return self._dots[i].get_xy() def move_xo_man(self, x, y): self._xo_man.move((x, y)) def get_xo_man_xy(self): return self._xo_man.get_xy() def rotate(self): x, y = self._dots[0].get_xy() for i in range(len(colors) - 1): self._dots[i].move(self._dots[i + 1].get_xy()) self._dots[-1].move((x, y)) def _generate_bg(self, color): ''' a background color ''' self._bg = Sprite(self._sprites, 0, 0, self._new_background(color)) self._bg.set_layer(0) self._bg.type = None def adj_background(self, color): ''' Change background ''' self._bg.set_image(self._new_background(color)) self._bg.set_layer(0) def _button_press_cb(self, win, event): win.grab_focus() x, y = map(int, event.get_coords()) self.dragpos = [x, y] spr = self._sprites.find_sprite((x, y)) if spr == None or spr == self._bg: return self.startpos = spr.get_xy() self.press = spr def _mouse_move_cb(self, win, event): """ Drag a rule with the mouse. """ if self.press is None: self.dragpos = [0, 0] return True win.grab_focus() x, y = map(int, event.get_coords()) dx = x - self.dragpos[0] dy = y - self.dragpos[1] self.press.move_relative((dx, dy)) self.dragpos = [x, y] def _button_release_cb(self, win, event): if self.press == None: return True if _distance(self.press.get_xy(), self.startpos) < 20: if type(self.press.type) == int: self.i = self.press.type self._new_surface() self.press.move(self.startpos) self.press = None def _new_surface(self): self.colors[0] = colors[self.i][0] self.colors[1] = colors[self.i][1] self._xo_man.set_image(self._new_xo_man(colors[self.i])) self._xo_man.set_layer(100) def __draw_cb(self, canvas, cr): self._sprites.redraw_sprites(cr=cr) def do_expose_event(self, event): ''' Handle the expose-event by drawing ''' # Restrict Cairo to the exposed area cr = self._canvas.window.cairo_create() cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): Gtk.main_quit() def _new_dot(self, color): ''' generate a dot of a color color ''' if True: # not color in self._dot_cache: self._stroke = color[0] self._fill = color[1] self._svg_width = int(60 * self._scale) self._svg_height = int(60 * self._scale) pixbuf = svg_str_to_pixbuf( self._header() + \ '<circle cx="%f" cy="%f" r="%f" stroke="%s" fill="%s" \ stroke-width="%f" visibility="visible" />' % ( 30 * self._scale, 30 * self._scale, self._radius * self._scale, self._stroke, self._fill, self._stroke_width * self._scale) + \ self._footer()) surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self._svg_width, self._svg_height) context = cairo.Context(surface) Gdk.cairo_set_source_pixbuf(context, pixbuf, 0, 0) context.rectangle(0, 0, self._svg_width, self._svg_height) context.fill() # self._dot_cache[color] = surface return surface # self._dot_cache[color] def _new_background(self, color): ''' Background color ''' self._svg_width = int(self._width) self._svg_height = int(self._height) string = \ self._header() + \ '<rect width="%f" height="%f" x="%f" \ y="%f" fill="%s" stroke="none" visibility="visible" />' % ( self._width, self._height, 0, 0, color) + \ self._footer() pixbuf = svg_str_to_pixbuf(string) surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self._svg_width, self._svg_height) context = cairo.Context(surface) Gdk.cairo_set_source_pixbuf(context, pixbuf, 0, 0) context.rectangle(0, 0, self._svg_width, self._svg_height) context.fill() return surface def _new_xo_man(self, color): ''' generate a xo-man of a color color ''' if True: # not color in self._xo_cache: self._stroke = color[0] self._fill = color[1] self._svg_width = int(240. * self._scale) self._svg_height = int(260. * self._scale) string = \ self._header() + \ '<g>' + \ '<g id="XO">' + \ '<path id="Line1" d="M%f,%f C%f,%f %f,%f %f,%f" stroke="%s" \ stroke-width="%f" stroke-linecap="round" fill="none" visibility="visible" />' \ % ( 165.5 * self._scale, 97 * self._scale, 120 * self._scale, 140.5 * self._scale, 120 * self._scale, 140.5 * self._scale, 74.5 * self._scale, 188 * self._scale, self._stroke, 37 * self._scale) + \ '<path id="Line2" d="M%f,%f C%f,%f %f,%f %f,%f" stroke="%s" \ stroke-width="%f" stroke-linecap="round" fill="none" visibility="visible" />' \ % ( 165.5 * self._scale, 188 * self._scale, 120 * self._scale, 140.5 * self._scale, 120 * self._scale, 140.5 * self._scale, 74.5 * self._scale, 97 * self._scale, self._stroke, 37 * self._scale) + \ '<path id="Fill1" d="M%f,%f C%f,%f %f,%f %f,%f" stroke="%s" \ stroke-width="%f" stroke-linecap="round" fill="none" visibility="visible" />' \ % ( 165.5 * self._scale, 97 * self._scale, 120 * self._scale, 140.5 * self._scale, 120 * self._scale, 140.5 * self._scale, 74.5 * self._scale, 188 * self._scale, self._fill, 17 * self._scale) + \ '<path id="Fill2" d="M%f,%f C%f,%f %f,%f %f,%f" stroke="%s" \ stroke-width="%f" stroke-linecap="round" fill="none" visibility="visible" />' \ % ( 165.5 * self._scale, 188 * self._scale, 120 * self._scale, 140.5 * self._scale, 120 * self._scale, 140.5 * self._scale, 74.5 * self._scale, 97 * self._scale, self._fill, 17 * self._scale) + \ '<circle id="Circle" cx="%f" cy="%f" r="%f" \ fill="%s" stroke="%s" stroke-width="%f" visibility="visible" />' % ( 120 * self._scale, 61.5 * self._scale, 27.5 * self._scale, self._fill, self._stroke, 11 * self._scale) + \ '</g></g>' + \ self._footer() pixbuf = svg_str_to_pixbuf(string) surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self._svg_width, self._svg_height) context = cairo.Context(surface) Gdk.cairo_set_source_pixbuf(context, pixbuf, 0, 0) context.rectangle(0, 0, self._svg_width, self._svg_height) context.fill() # self._xo_cache[color] = surface return surface # self._xo_cache[color] def _header(self): return '<svg\n' + 'xmlns:svg="http:#www.w3.org/2000/svg"\n' + \ 'xmlns="http://www.w3.org/2000/svg"\n' + \ 'xmlns:xlink="http://www.w3.org/1999/xlink"\n' + \ 'version="1.1"\n' + 'width="' + str(self._svg_width) + '"\n' + \ 'height="' + str(self._svg_height) + '">\n' def _footer(self): return '</svg>\n'
class Game(): """ The game play -- called from within Sugar or GNOME """ def __init__(self, canvas, path, parent=None): """ Initialize the playing surface """ self.path = path self.activity = parent # starting from command line # we have to do all the work that was done in CardSortActivity.py if parent is None: self.sugar = False self.canvas = canvas # starting from Sugar else: self.sugar = True self.canvas = canvas parent.show_all() self.canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self.canvas.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK) self.canvas.connect("draw", self._draw_cb) self.canvas.connect("button-press-event", self._button_press_cb) self.canvas.connect("button-release-event", self._button_release_cb) self.canvas.connect("key_press_event", self._keypress_cb) self.width = Gdk.Screen.width() self.height = Gdk.Screen.height() - GRID_CELL_SIZE self.card_dim = CARD_DIM self.scale = 0.6 * self.height / (self.card_dim * 3) # Initialize the sprite repository self.sprites = Sprites(self.canvas) # Initialize the grid self.mode = 'rectangle' self.grid = Grid(self, self.mode) self.bounds = LEVEL_BOUNDS[0] self.level = 0 # Start solving the puzzle self.press = None self.release = None self.start_drag = [0, 0] def _button_press_cb(self, win, event): win.grab_focus() x, y = list(map(int, event.get_coords())) self.start_drag = [x, y] spr = self.sprites.find_sprite((x, y)) if spr is None: self.press = None self.release = None return True # take note of card under button press self.press = spr return True def _button_release_cb(self, win, event): win.grab_focus() x, y = list(map(int, event.get_coords())) spr = self.sprites.find_sprite((x, y)) if spr is None: self.press = None self.release = None return True # take note of card under button release self.release = spr # if press and release are the same card (click), then rotate if self.press == self.release: self.press.set_layer(0) self.grid.card_table[self.grid.grid[self.grid.spr_to_i( self.press)]].rotate_ccw() if self.mode == 'hexagon': # Rotate a second time self.grid.card_table[self.grid.grid[self.grid.spr_to_i( self.press)]].rotate_ccw() self.press.set_layer(100) else: self.grid.swap(self.press, self.release, self.mode) self.press = None self.release = None if self.test() == True: if self.level < 2: GLib.timeout_add(3000, self.activity.change_play_level_cb, None) return True def _keypress_cb(self, area, event): """ Keypress is used to ... """ k = Gdk.keyval_name(event.keyval) def _expose_cb(self, win, event): ''' Callback to handle window expose events ''' self.do_expose_event(event) return True def _draw_cb(self, canvas, cr): self.sprites.redraw_sprites(cr=cr) def do_expose_event(self, event): ''' Handle the expose-event by drawing ''' # Restrict Cairo to the exposed area cr = self.canvas.props.window.cairo_create() cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list self.sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): Gtk.main_quit() def mask(self, level): """ mask out cards not on play level """ self.grid.hide_list(MASKS[level]) self.bounds = LEVEL_BOUNDS[level] self.level = level def test(self): """ Test the grid to see if the level is solved """ if self.mode != 'rectangle': return False for i in range(24): if i not in MASKS[self.level]: if not self.test_card(i): return False return True def test_card(self, i): """ Test a card with its neighbors; tests are bounded by the level """ row = int(i / 6) col = i % 6 if row > self.bounds[0][0] and row <= self.bounds[0][1]: if C[self.grid.grid[i]][rotate_index(0, self.grid.card_table[self.grid.grid[i]].orientation)] != \ C[self.grid.grid[i - 6]][rotate_index(1, self.grid.card_table[self.grid.grid[i - 6]].orientation)]: return False if C[self.grid.grid[i]][rotate_index(3, self.grid.card_table[self.grid.grid[i]].orientation)] != \ C[self.grid.grid[i - 6]][rotate_index(2, self.grid.card_table[self.grid.grid[i - 6]].orientation)]: return False if col > self.bounds[2][0] and col <= self.bounds[2][1]: if C[self.grid.grid[i]][rotate_index(3, self.grid.card_table[self.grid.grid[i]].orientation)] != \ C[self.grid.grid[i - 1]][rotate_index(0, self.grid.card_table[self.grid.grid[i - 1]].orientation)]: return False if C[self.grid.grid[i]][rotate_index(2, self.grid.card_table[self.grid.grid[i]].orientation)] != \ C[self.grid.grid[i - 1]][rotate_index(1, self.grid.card_table[self.grid.grid[i - 1]].orientation)]: return False return True
class BBoardActivity(activity.Activity): ''' Make a slideshow from starred Journal entries. ''' def __init__(self, handle): ''' Initialize the toolbars and the work surface ''' super(BBoardActivity, self).__init__(handle) self.datapath = get_path(activity, 'instance') self._hw = get_hardware() self._playback_buttons = {} self._audio_recordings = {} self.colors = profile.get_color().to_string().split(',') self._setup_toolbars() self._setup_canvas() self.slides = [] self._setup_workspace() self._buddies = [profile.get_nick_name()] self._setup_presence_service() self._thumbs = [] self._thumbnail_mode = False self._recording = False self._grecord = None self._alert = None self._dirty = False def _setup_canvas(self): ''' Create a canvas ''' self._canvas = gtk.DrawingArea() self._canvas.set_size_request(int(gtk.gdk.screen_width()), int(gtk.gdk.screen_height())) self._canvas.show() self.set_canvas(self._canvas) self.show_all() self._canvas.set_flags(gtk.CAN_FOCUS) self._canvas.add_events(gtk.gdk.BUTTON_PRESS_MASK) self._canvas.add_events(gtk.gdk.POINTER_MOTION_MASK) self._canvas.add_events(gtk.gdk.BUTTON_RELEASE_MASK) self._canvas.add_events(gtk.gdk.KEY_PRESS_MASK) self._canvas.connect("expose-event", self._expose_cb) self._canvas.connect("button-press-event", self._button_press_cb) self._canvas.connect("button-release-event", self._button_release_cb) self._canvas.connect("motion-notify-event", self._mouse_move_cb) def _setup_workspace(self): ''' Prepare to render the datastore entries. ''' # Use the lighter color for the text background if lighter_color(self.colors) == 0: tmp = self.colors[0] self.colors[0] = self.colors[1] self.colors[1] = tmp self._width = gtk.gdk.screen_width() self._height = gtk.gdk.screen_height() self._scale = gtk.gdk.screen_height() / 900. if not HAVE_TOOLBOX and self._hw[0:2] == 'xo': titlef = 18 descriptionf = 12 else: titlef = 36 descriptionf = 24 self._find_starred() for ds in self.dsobjects: if 'title' in ds.metadata: title = ds.metadata['title'] else: title = None pixbuf = None media_object = False mimetype = None if 'mime_type' in ds.metadata: mimetype = ds.metadata['mime_type'] if mimetype[0:5] == 'image': pixbuf = gtk.gdk.pixbuf_new_from_file_at_size( ds.file_path, MAXX, MAXY) # ds.file_path, 300, 225) media_object = True else: pixbuf = get_pixbuf_from_journal(ds, MAXX, MAXY) # 300, 225) if 'description' in ds.metadata: desc = ds.metadata['description'] else: desc = None self.slides.append(Slide(True, ds.object_id, self.colors, title, pixbuf, desc)) # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) self._help = Sprite( self._sprites, int((self._width - int(PREVIEWW * self._scale)) / 2), int(PREVIEWY * self._scale), gtk.gdk.pixbuf_new_from_file_at_size( os.path.join(activity.get_bundle_path(), 'help.png'), int(PREVIEWW * self._scale), int(PREVIEWH * self._scale))) self._help.hide() self._genblanks(self.colors) self._title = Sprite(self._sprites, 0, 0, self._title_pixbuf) self._title.set_label_attributes(int(titlef * self._scale), rescale=False) self._preview = Sprite(self._sprites, int((self._width - int(PREVIEWW * self._scale)) / 2), int(PREVIEWY * self._scale), self._preview_pixbuf) self._description = Sprite(self._sprites, int(DESCRIPTIONX * self._scale), int(DESCRIPTIONY * self._scale), self._desc_pixbuf) self._description.set_label_attributes(int(descriptionf * self._scale)) self._my_canvas = Sprite(self._sprites, 0, 0, self._canvas_pixbuf) self._my_canvas.set_layer(BOTTOM) self._clear_screen() self.i = 0 self._show_slide() self._playing = False self._rate = 10 def _genblanks(self, colors): ''' Need to cache these ''' self._title_pixbuf = svg_str_to_pixbuf( genblank(self._width, int(TITLEH * self._scale), colors)) self._preview_pixbuf = svg_str_to_pixbuf( genblank(int(PREVIEWW * self._scale), int(PREVIEWH * self._scale), colors)) self._desc_pixbuf = svg_str_to_pixbuf( genblank(int(self._width - (2 * DESCRIPTIONX * self._scale)), int(DESCRIPTIONH * self._scale), colors)) self._canvas_pixbuf = svg_str_to_pixbuf( genblank(self._width, self._height, (colors[0], colors[0]))) def _setup_toolbars(self): ''' Setup the toolbars. ''' self.max_participants = 6 if HAVE_TOOLBOX: toolbox = ToolbarBox() # Activity toolbar activity_button_toolbar = ActivityToolbarButton(self) toolbox.toolbar.insert(activity_button_toolbar, 0) activity_button_toolbar.show() self.set_toolbar_box(toolbox) toolbox.show() self.toolbar = toolbox.toolbar self.record_toolbar = gtk.Toolbar() record_toolbar_button = ToolbarButton( label=_('Record a sound'), page=self.record_toolbar, icon_name='media-audio') self.record_toolbar.show_all() record_toolbar_button.show() toolbox.toolbar.insert(record_toolbar_button, -1) else: # Use pre-0.86 toolbar design primary_toolbar = gtk.Toolbar() toolbox = activity.ActivityToolbox(self) self.set_toolbox(toolbox) toolbox.add_toolbar(_('Page'), primary_toolbar) self.record_toolbar = gtk.Toolbar() toolbox.add_toolbar(_('Record'), self.record_toolbar) toolbox.show() toolbox.set_current_toolbar(1) self.toolbar = primary_toolbar self._prev_button = button_factory( 'go-previous-inactive', self.toolbar, self._prev_cb, tooltip=_('Prev slide'), accelerator='<Ctrl>P') self._next_button = button_factory( 'go-next', self.toolbar, self._next_cb, tooltip=_('Next slide'), accelerator='<Ctrl>N') separator_factory(self.toolbar) slide_button = radio_factory('slide-view', self.toolbar, self._slides_cb, group=None, tooltip=_('Slide view')) radio_factory('thumbs-view', self.toolbar, self._thumbs_cb, tooltip=_('Thumbnail view'), group=slide_button) button_factory('view-fullscreen', self.toolbar, self.do_fullscreen_cb, tooltip=_('Fullscreen'), accelerator='<Alt>Return') separator_factory(self.toolbar) journal_button = button_factory( 'write-journal', self.toolbar, self._do_journal_cb, tooltip=_('Update description')) self._palette = journal_button.get_palette() msg_box = gtk.HBox() sw = gtk.ScrolledWindow() sw.set_size_request(int(gtk.gdk.screen_width() / 2), 2 * style.GRID_CELL_SIZE) sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self._text_view = gtk.TextView() self._text_view.set_left_margin(style.DEFAULT_PADDING) self._text_view.set_right_margin(style.DEFAULT_PADDING) self._text_view.set_wrap_mode(gtk.WRAP_WORD_CHAR) self._text_view.connect('focus-out-event', self._text_view_focus_out_event_cb) sw.add(self._text_view) sw.show() msg_box.pack_start(sw, expand=False) msg_box.show_all() self._palette.set_content(msg_box) label_factory(self.record_toolbar, _('Record a sound') + ':') self._record_button = button_factory( 'media-record', self.record_toolbar, self._record_cb, tooltip=_('Start recording')) separator_factory(self.record_toolbar) # Look to see if we have audio previously recorded obj_id = self._get_audio_obj_id() dsobject = self._search_for_audio_note(obj_id) if dsobject is not None: _logger.debug('Found previously recorded audio') self._add_playback_button(profile.get_nick_name(), self.colors, dsobject.file_path) if HAVE_TOOLBOX: button_factory('system-restart', activity_button_toolbar, self._resend_cb, tooltip=_('Refresh')) separator_factory(activity_button_toolbar) self._save_pdf = button_factory( 'save-as-pdf', activity_button_toolbar, self._save_as_pdf_cb, tooltip=_('Save as PDF')) else: separator_factory(self.toolbar) self._save_pdf = button_factory( 'save-as-pdf', self.toolbar, self._save_as_pdf_cb, tooltip=_('Save as PDF')) if HAVE_TOOLBOX: separator_factory(toolbox.toolbar, True, False) stop_button = StopButton(self) stop_button.props.accelerator = '<Ctrl>q' toolbox.toolbar.insert(stop_button, -1) stop_button.show() def _do_journal_cb(self, button): self._dirty = True if self._palette: if not self._palette.is_up(): self._palette.popup(immediate=True, state=self._palette.SECONDARY) else: self._palette.popdown(immediate=True) return def _text_view_focus_out_event_cb(self, widget, event): buffer = self._text_view.get_buffer() start_iter = buffer.get_start_iter() end_iter = buffer.get_end_iter() self.slides[self.i].desc = buffer.get_text(start_iter, end_iter) self._show_slide() def _destroy_cb(self, win, event): ''' Clean up on the way out. ''' gtk.main_quit() def _find_starred(self): ''' Find all the favorites in the Journal. ''' self.dsobjects, nobjects = datastore.find({'keep': '1'}) _logger.debug('found %d starred items', nobjects) def _prev_cb(self, button=None): ''' The previous button has been clicked; goto previous slide. ''' if self.i > 0: self.i -= 1 self._show_slide(direction=-1) def _next_cb(self, button=None): ''' The next button has been clicked; goto next slide. ''' if self.i < len(self.slides) - 1: self.i += 1 self._show_slide() def _save_as_pdf_cb(self, button=None): ''' Export an PDF version of the slideshow to the Journal. ''' _logger.debug('saving to PDF...') if 'description' in self.metadata: tmp_file = save_pdf(self, self._buddies, description=self.metadata['description']) else: tmp_file = save_pdf(self, self._buddies) _logger.debug('copying PDF file to Journal...') dsobject = datastore.create() dsobject.metadata['title'] = profile.get_nick_name() + ' ' + \ _('Bboard') dsobject.metadata['icon-color'] = profile.get_color().to_string() dsobject.metadata['mime_type'] = 'application/pdf' dsobject.set_file_path(tmp_file) dsobject.metadata['activity'] = 'org.laptop.sugar.ReadActivity' datastore.write(dsobject) dsobject.destroy() return def _clear_screen(self): ''' Clear the screen to the darker of the two user colors. ''' self._title.hide() self._preview.hide() self._description.hide() if hasattr(self, '_thumbs'): for thumbnail in self._thumbs: thumbnail[0].hide() self.invalt(0, 0, self._width, self._height) # Reset drag settings self._press = None self._release = None self._dragpos = [0, 0] self._total_drag = [0, 0] self.last_spr_moved = None def _update_colors(self): ''' Match the colors to those of the slide originator. ''' if len(self.slides) == 0: return self._genblanks(self.slides[self.i].colors) self._title.set_image(self._title_pixbuf) self._preview.set_image(self._preview_pixbuf) self._description.set_image(self._desc_pixbuf) self._my_canvas.set_image(self._canvas_pixbuf) def _show_slide(self, direction=1): ''' Display a title, preview image, and decription for slide. ''' self._clear_screen() self._update_colors() if len(self.slides) == 0: self._prev_button.set_icon('go-previous-inactive') self._next_button.set_icon('go-next-inactive') self._description.set_label( _('Do you have any items in your Journal starred?')) self._help.set_layer(TOP) self._description.set_layer(MIDDLE) return if self.i == 0: self._prev_button.set_icon('go-previous-inactive') else: self._prev_button.set_icon('go-previous') if self.i == len(self.slides) - 1: self._next_button.set_icon('go-next-inactive') else: self._next_button.set_icon('go-next') pixbuf = self.slides[self.i].pixbuf if pixbuf is not None: self._preview.set_shape(pixbuf.scale_simple( int(PREVIEWW * self._scale), int(PREVIEWH * self._scale), gtk.gdk.INTERP_NEAREST)) self._preview.set_layer(MIDDLE) else: if self._preview is not None: self._preview.hide() self._title.set_label(self.slides[self.i].title) self._title.set_layer(MIDDLE) if self.slides[self.i].desc is not None: self._description.set_label(self.slides[self.i].desc) self._description.set_layer(MIDDLE) text_buffer = gtk.TextBuffer() text_buffer.set_text(self.slides[self.i].desc) self._text_view.set_buffer(text_buffer) else: self._description.set_label('') self._description.hide() def _add_playback_button(self, nick, colors, audio_file): ''' Add a toolbar button for this audio recording ''' if nick not in self._playback_buttons: self._playback_buttons[nick] = button_factory( 'xo-chat', self.record_toolbar, self._playback_recording_cb, cb_arg=nick, tooltip=_('Audio recording by %s') % (nick)) xocolor = XoColor('%s,%s' % (colors[0], colors[1])) icon = Icon(icon_name='xo-chat', xo_color=xocolor) icon.show() self._playback_buttons[nick].set_icon_widget(icon) self._playback_buttons[nick].show() self._audio_recordings[nick] = audio_file def _slides_cb(self, button=None): if self._thumbnail_mode: self._thumbnail_mode = False self.i = self._current_slide self._show_slide() def _thumbs_cb(self, button=None): ''' Toggle between thumbnail view and slideshow view. ''' if not self._thumbnail_mode: self._current_slide = self.i self._thumbnail_mode = True self._clear_screen() self._prev_button.set_icon('go-previous-inactive') self._next_button.set_icon('go-next-inactive') n = int(ceil(sqrt(len(self.slides)))) if n > 0: w = int(self._width / n) else: w = self._width h = int(w * 0.75) # maintain 4:3 aspect ratio x_off = int((self._width - n * w) / 2) x = x_off y = 0 self._thumbs = [] for i in range(len(self.slides)): self._show_thumb(i, x, y, w, h) x += w if x + w > self._width: x = x_off y += h self.i = 0 # Reset position in slideshow to the beginning return False def _show_thumb(self, i, x, y, w, h): ''' Display a preview image and title as a thumbnail. ''' pixbuf = self.slides[i].pixbuf if pixbuf is not None: pixbuf_thumb = pixbuf.scale_simple( int(w), int(h), gtk.gdk.INTERP_TILES) else: pixbuf_thumb = svg_str_to_pixbuf( genblank(int(w), int(h), self.slides[i].colors)) # Create a Sprite for this thumbnail self._thumbs.append([Sprite(self._sprites, x, y, pixbuf_thumb), x, y, i]) self._thumbs[i][0].set_image( svg_str_to_pixbuf(svg_rectangle(int(w), int(h), self.slides[i].colors)), i=1) self._thumbs[i][0].set_layer(TOP) def _expose_cb(self, win, event): ''' Callback to handle window expose events ''' self.do_expose_event(event) return True # Handle the expose-event by drawing def do_expose_event(self, event): # Create the cairo context cr = self.canvas.window.cairo_create() # Restrict Cairo to the exposed area; avoid extra work cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list self._sprites.redraw_sprites(cr=cr) def write_file(self, file_path): ''' Clean up ''' if self._dirty: self._save_descriptions_cb() self._dirty = False if os.path.exists(os.path.join(self.datapath, 'output.ogg')): os.remove(os.path.join(self.datapath, 'output.ogg')) def do_fullscreen_cb(self, button): ''' Hide the Sugar toolbars. ''' self.fullscreen() def invalt(self, x, y, w, h): ''' Mark a region for refresh ''' self._canvas.window.invalidate_rect( gtk.gdk.Rectangle(int(x), int(y), int(w), int(h)), False) def _spr_to_thumb(self, spr): ''' Find which entry in the thumbnails table matches spr. ''' for i, thumb in enumerate(self._thumbs): if spr == thumb[0]: return i return -1 def _spr_is_thumbnail(self, spr): ''' Does spr match an entry in the thumbnails table? ''' if self._spr_to_thumb(spr) == -1: return False else: return True def _button_press_cb(self, win, event): ''' The mouse button was pressed. Is it on a thumbnail sprite? ''' win.grab_focus() x, y = map(int, event.get_coords()) self._dragpos = [x, y] self._total_drag = [0, 0] spr = self._sprites.find_sprite((x, y)) self._press = None self._release = None # Are we clicking on a thumbnail? if not self._spr_is_thumbnail(spr): return False self.last_spr_moved = spr self._press = spr self._press.set_layer(DRAG) return False def _mouse_move_cb(self, win, event): """ Drag a thumbnail with the mouse. """ spr = self._press if spr is None: self._dragpos = [0, 0] return False win.grab_focus() x, y = map(int, event.get_coords()) dx = x - self._dragpos[0] dy = y - self._dragpos[1] spr.move_relative([dx, dy]) # Also move the star self._dragpos = [x, y] self._total_drag[0] += dx self._total_drag[1] += dy return False def _button_release_cb(self, win, event): ''' Button event is used to swap slides or goto next slide. ''' win.grab_focus() self._dragpos = [0, 0] x, y = map(int, event.get_coords()) if self._thumbnail_mode: if self._press is None: return # Drop the dragged thumbnail below the other thumbnails so # that you can find the thumbnail beneath it. self._press.set_layer(UNDRAG) i = self._spr_to_thumb(self._press) spr = self._sprites.find_sprite((x, y)) if self._spr_is_thumbnail(spr): self._release = spr # If we found a thumbnail and it is not the one we # dragged, swap their positions. if not self._press == self._release: j = self._spr_to_thumb(self._release) self._thumbs[i][0] = self._release self._thumbs[j][0] = self._press tmp = self.slides[i] self.slides[i] = self.slides[j] self.slides[j] = tmp self._thumbs[j][0].move((self._thumbs[j][1], self._thumbs[j][2])) self._thumbs[i][0].move((self._thumbs[i][1], self._thumbs[i][2])) self._press.set_layer(TOP) self._press = None self._release = None else: self._next_cb() return False def _unit_combo_cb(self, arg=None): ''' Read value of predefined conversion factors from combo box ''' if hasattr(self, '_unit_combo'): active = self._unit_combo.get_active() if active in UNIT_DICTIONARY: self._rate = UNIT_DICTIONARY[active][1] def _record_cb(self, button=None): ''' Start/stop audio recording ''' if self._grecord is None: _logger.debug('setting up grecord') self._grecord = Grecord(self) if self._recording: # Was recording, so stop (and save?) _logger.debug('recording...True. Preparing to save.') self._grecord.stop_recording_audio() self._recording = False self._record_button.set_icon('media-record') self._record_button.set_tooltip(_('Start recording')) _logger.debug('Autosaving recording') self._notify(title=_('Save recording')) gobject.timeout_add(100, self._wait_for_transcoding_to_finish) else: # Wasn't recording, so start _logger.debug('recording...False. Start recording.') self._grecord.record_audio() self._recording = True self._record_button.set_icon('media-recording') self._record_button.set_tooltip(_('Stop recording')) def _wait_for_transcoding_to_finish(self, button=None): while not self._grecord.transcoding_complete(): time.sleep(1) if self._alert is not None: self.remove_alert(self._alert) self._alert = None self._save_recording() def _playback_recording_cb(self, button=None, nick=profile.get_nick_name()): ''' Play back current recording ''' _logger.debug('Playback current recording from %s...' % (nick)) if nick in self._audio_recordings: play_audio_from_file(self._audio_recordings[nick]) return def _get_audio_obj_id(self): ''' Find unique name for audio object ''' if 'activity_id' in self.metadata: obj_id = self.metadata['activity_id'] else: obj_id = _('Bulletin Board') _logger.debug(obj_id) return obj_id def _save_recording(self): if os.path.exists(os.path.join(self.datapath, 'output.ogg')): _logger.debug('Saving recording to Journal...') obj_id = self._get_audio_obj_id() copyfile(os.path.join(self.datapath, 'output.ogg'), os.path.join(self.datapath, '%s.ogg' % (obj_id))) dsobject = self._search_for_audio_note(obj_id) if dsobject is None: dsobject = datastore.create() if dsobject is not None: _logger.debug(self.dsobjects[self.i].metadata['title']) dsobject.metadata['title'] = _('Audio recording by %s') % \ (self.metadata['title']) dsobject.metadata['icon-color'] = \ profile.get_color().to_string() dsobject.metadata['tags'] = obj_id dsobject.metadata['mime_type'] = 'audio/ogg' dsobject.set_file_path( os.path.join(self.datapath, '%s.ogg' % (obj_id))) datastore.write(dsobject) dsobject.destroy() self._add_playback_button( profile.get_nick_name(), self.colors, os.path.join(self.datapath, '%s.ogg' % (obj_id))) if hasattr(self, 'chattube') and self.chattube is not None: self._share_audio() else: _logger.debug('Nothing to save...') return def _search_for_audio_note(self, obj_id): ''' Look to see if there is already a sound recorded for this dsobject ''' dsobjects, nobjects = datastore.find({'mime_type': ['audio/ogg']}) # Look for tag that matches the target object id for dsobject in dsobjects: if 'tags' in dsobject.metadata and \ obj_id in dsobject.metadata['tags']: _logger.debug('Found audio note') return dsobject return None def _save_descriptions_cb(self, button=None): ''' Find the object in the datastore and write out the changes to the decriptions. ''' for s in self.slides: if not s.owner: continue jobject = datastore.get(s.uid) jobject.metadata['description'] = s.desc datastore.write(jobject, update_mtime=False, reply_handler=self.datastore_write_cb, error_handler=self.datastore_write_error_cb) def datastore_write_cb(self): pass def datastore_write_error_cb(self, error): _logger.error('datastore_write_error_cb: %r' % error) def _notify(self, title='', msg=''): ''' Notify user when saves are completed ''' self._alert = Alert() self._alert.props.title = title self._alert.props.msg = msg self.add_alert(self._alert) self._alert.show() def _resend_cb(self, button=None): ''' Resend slides, but only of sharing ''' if hasattr(self, 'chattube') and self.chattube is not None: self._share_slides() self._share_audio() # Serialize def _dump(self, slide): ''' Dump data for sharing.''' _logger.debug('dumping %s' % (slide.uid)) data = [slide.uid, slide.colors, slide.title, pixbuf_to_base64(activity, slide.pixbuf), slide.desc] return self._data_dumper(data) def _data_dumper(self, data): if _OLD_SUGAR_SYSTEM: return json.write(data) else: io = StringIO() jdump(data, io) return io.getvalue() def _load(self, data): ''' Load game data from the journal. ''' slide = self._data_loader(data) if len(slide) == 5: if not self._slide_search(slide[0]): _logger.debug('loading %s' % (slide[0])) self.slides.append(Slide( False, slide[0], slide[1], slide[2], base64_to_pixbuf(activity, slide[3]), slide[4])) def _slide_search(self, uid): ''' Is this slide in the list already? ''' for slide in self.slides: if slide.uid == uid: _logger.debug('skipping %s' % (slide.uid)) return True return False def _data_loader(self, data): if _OLD_SUGAR_SYSTEM: return json.read(data) else: io = StringIO(data) return jload(io) # Sharing-related methods def _setup_presence_service(self): ''' Setup the Presence Service. ''' self.pservice = presenceservice.get_instance() self.initiating = None # sharing (True) or joining (False) owner = self.pservice.get_owner() self.owner = owner self.buddies = [owner] self._share = '' self.connect('shared', self._shared_cb) self.connect('joined', self._joined_cb) def _shared_cb(self, activity): ''' Either set up initial share...''' if self._shared_activity is None: _logger.error('Failed to share or join activity ... \ _shared_activity is null in _shared_cb()') return self.initiating = True self.waiting = False _logger.debug('I am sharing...') self.conn = self._shared_activity.telepathy_conn self.tubes_chan = self._shared_activity.telepathy_tubes_chan self.text_chan = self._shared_activity.telepathy_text_chan self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal( 'NewTube', self._new_tube_cb) _logger.debug('This is my activity: making a tube...') id = self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube( SERVICE, {}) def _joined_cb(self, activity): ''' ...or join an exisiting share. ''' if self._shared_activity is None: _logger.error('Failed to share or join activity ... \ _shared_activity is null in _shared_cb()') return self.initiating = False _logger.debug('I joined a shared activity.') self.conn = self._shared_activity.telepathy_conn self.tubes_chan = self._shared_activity.telepathy_tubes_chan self.text_chan = self._shared_activity.telepathy_text_chan self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal(\ 'NewTube', self._new_tube_cb) _logger.debug('I am joining an activity: waiting for a tube...') self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes( reply_handler=self._list_tubes_reply_cb, error_handler=self._list_tubes_error_cb) self.waiting = True def _list_tubes_reply_cb(self, tubes): ''' Reply to a list request. ''' for tube_info in tubes: self._new_tube_cb(*tube_info) def _list_tubes_error_cb(self, e): ''' Log errors. ''' _logger.error('ListTubes() failed: %s', e) def _new_tube_cb(self, id, initiator, type, service, params, state): ''' Create a new tube. ''' _logger.debug('New tube: ID=%d initator=%d type=%d service=%s ' 'params=%r state=%d', id, initiator, type, service, params, state) if (type == telepathy.TUBE_TYPE_DBUS and service == SERVICE): if state == telepathy.TUBE_STATE_LOCAL_PENDING: self.tubes_chan[ \ telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id) tube_conn = TubeConnection(self.conn, self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES], id, \ group_iface=self.text_chan[telepathy.CHANNEL_INTERFACE_GROUP]) self.chattube = ChatTube(tube_conn, self.initiating, \ self.event_received_cb) if self.waiting: self._send_event('j:%s' % (profile.get_nick_name())) def event_received_cb(self, text): ''' Data is passed as tuples: cmd:text ''' _logger.debug('<<< %s' % (text[0])) if text[0] == 's': # shared journal objects e, data = text.split(':') self._load(data) elif text[0] == 'j': # Someone new has joined e, buddy = text.split(':') _logger.debug('%s has joined' % (buddy)) if buddy not in self._buddies: self._buddies.append(buddy) if self.initiating: self._send_event('J:%s' % (profile.get_nick_name())) self._share_slides() self._share_audio() elif text[0] == 'J': # Everyone must share e, buddy = text.split(':') self.waiting = False if buddy not in self._buddies: self._buddies.append(buddy) _logger.debug('%s has joined' % (buddy)) self._share_slides() self._share_audio() elif text[0] == 'a': # audio recording e, data = text.split(':') nick, colors, base64 = self._data_loader(data) path = os.path.join(activity.get_activity_root(), 'instance', 'nick.ogg') base64_to_file(activity, base64, path) self._add_playback_button(nick, colors, path) def _share_audio(self): if profile.get_nick_name() in self._audio_recordings: base64 = file_to_base64( activity, self._audio_recordings[profile.get_nick_name()]) gobject.idle_add(self._send_event, 'a:' + str( self._data_dumper([profile.get_nick_name(), self.colors, base64]))) def _share_slides(self): for s in self.slides: if s.owner: # Maybe stagger the timing of the sends? gobject.idle_add(self._send_event, 's:' + str(self._dump(s))) _logger.debug('finished sharing') def _send_event(self, text): ''' Send event through the tube. ''' if hasattr(self, 'chattube') and self.chattube is not None: _logger.debug('>>> %s' % (text[0])) self.chattube.SendText(text)
class Game(): def __init__(self, canvas, parent=None, colors=['#A0FFA0', '#FF8080']): self._activity = parent self._colors = [colors[0]] self._colors.append(colors[1]) self._colors.append('#D0D0D0') self._colors.append('#000000') self._canvas = canvas if parent is not None: parent.show_all() self._parent = parent self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.connect("draw", self.__draw_cb) self._canvas.connect("button-press-event", self._button_press_cb) self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - (GRID_CELL_SIZE * 1.5) self._scale = self._width / (10 * DOT_SIZE * 1.2) self._dot_size = int(DOT_SIZE * self._scale) self._space = int(self._dot_size / 5.) self.we_are_sharing = False self._edge = 4 self._move_list = [] # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) self._dots = [] self._generate_grid() def _generate_grid(self): ''' Make a new set of dots for a grid of size edge ''' i = 0 for y in range(self._edge): for x in range(self._edge): xoffset = int((self._width - self._edge * self._dot_size - \ (self._edge - 1) * self._space) / 2.) if i < len(self._dots): self._dots[i].move( (xoffset + x * (self._dot_size + self._space), y * (self._dot_size + self._space))) else: self._dots.append( Sprite(self._sprites, xoffset + x * (self._dot_size + self._space), y * (self._dot_size + self._space), self._new_dot(self._colors[0]))) self._dots[i].type = 0 self._dots[-1].set_label_attributes(40) i += 1 # and initialize a few variables we'll need. self._all_clear() def _all_clear(self): ''' Things to reinitialize when starting up a new game. ''' self._move_list = [] # Clear dots for dot in self._dots: dot.type = 0 dot.set_shape(self._new_dot(self._colors[0])) dot.set_label('') def _initiating(self): return self._activity.initiating def more_dots(self): ''' Enlarge the grid ''' if self._edge < MAX: self._edge += 1 self._generate_grid() self.new_game() def new_game(self): ''' Start a new game. ''' self._all_clear() # Fill in a few dots to start for i in range(MAX * 2): self._flip_them(int(uniform(0, self._edge * self._edge))) if self.we_are_sharing: _logger.debug('sending a new game') self._parent.send_new_game() def restore_game(self, dot_list, move_list): ''' Restore a game from the Journal or share ''' edge = int(sqrt(len(dot_list))) if edge > MAX: edge = MAX while self._edge < edge: self.more_dots() for i, dot in enumerate(dot_list): self._dots[i].type = dot self._dots[i].set_shape( self._new_dot(self._colors[self._dots[i].type])) if move_list is not None: self._move_list = move_list[:] def save_game(self): ''' Return dot list, move_list for saving to Journal or sharing ''' dot_list = [] for dot in self._dots: dot_list.append(dot.type) return (dot_list, self._move_list) def _set_label(self, string): ''' Set the label in the toolbar or the window frame. ''' self._activity.status.set_label(string) def _button_press_cb(self, win, event): win.grab_focus() x, y = map(int, event.get_coords()) spr = self._sprites.find_sprite((x, y)) if spr == None: return if spr.type is not None: self._flip_them(self._dots.index(spr)) self._test_game_over() if self.we_are_sharing: _logger.debug('sending a click to the share') self._parent.send_dot_click(self._dots.index(spr)) return True def solve(self): ''' Solve the puzzle by undoing moves ''' if self._move_list == []: return self._flip_them(self._move_list.pop(), append=False) GObject.timeout_add(750, self.solve) def _flip_them(self, dot, append=True): ''' flip the dot and its neighbors ''' if append: self._move_list.append(dot) x, y = self._dot_to_grid(dot) self._flip(self._dots[dot]) if x > 0: self._flip(self._dots[dot - 1]) if y > 0: self._flip(self._dots[dot - self._edge]) if x < self._edge - 1: self._flip(self._dots[dot + 1]) if y < self._edge - 1: self._flip(self._dots[dot + self._edge]) def _flip(self, spr): ''' flip a dot ''' spr.type += 1 spr.type %= 2 spr.set_shape(self._new_dot(self._colors[spr.type])) def remote_button_press(self, dot): ''' Receive a button press from a sharer ''' self._flip_them(dot) self._test_game_over() def set_sharing(self, share=True): _logger.debug('enabling sharing') self.we_are_sharing = share def _smile(self): for dot in self._dots: dot.set_label(':)') def _test_game_over(self): ''' Check to see if game is over: all dots the same color ''' match = self._dots[0].type for y in range(self._edge): for x in range(self._edge): if self._dots[y * self._edge + x].type != match: self._set_label(_('keep trying')) return False self._set_label(_('good work')) self._smile() GObject.timeout_add(2000, self.more_dots) return True def _grid_to_dot(self, pos): ''' calculate the dot index from a column and row in the grid ''' return pos[0] + pos[1] * self._edge def _dot_to_grid(self, dot): ''' calculate the grid column and row for a dot ''' return [dot % self._edge, int(dot / self._edge)] def game_over(self, msg=_('Game over')): ''' Nothing left to do except show the results. ''' self._set_label(msg) def __draw_cb(self, canvas, cr): self._sprites.redraw_sprites(cr=cr) def do_expose_event(self, event): ''' Handle the expose-event by drawing ''' # Restrict Cairo to the exposed area cr = self._canvas.window.cairo_create() cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): Gtk.main_quit() def _new_dot(self, color): ''' generate a dot of a color color ''' self._dot_cache = {} if not color in self._dot_cache: self._stroke = color self._fill = color self._svg_width = self._dot_size self._svg_height = self._dot_size pixbuf = svg_str_to_pixbuf( self._header() + \ self._circle(self._dot_size / 2., self._dot_size / 2., self._dot_size / 2.) + \ self._footer()) surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self._svg_width, self._svg_height) context = cairo.Context(surface) Gdk.cairo_set_source_pixbuf(context, pixbuf, 0, 0) context.rectangle(0, 0, self._svg_width, self._svg_height) context.fill() self._dot_cache[color] = surface return self._dot_cache[color] def _header(self): return '<svg\n' + 'xmlns:svg="http://www.w3.org/2000/svg"\n' + \ 'xmlns="http://www.w3.org/2000/svg"\n' + \ 'xmlns:xlink="http://www.w3.org/1999/xlink"\n' + \ 'version="1.1"\n' + 'width="' + str(self._svg_width) + '"\n' + \ 'height="' + str(self._svg_height) + '">\n' def _circle(self, r, cx, cy): return '<circle style="fill:' + str(self._fill) + ';stroke:' + \ str(self._stroke) + ';" r="' + str(r - 0.5) + '" cx="' + \ str(cx) + '" cy="' + str(cy) + '" />\n' def _footer(self): return '</svg>\n'
class Game(): def __init__(self, canvas, parent=None, colors=['#A0FFA0', '#FF8080']): self._activity = parent self.colors = colors # Starting from command line if parent is None: self._running_sugar = False self._canvas = canvas else: self._running_sugar = True self._canvas = canvas parent.show_all() self._canvas.set_can_focus(True) self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK | Gdk.EventMask.POINTER_MOTION_MASK) self._canvas.connect("draw", self._draw_cb) self._canvas.connect("button-press-event", self._button_press_cb) self._canvas.connect("button-release-event", self._button_release_cb) self._canvas.connect("motion-notify-event", self._mouse_move_cb) self._canvas.connect("key_press_event", self._keypress_cb) self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - (GRID_CELL_SIZE * 1.5) self._scale = self._height / (8.0 * TILE_HEIGHT) self.tile_width = TILE_WIDTH * self._scale self.tile_height = TILE_HEIGHT * self._scale # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) self.grid = Grid(self._sprites, self._width, self._height, self.tile_width, self.tile_height, self._scale, colors[0]) self.deck = Deck(self._sprites, self._scale, colors[1]) self.deck.board.move((self.grid.left, self.grid.top)) self.hands = [] self.hands.append(Hand(self.tile_width, self.tile_height)) self._errormsg = [] for i in range(4): self._errormsg.append(error_graphic(self._sprites)) self._highlight = highlight_graphic(self._sprites, self._scale) self._score_card = blank_tile(self._sprites, scale=self._scale * 2, color=colors[1]) self._score_card.set_label_attributes(64) self._score_card.move(((int(self._width / 2) - self.tile_width), int(self._height / 2) - self.tile_height)) # and initialize a few variables we'll need. self.buddies = [] self._my_hand = MY_HAND self.playing_with_robot = False self._all_clear() def _all_clear(self): ''' Things to reinitialize when starting up a new game. ''' self._hide_highlight() self._hide_errormsgs() self.deck.hide() self.deck.clear() self.grid.clear() for hand in self.hands: hand.clear() self.show_connected_tiles() self._press = None self._release = None self._dragpos = [0, 0] self._total_drag = [0, 0] self.last_spr_moved = None self._last_tile_played = None self._last_tile_orientation = 0 self._last_grid_played = None self.whos_turn = MY_HAND self._waiting_for_my_turn = False self._waiting_for_robot = False self.placed_a_tile = False self._there_are_errors = False self.score = 0 self._score_card.set_layer(HIDE) self._score_card.move(((int(self._width / 2) - self.tile_width), int(self._height / 2) - self.tile_height)) self.saw_game_over = False def _initiating(self): if not self._running_sugar: return True return self._activity.initiating def new_game(self, saved_state=None, deck_index=0): ''' Start a new game. ''' self._all_clear() # If we are not sharing or we are the sharer... if not self.we_are_sharing() or self._initiating(): # Let joiners know we are starting a new game... if self.we_are_sharing(): self._activity.send_event("n", " ") # The initiator shuffles the deck... self.deck.shuffle() # ...and shares it. if self.we_are_sharing(): self._activity.send_event("d", self.deck.serialize()) # Deal a hand to yourself... self.hands[self._my_hand].deal(self.deck) # ...deal a hand to the robot... if self.playing_with_robot: if len(self.hands) < ROBOT_HAND + 1: self.hands.append( Hand(self.tile_width, self.tile_height, remote=True)) self.hands[ROBOT_HAND].deal(self.deck) # ...or deal hands to the joiners. elif len(self.buddies) > 1: for i, buddy in enumerate(self.buddies): if buddy != self._activity.nick: self.hands.append( Hand(self.tile_width, self.tile_height, remote=True)) self.hands[i].deal(self.deck) self._activity.send_event( "h", self.hands[i].serialize(buddy=buddy)) # As initiator, you take the first turn. self.its_my_turn() # If we are joining, we need to wait for a hand. else: self._my_hand = self.buddies.index(self._activity.nick) self.its_their_turn(self.buddies[1]) # Sharer will be buddy 1 def we_are_sharing(self): ''' If we are sharing, there is more than one buddy. ''' if len(self.buddies) > 1: return True def _set_label(self, string): ''' Set the label in the toolbar or the window frame. ''' if self._running_sugar: self._activity.status.set_label(string) self._activity.score.set_label(_('Score: ') + str(self.score)) elif hasattr(self, 'win'): self.win.set_title('%s: %s [%d]' % (_('Paths'), string, self.score)) def its_my_turn(self): # I need to play a piece... self.placed_a_tile = False # and I am no longer waiting for my turn. self._waiting_for_my_turn = False # If I don't have any tiles left, time to redeal. if self.hands[self._my_hand].tiles_in_hand() == 0: self._redeal() if self._running_sugar: self._activity.set_player_on_toolbar(self._activity.nick) self._activity.dialog_button.set_icon_name('go-next') self._activity.dialog_button.set_tooltip( _('Click after taking your turn.')) self._set_label(_('It is your turn.')) def _redeal(self): # Only the sharer deals tiles. if not self.we_are_sharing(): self.hands[self._my_hand].deal(self.deck) if self.playing_with_robot: self.hands[ROBOT_HAND].deal(self.deck) if self.hands[self._my_hand].tiles_in_hand() == 0: if self._running_sugar: self._activity.dialog_button.set_icon_name( 'media-playback-stop-insensitive') self._activity.dialog_button.set_tooltip(_('Game over')) self.game_over() elif self._initiating(): if self.deck.empty(): self.game_over() return if self.deck.tiles_remaining() < COL * len(self.buddies): number_of_tiles_to_deal = \ int(self.deck.tiles_remaining() / len(self.buddies)) if number_of_tiles_to_deal == 0: number_of_tiles_to_deal = 1 # Deal last tile in deck. else: number_of_tiles_to_deal = COL for i, nick in enumerate(self.buddies): self.hands[i].deal(self.deck, number_of_tiles_to_deal) # Send the joiners their new hands. if nick != self._activity.nick: self._activity.send_event( "h", (self.hands[i].serialize(buddy=nick))) def took_my_turn(self): # Did I complete my turn without any errors? if self._there_are_errors: self._set_label(_('There are errors—it is still your turn.')) return # After the tile is placed, expand regions of playable grid squares. self.show_connected_tiles() # Are there any completed paths? self._test_for_complete_paths(self._last_grid_played) # If so, let everyone know what piece I moved. if self.we_are_sharing(): self._activity.send_event( "p", json_dump([ self._last_tile_played, self._last_tile_orientation, self._last_grid_played ])) self._last_tile_orientation = 0 # Reset orientation. # I took my turn, so I am waiting again. self._waiting_for_my_turn = True if self.last_spr_moved is not None: self.last_spr_moved.set_layer(TILES) self.last_spr_moved = None self._hide_highlight() self._set_label(_('You took your turn.')) if self.playing_with_robot: self.its_their_turn(_('robot')) self._waiting_for_robot = True gobject.timeout_add(1000, self._robot_turn) elif not self.we_are_sharing(): if self.deck.empty() and \ self.hands[self._my_hand].tiles_in_hand() == 0: self.game_over() else: self.its_my_turn() elif self._initiating(): self.whos_turn += 1 if self.whos_turn == len(self.buddies): self.whos_turn = 0 else: self.its_their_turn(self.buddies[self.whos_turn]) self._activity.send_event("t", self.buddies[self.whos_turn]) def _robot_turn(self): self._robot_play() self.show_connected_tiles() if not self._waiting_for_robot: self.its_my_turn() def its_their_turn(self, nick): # It is someone else's turn. if self._running_sugar: if not self.playing_with_robot: self._activity.set_player_on_toolbar(nick) self._activity.dialog_button.set_icon_name('media-playback-stop') self._activity.dialog_button.set_tooltip(_('Wait your turn.')) self._set_label(_('Waiting for') + ' ' + nick) self._waiting_for_my_turn = True # I am still waiting. def _button_press_cb(self, win, event): win.grab_focus() x, y = list(map(int, event.get_coords())) self._dragpos = [x, y] self._total_drag = [0, 0] spr = self._sprites.find_sprite((x, y)) # If it is not my turn, do nothing. if self._waiting_for_my_turn: self._press = None return self._release = None # Ignore clicks on background except to indicate you took your turn if spr is None or spr in self.grid.blanks or spr == self.deck.board: if self.placed_a_tile and spr is None: self.took_my_turn() self._press = None return True # Are we clicking on a tile in the hand? if self.hands[self._my_hand].spr_to_hand(spr) is not None and \ not self._there_are_errors: self.last_spr_moved = spr clicked_in_hand = True if self.placed_a_tile: self._press = None self.took_my_turn() else: clicked_in_hand = False # We cannot switch to an old tile. if spr == self.last_spr_moved: self._press = spr spr.set_layer(TOP) self._show_highlight() return True def _mouse_move_cb(self, win, event): """ Drag a tile with the mouse. """ spr = self._press if spr is None: self._dragpos = [0, 0] return True win.grab_focus() x, y = list(map(int, event.get_coords())) dx = x - self._dragpos[0] dy = y - self._dragpos[1] spr.move_relative([dx, dy]) self._move_relative_highlight([dx, dy]) self._dragpos = [x, y] self._total_drag[0] += dx self._total_drag[1] += dy def _button_release_cb(self, win, event): win.grab_focus() self._dragpos = [0, 0] if self._waiting_for_my_turn: return if self._press is None: return x, y = list(map(int, event.get_coords())) spr = self._sprites.find_sprite((x, y)) self._release = spr grid_pos = self.grid.xy_to_grid(x, y) hand_pos = self.hands[self._my_hand].xy_to_hand(x, y) # Placing tile in grid if grid_pos is not None and self._it_is_a_drag() and \ self.grid.blanks[grid_pos].get_layer() > HIDE: # Moving to an empty grid position if self.grid.grid[grid_pos] is None: tile = self.deck.spr_to_tile(self._press) tile.spr.move(self.grid.grid_to_xy(grid_pos)) # If the tile was previously in the grid, empty its old pos. i = self.grid.spr_to_grid(self._press) if i is not None: self.grid.grid[i] = None # Assign the tile to the new grid position. self.grid.grid[grid_pos] = tile self.placed_a_tile = True self._last_tile_played = tile.number self._last_grid_played = grid_pos # If the tile came from the hand, empty its old position. i = self.hands[self._my_hand].spr_to_hand(self._press) if i is not None: self.hands[self._my_hand].hand[i] = None # Remember which tile moved. if self.last_spr_moved != tile.spr: self.last_spr_moved = tile.spr self._show_highlight() # Returning tile to hand elif hand_pos is not None: # Make sure there is somewhere to place the tile. empty = self.hands[self._my_hand].find_empty_slot() if empty is not None: tile = self.deck.spr_to_tile(self._press) tile.spr.move(self.hands[self._my_hand].hand_to_xy(empty)) # Did the tile come from elsewhere in the hand? if self.hands[self._my_hand].spr_to_hand( self._press) is not None: self.hands[self._my_hand].hand[self.hands[ self._my_hand].spr_to_hand(self._press)] = None # or from the grid? elif self.grid.spr_to_grid(self._press) is not None: self.grid.grid[self.grid.spr_to_grid(self._press)] = None self.hands[self._my_hand].hand[empty] = tile # Remember which tile moved. if spr == self.last_spr_moved: self.last_spr_moved = None self._hide_errormsgs() self._there_are_errors = False else: # Or return tile to the grid grid_pos = self.grid.spr_to_grid(self._press) if grid_pos is not None: tile = self.deck.spr_to_tile(self._press) tile.spr.move(self.grid.grid_to_xy(grid_pos)) self._hide_highlight() self._press = None self._release = None self.placed_a_tile = False return True # Rotate elif self._press == self._release and not self._it_is_a_drag(): tile = self.deck.spr_to_tile(spr) tile.rotate_clockwise() self._last_tile_orientation = tile.orientation # Remember which tile moved. if self.last_spr_moved != tile.spr: self.last_spr_moved = tile.spr self._show_highlight() # In limbo: return to grid if hand_pos is None and x < self.grid.left: grid_pos = self.grid.spr_to_grid(self._press) if grid_pos is not None: tile = self.deck.spr_to_tile(self._press) tile.spr.move(self.grid.grid_to_xy(grid_pos)) self._hide_highlight() self._snap_to_grid(self._release) self._test_for_bad_paths(self.grid.spr_to_grid(self._press)) self._press = None self._release = None return True def _snap_to_grid(self, spr): ''' make sure a tile is aligned in its grid position ''' for i in range(COL * ROW): if self.grid.grid[i] is not None: self.grid.grid[i].spr.move(self.grid.grid_to_xy(i)) if self.grid.grid[i].spr == spr: self._move_highlight(self.grid.grid_to_xy(i)) def _it_is_a_drag(self): ''' The movement was large enough to be consider a drag as opposed to a tile rotate. ''' if self._total_drag[0] * self._total_drag[0] + \ self._total_drag[1] * self._total_drag[1] > \ self.tile_width * self.tile_height: return True return False def _shuffle_up(self, hand): ''' Shuffle all the tiles in a hand to the top. ''' for i, tile in enumerate(self.hands[hand].hand): empty = self.hands[hand].find_empty_slot() if i > 0 and tile is not None and empty is not None: tile.spr.move(self.hands[hand].hand_to_xy(empty)) self.hands[hand].hand[empty] = tile self.hands[hand].hand[i] = None def game_over(self, msg=_('Game over')): ''' Nothing left to do except show the results. ''' self._set_label(msg) self.saw_game_over = True if self.hands[self._my_hand].tiles_in_hand() == 0: self.score += 50 # Bonus points else: for tile in self.hands[self._my_hand].hand: if tile is not None: self.score -= 2 * tile.get_value() # Penalty self._shuffle_up(self._my_hand) if self._running_sugar: self._activity.score.set_label(_('Score: ') + str(self.score)) self._score_card.set_label(str(self.score)) self._score_card.set_layer(OVER_THE_TOP) self._score_card.move((int(self.tile_width / 2), int(self._height / 2) + 2 * self.tile_height)) if self.playing_with_robot: self._shuffle_up(ROBOT_HAND) for tile in range(COL): if self.hands[ROBOT_HAND].hand[tile] is not None: x, y = self.hands[ROBOT_HAND].hand_to_xy(tile) self.hands[ROBOT_HAND].hand[tile].spr.move( (self.grid.left_hand + self.grid.xinc, y)) if self._running_sugar: self._activity.set_robot_status(False, 'robot-off') elif self.we_are_sharing(): self._activity.send_event("g", " ") def show_connected_tiles(self): ''' Highlight the squares that surround the tiles already on the grid. ''' for i in range(ROW * COL): if self._connected(i): self.grid.blanks[i].set_layer(GRID) else: self.grid.blanks[i].set_layer(HIDE) def _connected(self, tile): ''' Does tile abut the path? ''' if self.grid.grid.count(None) == ROW * COL: return True if self.grid.grid[tile] is not None: # already has a tile return False # Looking north if tile >= COL and self.grid.grid[tile + OFFSETS[0]] is not None: return True # Looking east if tile % ROW < ROW - 1 and \ self.grid.grid[tile + OFFSETS[1]] is not None: return True # Looking south if tile < (ROW - 1) * COL and \ self.grid.grid[tile + OFFSETS[2]] is not None: return True # Looking west if tile % ROW > 0 and self.grid.grid[tile + OFFSETS[3]] is not None: return True return False def give_a_hint(self): ''' Try to find an open place on the grid for any tile in my_hand. ''' order = self.deck.random_order(ROW * COL) for i in range(ROW * COL): if self._connected(order[i]): for tile in self.hands[self._my_hand].hand: if self._try_placement(tile, order[i]): # Success, so give hint. self.grid.grid[order[i]] = None self._show_highlight( pos=self.grid.grid_to_xy(order[i])) return # Nowhere to play. self.game_over(_('Nowhere to play.')) def _robot_play(self): ''' The robot tries random tiles in random locations. ''' # TODO: strategy try to complete paths order = self.deck.random_order(ROW * COL) for i in range(ROW * COL): if self._connected(order[i]): for tile in self.hands[ROBOT_HAND].hand: if self._try_placement(tile, order[i]): # Success, so remove tile from hand. self.hands[ROBOT_HAND].hand[ self.hands[ROBOT_HAND].hand.index(tile)] = None tile.spr.move(self.grid.grid_to_xy(order[i])) tile.spr.set_layer(TILES) self._waiting_for_robot = False return # If we didn't return above, we were unable to play a tile. self.game_over(_('Robot unable to play')) def _try_placement(self, tile, i): ''' Try to place a tile at grid posiion i. Rotate it, if necessary. ''' if tile is None: return False self.grid.grid[i] = tile for j in range(4): self._test_for_bad_paths(i) if not self._there_are_errors: return True tile.rotate_clockwise() self.grid.grid[i] = None return False def _test_for_complete_paths(self, tile): ''' Did this tile complete a path? (or two paths?) ''' # A tile can complete up to two paths. self._paths = [[], []] break_in_path = [False, False] # Seed the paths and lists with the current tile. if tile is not None: self._add_to_path_list(tile, 0, 0) if len(self.grid.grid[tile].paths) == 2: self._add_to_path_list(tile, 1, 1) # Walk the path. for p in range(2): tile, path = self._tile_to_test(p) while (tile is not None): self._test(tile, path, p, self._test_a_neighbor) self._tile_has_been_tested(tile, path, p) tile, path = self._tile_to_test(p) # Is the path complete? for i in self._paths[p]: if not self._test(i[0], i[1], None, self._test_a_connection): break_in_path[p] = True if not break_in_path[p] and len(self._paths[p]) > 0: for i in self._paths[p]: self.grid.grid[i[0]].set_shape(i[1]) self.score += self.grid.grid[i[0]].get_value() def _tile_to_test(self, test_path): ''' Find a tile that needs testing. ''' for i in self._paths[test_path]: if i[2] is False: return i[0], i[1] return None, None def _add_to_path_list(self, tile, tile_path, test_path): ''' Only add a tile to the path if it is not already there. ''' for i in self._paths[test_path]: if i[0] == tile and i[1] == tile_path: return self._paths[test_path].append([tile, tile_path, False]) def _tile_has_been_tested(self, tile, tile_path, test_path): ''' Mark a tile as tested. ''' for i in self._paths[test_path]: if i[0] == tile and i[1] == tile_path: i[2] = True return def _test(self, tile, tile_path, test_path, test): ''' Test each neighbor of a block for a connecting path. ''' if tile is None: return False for i in range(4): if not test(tile, tile_path, test_path, i, tile + OFFSETS[i]): return False return True def _test_a_connection(self, tile, tile_path, test_path, direction, neighbor): ''' Is there a break in the connection? If so return False. ''' if self.grid.grid[tile].paths[tile_path][direction] == 1: if self.grid.grid[neighbor] is None: return False # Which of the neighbor's paths are we connecting to? if len(self.grid.grid[neighbor].paths) == 1: if self.grid.grid[neighbor].paths[0][(direction + 2) % 4] == 0: return False else: return True if self.grid.grid[neighbor].paths[0][(direction + 2) % 4] == 0 and\ self.grid.grid[neighbor].paths[1][(direction + 2) % 4] == 0: return False return True def _test_a_neighbor(self, tile, tile_path, test_path, direction, neighbor): ''' Are we connected to a neighbor's path? If so, add the neighbor to our paths list and to the list of tiles that need to be tested. ''' if self.grid.grid[tile].paths[tile_path][direction] == 1: if self.grid.grid[neighbor] is not None: if not neighbor in self._paths[test_path]: # Which of the neighbor's paths are we connecting to? if self.grid.grid[neighbor].paths[0][(direction + 2) % 4] == 1: self._add_to_path_list(neighbor, 0, test_path) elif len(self.grid.grid[neighbor].paths) == 2 and \ self.grid.grid[neighbor].paths[1][ (direction + 2) % 4] == 1: self._add_to_path_list(neighbor, 1, test_path) return True def _test_for_bad_paths(self, tile): ''' Is there a path to nowhere? ''' self._hide_errormsgs() self._there_are_errors = False if tile is not None: self._check_tile(tile, [int(tile / COL), 0], NORTH, tile + OFFSETS[0]) self._check_tile(tile, [tile % ROW, ROW - 1], EAST, tile + OFFSETS[1]) self._check_tile(tile, [int(tile / COL), COL - 1], SOUTH, tile + OFFSETS[2]) self._check_tile(tile, [tile % ROW, 0], WEST, tile + OFFSETS[3]) def _check_tile(self, i, edge_check, direction, neighbor): ''' Can a tile be placed at position i? ''' if edge_check[0] == edge_check[1]: for path in self.grid.grid[i].paths: if path[direction] == 1: self._display_errormsg(i, direction) else: if self.grid.grid[neighbor] is not None: my_path = 0 your_path = 0 for c in self.grid.grid[i].paths: if c[direction] == 1: my_path = 1 for c in self.grid.grid[neighbor].paths: if c[(direction + 2) % 4] == 1: your_path = 1 if my_path != your_path: self._display_errormsg(i, direction) def _display_errormsg(self, i, direction): ''' Display an error message where and when appropriate. ''' if self._press is not None: dxdy = [[0.375, -0.125], [0.875, 0.375], [0.375, 0.875], [-0.125, 0.375]] x, y = self._press.get_xy() self._errormsg[direction].move( (x + dxdy[direction][0] * self.tile_width, y + dxdy[direction][1] * self.tile_height)) self._errormsg[direction].set_layer(OVER_THE_TOP) self._there_are_errors = True def _hide_errormsgs(self): ''' Hide all the error messages. ''' for i in range(4): self._errormsg[i].move((self.grid.left, self.grid.top)) self._errormsg[i].set_layer(HIDE) def _hide_highlight(self): ''' No tile is selected. ''' for i in range(4): self._highlight[i].move((self.grid.left, self.grid.top)) self._highlight[i].set_layer(HIDE) def _move_relative_highlight(self, pos): for i in range(4): self._highlight[i].move_relative(pos) def _move_highlight(self, pos): x, y = pos self._highlight[0].move((x, y)) self._highlight[1].move((x + 7 * self.tile_width / 8, y)) self._highlight[2].move( (x + 7 * self.tile_width / 8, y + 7 * self.tile_height / 8)) self._highlight[3].move((x, y + 7 * self.tile_height / 8)) def _show_highlight(self, pos=None): ''' Highlight the tile that is selected. ''' if self.last_spr_moved is None and pos is None: self._hide_highlight() else: if pos is None: x, y = self.last_spr_moved.get_xy() else: # Giving a hint. x, y = pos self._move_highlight((x, y)) for i in range(4): self._highlight[i].set_layer(OVER_THE_TOP) def _keypress_cb(self, area, event): return True def _draw_cb(self, win, context): ''' Callback to handle window expose events ''' self.do_draw(context) return True def do_draw(self, cr): ''' Handle the expose-event by drawing ''' # Restrict Cairo to the exposed area alloc = self._canvas.get_allocation() cr.rectangle(alloc.x, alloc.y, alloc.width, alloc.height) cr.clip() # Refresh sprite list self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): Gtk.main_quit()
class Abacus(): """ The Abacus class is used to define the user interaction. """ def __init__(self, canvas, parent=None): """ Initialize the canvas and set up the callbacks. """ self.activity = parent if parent is None: # Starting from command line self.sugar = False self.canvas = canvas else: # Starting from Sugar self.sugar = True self.canvas = canvas parent.show_all() self.canvas.set_flags(gtk.CAN_FOCUS) self.canvas.add_events(gtk.gdk.BUTTON_PRESS_MASK) self.canvas.add_events(gtk.gdk.BUTTON_RELEASE_MASK) self.canvas.add_events(gtk.gdk.POINTER_MOTION_MASK) self.canvas.connect("expose-event", self._expose_cb) self.canvas.connect("button-press-event", self._button_press_cb) self.canvas.connect("button-release-event", self._button_release_cb) self.canvas.connect("motion-notify-event", self._mouse_move_cb) self.width = gtk.gdk.screen_width() self.height = gtk.gdk.screen_height()-GRID_CELL_SIZE self.sprites = Sprites(self.canvas) self.scale = gtk.gdk.screen_height()/900.0 self.dragpos = 0 self.press = None self.chinese = Suanpan(self) self.japanese = Soroban(self) self.russian = Schety(self) self.mayan = Nepohualtzintzin(self) self.binary = Binary(self) self.hex = Hex(self) self.fraction = Fractions(self) self.custom = None self.chinese.show() self.japanese.hide() self.russian.hide() self.mayan.hide() self.binary.hide() self.hex.hide() self.fraction.hide() self.mode = self.chinese def _button_press_cb(self, win, event): """ Callback to handle the button presses """ win.grab_focus() x, y = map(int, event.get_coords()) self.press = self.sprites.find_sprite((x,y)) if self.press is not None: if self.press.type == 'bead': self.dragpos = y elif self.press.type == 'mark': self.dragpos = x else: self.press = None return True def _mouse_move_cb(self, win, event): """ Callback to handle the mouse moves """ if self.press is None: self.dragpos = 0 return True win.grab_focus() x, y = map(int, event.get_coords()) if self.press.type == 'bead': self.mode.move_bead(self.press, y-self.dragpos) elif self.press.type == 'mark': mx, my = self.mode.mark.get_xy() self.mode.move_mark(x-mx) def _button_release_cb(self, win, event): """ Callback to handle the button releases """ if self.press == None: return True self.press = None self.mode.label(self.mode.value()) return True def _expose_cb(self, win, event): """ Callback to handle window expose events """ self.sprites.redraw_sprites() return True def _destroy_cb(self, win, event): """ Callback to handle quit """ gtk.main_quit()
class Game(): ''' The game play -- called from within Sugar or GNOME ''' def __init__(self, canvas, parent=None): ''' Initialize the playing surface ''' self.activity = parent if parent is None: # Starting from command line self._sugar = False self._canvas = canvas else: # Starting from Sugar self._sugar = True self._canvas = canvas parent.show_all() self._canvas.set_can_focus(True) self._canvas.add_events(Gdk.EventMask.TOUCH_MASK) self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK) self._canvas.add_events(Gdk.EventMask.BUTTON_MOTION_MASK) self._canvas.connect('event', self.__event_cb) self._canvas.connect('draw', self.__draw_cb) self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - GRID_CELL_SIZE * 2 if self._width < self._height: self.portrait = True self._scale = 0.8 * self._width / (CARD_HEIGHT * 5.5) else: self.portrait = False self._scale = 0.8 * self._height / (CARD_HEIGHT * 5.5) self._card_width = CARD_WIDTH * self._scale self._card_height = CARD_HEIGHT * self._scale self.custom_paths = [None, None, None, None, None, None, None, None, None] self._sprites = Sprites(self._canvas) self._sprites.set_delay(True) self._press = None self.matches = 0 self.robot_matches = 0 self._match_area = [] self._matches_on_display = False self._smiley = [] self._frowny = [] self._help = [] self._stop_help = True self._failure = None self.clicked = [] self.last_click = None self._drag_pos = [0, 0] self._start_pos = [0, 0] self.low_score = [-1, -1, -1] self.all_scores = [] self.robot = False self.robot_time = 0 self.total_time = 0 self.numberC = 0 self.numberO = 0 self.word_lists = None self.editing_word_list = False self.editing_custom_cards = False self._edit_card = None self._dead_key = None self._found_a_match = False self.level = 0 self.card_type = 'pattern' self.buddies = [] self._dealing = False self._the_game_is_over = False self.grid = Grid(self._width, self._height, self._card_width, self._card_height) self._cards = [] for i in range(DECKSIZE): self._cards.append(Card(scale=self._scale)) self.deck = Deck(self._cards, scale=self._scale) for i in range(CARDS_IN_A_MATCH): self.clicked.append(Click()) self._match_area.append(Card(scale=self._scale)) self._match_area[-1].create( generate_match_card(self._scale), sprites=self._sprites) self._match_area[-1].spr.move(self.grid.match_to_xy(i)) for i in range((ROW - 1) * COL): self._smiley.append(Card(scale=self._scale)) self._smiley[-1].create( generate_smiley(self._scale), sprites=self._sprites) self._smiley[-1].spr.move(self.grid.grid_to_xy(i)) self._smiley.append(Card(scale=self._scale)) self._smiley[-1].create( generate_smiley(self._scale), sprites=self._sprites) self._smiley[-1].spr.move(self.grid.match_to_xy(3)) self._smiley[-1].spr.hide() # A different frowny face for each type of error self._frowny.append(Card(self._scale)) self._frowny[-1].create( generate_frowny_shape(self._scale), sprites=self._sprites) self._frowny[-1].spr.move(self.grid.match_to_xy(3)) self._frowny.append(Card(self._scale)) self._frowny[-1].create( generate_frowny_color(self._scale), sprites=self._sprites) self._frowny[-1].spr.move(self.grid.match_to_xy(3)) self._frowny.append(Card(self._scale)) self._frowny[-1].create( generate_frowny_texture(self._scale), sprites=self._sprites) self._frowny[-1].spr.move(self.grid.match_to_xy(3)) self._frowny.append(Card(self._scale)) self._frowny[-1].create( generate_frowny_number(self._scale), sprites=self._sprites) self._frowny[-1].spr.move(self.grid.match_to_xy(3)) self._label = Card() self._label.create(generate_label(min(self._width, self._height), LABELH), sprites=self._sprites) self._label.spr.move((0, 0)) self._label.spr.set_label_attributes(24, horiz_align="left") self._labels = {'deck': '', 'match': '', 'clock': '', 'status': ''} Gdk.Screen.get_default().connect('size-changed', self._configure_cb) def _configure_cb(self, event): self.grid.stop_animation = True self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - GRID_CELL_SIZE * 2 if self._width < self._height: self.portrait = True else: self.portrait = False self.grid.rotate(self._width, self._height) for i in range(CARDS_IN_A_MATCH): self._match_area[i].spr.move(self.grid.match_to_xy(i)) self._smiley[-1].spr.move(self.grid.match_to_xy(3)) for c in self._frowny: c.spr.move(self.grid.match_to_xy(3)) for i, c in enumerate(self.clicked): if c.spr is not None: c.spr.move(self.grid.match_to_xy(i)) def new_game(self, saved_state=None, deck_index=0): ''' Start a new game ''' # If we were editing the word list, time to stop self.grid.stop_animation = True self.editing_word_list = False self.editing_custom_cards = False self._edit_card = None self._saved_state = saved_state self._deck_index = deck_index # Wait for any animations to stop before starting new game timeout = GObject.timeout_add(200, self._prepare_new_game) def _prepare_new_game(self): # If there is already a deck, hide it. if hasattr(self, 'deck'): self.deck.hide() self._dealing = False self._hide_clicked() self._matches_on_display = False self._failure = None self._hide_frowny() self._smiley[-1].spr.hide() if self._saved_state is not None: _logger.debug('Restoring state: %s' % (str(self._saved_state))) if self.card_type == 'custom': self.deck.create(self._sprites, self.card_type, [self.numberO, self.numberC], self.custom_paths, DIFFICULTY_LEVEL[self.level]) else: self.deck.create(self._sprites, self.card_type, [self.numberO, self.numberC], self.word_lists, DIFFICULTY_LEVEL[self.level]) self.deck.hide() self.deck.index = self._deck_index deck_start = ROW * COL + 3 deck_stop = deck_start + self.deck.count() self._restore_word_list(self._saved_state[deck_stop + 3 * self.matches:]) if self._saved_state[deck_start] is not None: self.deck.restore(self._saved_state[deck_start: deck_stop]) self.grid.restore(self.deck, self._saved_state[0: ROW * COL]) self._restore_matches( self._saved_state[deck_stop: deck_stop + 3 * self.matches]) self._restore_clicked( self._saved_state[ROW * COL: ROW * COL + 3]) else: self.deck.hide() self.deck.shuffle() self.grid.deal(self.deck) if not self._find_a_match(): self.grid.deal_extra_cards(self.deck) self.matches = 0 self.robot_matches = 0 self.match_list = [] self.total_time = 0 elif not self.joiner(): _logger.debug('Starting new game.') if self.card_type == 'custom': self.deck.create(self._sprites, self.card_type, [self.numberO, self.numberC], self.custom_paths, DIFFICULTY_LEVEL[self.level]) else: self.deck.create(self._sprites, self.card_type, [self.numberO, self.numberC], self.word_lists, DIFFICULTY_LEVEL[self.level]) self.deck.hide() self.deck.shuffle() self.grid.deal(self.deck) if not self._find_a_match(): self.grid.deal_extra_cards(self.deck) self.matches = 0 self.robot_matches = 0 self.match_list = [] self.total_time = 0 # When sharer starts a new game, joiners should be notified. if self.sharer(): self.activity._send_event('J') self._update_labels() self._the_game_is_over = False if self._game_over(): if hasattr(self, 'timeout_id') and self.timeout_id is not None: GObject.source_remove(self.timeout_id) else: if hasattr(self, 'match_timeout_id') and \ self.match_timeout_id is not None: GObject.source_remove(self.match_timeout_id) self._timer_reset() for i in range((ROW - 1) * COL): self._smiley[i].hide_card() self._sprites.draw_all() def _sharing(self): ''' Are we sharing? ''' if self._sugar and hasattr(self.activity, 'chattube') and \ self.activity.chattube is not None: return True return False def joiner(self): ''' Are you the one joining? ''' if self._sharing() and not self.activity.initiating: return True return False def sharer(self): ''' Are you the one sharing? ''' if self._sharing() and self.activity.initiating: return True return False def edit_custom_card(self): ''' Update the custom cards from the Journal ''' if not self.editing_custom_cards: return # Set the card type to custom, and generate a new deck. self._hide_clicked() self.deck.hide() self.card_type = 'custom' if len(self.custom_paths) < 3: for i in range(len(self.custom_paths), 81): self.custom_paths.append(None) self.deck.create(self._sprites, self.card_type, [self.numberO, self.numberC], self.custom_paths, DIFFICULTY_LEVEL.index(HIGH)) self.deck.hide() self.matches = 0 self.robot_matches = 0 self.match_list = [] self.total_time = 0 self._edit_card = None self._dead_key = None if hasattr(self, 'timeout_id') and self.timeout_id is not None: GObject.source_remove(self.timeout_id) # Fill the grid with custom cards. self.grid.restore(self.deck, CUSTOM_CARD_INDICIES) self.set_label('deck', '') self.set_label('match', '') self.set_label('clock', '') self.set_label('status', _('Edit the custom cards.')) def edit_word_list(self): ''' Update the word cards ''' if not self.editing_word_list: if hasattr(self, 'text_entry'): self.text_entry.hide() self.text_entry.disconnect(self.text_event_id) return # Set the card type to words, and generate a new deck. self._hide_clicked() self.deck.hide() self.card_type = 'word' self.deck.create(self._sprites, self.card_type, [self.numberO, self.numberC], self.word_lists, DIFFICULTY_LEVEL.index(HIGH)) self.deck.hide() self.matches = 0 self.robot_matches = 0 self.match_list = [] self.total_time = 0 self._edit_card = None self._dead_key = None if hasattr(self, 'timeout_id') and self.timeout_id is not None: GObject.source_remove(self.timeout_id) # Fill the grid with word cards. self.grid.restore(self.deck, WORD_CARD_INDICIES) self.set_label('deck', '') self.set_label('match', '') self.set_label('clock', '') self.set_label('status', _('Edit the word cards.')) if not hasattr(self, 'text_entry'): self.text_entry = Gtk.TextView() self.text_entry.set_wrap_mode(Gtk.WrapMode.WORD) self.text_entry.set_pixels_above_lines(0) self.text_entry.set_size_request(self._card_width, self._card_height) ''' rgba = Gdk.RGBA() rgba.red, rgba.green, rgba.blue = rgb(self._colors[1]) rgba.alpha = 1. self.text_entry.override_background_color( Gtk.StateFlags.NORMAL, rgba) ''' font_text = Pango.font_description_from_string('24') self.text_entry.modify_font(font_text) self.activity.fixed.put(self.text_entry, 0, 0) def _text_focus_out_cb(self, widget=None, event=None): if self._edit_card is None: self.text_entry.hide() self.text_entry.disconnect(self.text_event_id) self._update_word_card() self.text_entry.hide() def _update_word_card(self): bounds = self.text_buffer.get_bounds() text = self.text_buffer.get_text(bounds[0], bounds[1], True) self._edit_card.spr.set_label(text) (i, j) = WORD_CARD_MAP[self._edit_card.index] self.word_lists[i][j] = text self._edit_card = None def __event_cb(self, widget, event): ''' Handle touch events ''' if event.type in (Gdk.EventType.TOUCH_BEGIN, Gdk.EventType.TOUCH_END, Gdk.EventType.TOUCH_UPDATE, Gdk.EventType.BUTTON_PRESS, Gdk.EventType.BUTTON_RELEASE, Gdk.EventType.MOTION_NOTIFY): x = event.get_coords()[1] y = event.get_coords()[2] if event.type == Gdk.EventType.TOUCH_BEGIN or \ event.type == Gdk.EventType.BUTTON_PRESS: self._button_press(x, y) elif event.type == Gdk.EventType.TOUCH_UPDATE or \ event.type == Gdk.EventType.MOTION_NOTIFY: self._drag_event(x, y) elif event.type == Gdk.EventType.TOUCH_END or \ event.type == Gdk.EventType.BUTTON_RELEASE: self._button_release(x, y) def _button_press_cb(self, win, event): ''' Look for a card under the button press and save its position. ''' win.grab_focus() x, y = map(int, event.get_coords()) self._button_press(x, y) def _button_press(self, x, y): # Turn off help animation if not self._stop_help: self._stop_help = True return True # Don't do anything if the game is over if self._the_game_is_over: return True # Don't do anything during a deal if self._dealing: return True # Find the sprite under the mouse. spr = self._sprites.find_sprite((x, y)) # If there is a match showing, hide it. if self._matches_on_display: self.clean_up_match(share=True) # Nothing else to do. if spr is None: return True # Don't grab cards in the match pile. if spr in self.match_list: return True # Don't grab a card being animated. if True in self.grid.animation_lock: return True # Don't do anything if a card is already in motion if self._in_motion(spr): return True # Keep track of starting drag position. self._drag_pos = [x, y] self._start_pos = [x, y] # If the match area is full, we need to move a card back to the grid if self._failure is not None: if not self.grid.xy_in_match(spr.get_xy()): return True # We are only interested in cards in the deck. if self.deck.spr_to_card(spr) is not None: self._press = spr # Save its starting position so we can restore it if necessary if self._where_in_clicked(spr) is None: i = self._none_in_clicked() if i is None: self._press = None else: self.clicked[i].spr = spr self.clicked[i].pos = spr.get_xy() self.last_click = i else: self._press = None return True def clean_up_match(self, share=False): ''' Unselect clicked cards that are now in the match pile ''' self._matches_on_display = False self._hide_clicked() self._smiley[-1].spr.hide() if share and self._sharing(): self.activity._send_event('r:') def clean_up_no_match(self, spr, share=False): ''' Return last card played to grid ''' if self.clicked[2].spr is not None and self.clicked[2].spr != spr: self.return_card_to_grid(2) self.last_click = 2 if share and self._sharing(): self.activity._send_event('R:2') self._hide_frowny() self._failure = None def _mouse_move_cb(self, win, event): ''' Drag the card with the mouse. ''' win.grab_focus() x, y = map(int, event.get_coords()) self._drag_event(x, y) def _drag_event(self, x, y): if self._press is None or self.editing_word_list or \ self.editing_custom_cards: self._drag_pos = [0, 0] return True dx = x - self._drag_pos[0] dy = y - self._drag_pos[1] self._press.set_layer(5000) self._press.move_relative((dx, dy)) self._drag_pos = [x, y] def _button_release_cb(self, win, event): ''' Lots of possibilities here between clicks and drags ''' win.grab_focus() x, y = map(int, event.get_coords()) self._button_release(x, y) def _button_release(self, x, y): # Maybe there is nothing to do. if self._press is None: if self.editing_word_list: self._text_focus_out_cb() self._drag_pos = [0, 0] return True self._press.set_layer(2000) # Determine if it was a click, a drag, or an aborted drag d = _distance((x, y), (self._start_pos[0], self._start_pos[1])) if d < self._card_width / 10: # click move = 'click' elif d < self._card_width / 2: # aborted drag move = 'abort' else: move = 'drag' # Determine status of card status = self.grid.spr_to_grid(self._press) if move == 'click': if self.editing_word_list: # Only edit one card at a time, so unselect other cards for i, c in enumerate(self.clicked): if c.spr is not None and c.spr != self._press: c.spr.set_label( c.spr.labels[0].replace(CURSOR, '')) c.spr = None # Unselect elif self.editing_custom_cards: pass else: self.process_click(self._press) elif move == 'abort': i = self._where_in_clicked(self._press) self._press.move(self.clicked[i].pos) else: # move == 'drag' move = self._process_drag(self._press, x, y) if move == 'abort': self._press = None return if self._sharing(): if self.deck.spr_to_card(self._press) is not None: # Tell everyone about the card we just clicked self.activity._send_event( 'B:%d' % (self.deck.spr_to_card(self._press).index)) i = self._where_in_clicked(self._press) if i is not None: self.activity._send_event('S:%d' % (i)) elif self.last_click is not None: self.activity._send_event('S:%d' % (self.last_click)) else: _logger.error('WARNING: Cannot find last click') self.last_click = None self.process_selection(self._press) self._press = None return def process_click(self, spr): ''' Either move the card to the match area or back to the grid.''' if self.grid.spr_to_grid(spr) is None: # Return card to grid i = self._where_in_clicked(spr) if i is not None: self.return_card_to_grid(i) self.last_click = i self._hide_frowny() self._failure = None else: i = self._where_in_clicked(spr) if i is None: spr.move((self._start_pos)) else: spr.set_layer(5000) self.grid.grid[self.grid.spr_to_grid(spr)] = None self.grid.display_match(spr, i) def _process_drag(self, spr, x, y): ''' Either drag to the match area, back to the grid, or to a new slot. ''' move = 'drag' if self.grid.spr_to_grid(spr) is None: # Returning a card to the grid if (self.portrait and y < self.grid.bottom) or \ (not self.portrait and x > self.grid.left): i = self.grid.xy_to_grid((x, y)) if self.grid.grid[i] is not None: i = self.grid.find_an_empty_slot() spr.move(self.grid.grid_to_xy(i)) self.grid.grid[i] = self.deck.spr_to_card(spr) i = self._where_in_clicked(spr) self.last_click = i self.clicked[i].reset() self._hide_frowny() self._failure = None # Move a click to a different match slot else: i = self._where_in_clicked(spr) j = self.grid.xy_to_match((x, y)) if i == j: spr.move(self.clicked[i].pos) else: temp_spr = self.clicked[i].spr self.clicked[i].spr = self.clicked[j].spr self.clicked[j].spr = temp_spr if self.clicked[i].spr is not None: self.clicked[i].spr.move(self.grid.match_to_xy(i)) if self.clicked[j].spr is not None: self.clicked[j].spr.move(self.grid.match_to_xy(j)) move = 'abort' else: i = self._where_in_clicked(spr) if i is None: move = 'abort' # Moving a card to the match area elif (self.portrait and y > self.grid.bottom) or \ (not self.portrait and x < self.grid.left): self.grid.grid[self.grid.spr_to_grid(spr)] = None spr.move(self._match_area[i].spr.get_xy()) else: # Shuffle positions in match area j = self.grid.xy_to_grid((x, y)) k = self.grid.xy_to_grid(self.clicked[i].pos) if j < 0 or k < 0 or j > 15 or k > 15 or j == k: spr.move(self.clicked[i].pos) else: tmp_card = self.grid.grid[k] if self.grid.grid[j] is not None: self.grid.grid[j].spr.move(self.grid.grid_to_xy(k)) spr.move(self.grid.grid_to_xy(j)) self.grid.grid[k] = self.grid.grid[j] self.grid.grid[j] = tmp_card else: spr.move(self.grid.grid_to_xy(j)) self.grid.grid[j] = self.grid.grid[k] self.grid.grid[k] = None move = 'abort' self.clicked[i].reset() self._consistency_check() return move def _consistency_check(self): ''' Make sure that the cards in the grid are really in the grid ''' # Root cause: a race condition? for i in range(3): spr = self.clicked[i].spr if spr is not None: if not self.grid.xy_in_match(spr.get_xy()): _logger.debug('card in both the grid and ' 'match area (%d)' % (i)) spr.move(self.grid.match_to_xy(i)) def process_selection(self, spr): ''' After a card has been selected... ''' if self.editing_word_list: # Edit label of selected card x, y = spr.get_xy() if self._edit_card is not None: self._update_word_card() self._edit_card = self.deck.spr_to_card(spr) self.text_buffer = self.text_entry.get_buffer() self.text_entry.show() self.text_buffer.set_text(self._edit_card.spr.labels[0]) self.activity.fixed.move(self.text_entry, x, y) self.text_event_id = self.text_entry.connect( 'focus-out-event', self._text_focus_out_cb) self.text_entry.grab_focus() elif self.editing_custom_cards: # Only edit one card at a time, so unselect other cards for i, c in enumerate(self.clicked): if c.spr is not None and c.spr != spr: c.spr = None # Choose an image from the Journal for a card self._edit_card = self.deck.spr_to_card(spr) self._choose_custom_card() # Regenerate the deck with the new card definitions self.deck.create(self._sprites, self.card_type, [self.numberO, self.numberC], self.custom_paths, DIFFICULTY_LEVEL[1]) self.deck.hide() self.grid.restore(self.deck, CUSTOM_CARD_INDICIES) elif self._none_in_clicked() == None: # If we have three cards selected, test for a match. self._test_for_a_match() if self._matches_on_display: self._smiley[-1].spr.set_layer(100) elif not self._the_game_is_over and self._failure is not None: self._frowny[self._failure].spr.set_layer(100) return def _none_in_clicked(self): ''' Look for room on the click list ''' for i, c in enumerate(self.clicked): if c.spr is None: return i return None def _where_in_clicked(self, spr): ''' Is the card already selected? ''' for i, c in enumerate(self.clicked): if c.spr == spr: return i return None def add_to_clicked(self, spr, pos=[0, 0]): ''' Add a card to the selected list ''' i = self._where_in_clicked(spr) if i is not None: self.last_click = i else: i = self._none_in_clicked() if i is None: _logger.error('WARNING: No room in clicked') self.last_click = None return self.clicked[i].spr = spr self.clicked[i].pos = pos self.last_click = i def _hide_clicked(self): ''' Hide the clicked cards ''' for c in self.clicked: if c is not None: c.hide() def _hide_frowny(self): ''' Hide the frowny cards ''' for c in self._frowny: c.spr.hide() def return_card_to_grid(self, i): ''' "Unclick" ''' j = self.grid.find_an_empty_slot() if j is not None: self.grid.return_to_grid(self.clicked[i].spr, j, i) self.grid.grid[j] = self.deck.spr_to_card(self.clicked[i].spr) self.clicked[i].reset() def _game_over(self): ''' Game is over when the deck is empty and no more matches. ''' if self.deck.empty() and not self._find_a_match(): self._hide_frowny() self.set_label('deck', '') self.set_label('clock', '') self.set_label('status', '%s (%d:%02d)' % (_('Game over'), int(self.total_time / 60), int(self.total_time % 60))) for i in range((ROW - 1) * COL): if self.grid.grid[i] == None: self._smiley[i].show_card() self.match_timeout_id = GObject.timeout_add( 2000, self._show_matches, 0) self._the_game_is_over = True elif self.grid.cards_in_grid() == DEAL + 3 \ and not self._find_a_match(): self._hide_frowny() self.set_label('deck', '') self.set_label('clock', '') self.set_label('status', _('unsolvable')) self._the_game_is_over = True return self._the_game_is_over def _test_for_a_match(self): ''' If we have a match, then we have work to do. ''' if self._match_check([self.deck.spr_to_card(self.clicked[0].spr), self.deck.spr_to_card(self.clicked[1].spr), self.deck.spr_to_card(self.clicked[2].spr)], self.card_type): # Stop the timer. if hasattr(self, 'timeout_id'): if self.timeout_id is not None: GObject.source_remove(self.timeout_id) self.total_time += GObject.get_current_time() - self.start_time # Increment the match counter and add the match to the match list. self.matches += 1 for c in self.clicked: self.match_list.append(c.spr) self._matches_on_display = True # Test to see if the game is over. if self._game_over(): if hasattr(self, 'timeout_id'): GObject.source_remove(self.timeout_id) if self.low_score[self.level] == -1: self.low_score[self.level] = self.total_time elif self.total_time < self.low_score[self.level]: self.low_score[self.level] = self.total_time self.set_label('status', '%s (%d:%02d)' % (_('New record'), int(self.total_time / 60), int(self.total_time % 60))) # Round to nearest second self.all_scores.append(int(self.total_time + 0.5)) if not self._sugar: self.activity.save_score() else: self._auto_increase_difficulty() return True else: # Wait a few seconds before dealing new cards. self._dealing = True GObject.timeout_add(2000, self._deal_new_cards) # Keep playing. self._update_labels() self._timer_reset() else: self._matches_on_display = False def _auto_increase_difficulty(self): ''' Auto advance levels ''' if self.level == 2 and len(self.all_scores) > 3: sum = 0 for i in range(3): sum += self.all_scores[-i - 1] if sum < 120: self.level = 0 self.activity.intermediate_button.set_active(True) elif self.level == 0 and len(self.all_scores) > 8: sum = 0 for i in range(3): sum += self.all_scores[-i - 1] if sum < 240: self.level = 1 self.activity.expert_button.set_active(True) def _deal_new_cards(self): ''' Deal three new cards. ''' self.grid.replace(self.deck) self.set_label('deck', '%d %s' % (self.deck.cards_remaining(), _('cards'))) # Consolidate the grid. self.grid.consolidate() # Test to see if we need to deal extra cards. if not self._find_a_match(): self.grid.deal_extra_cards(self.deck) self._failure = None self._dealing = False def __draw_cb(self, canvas, cr): self._sprites.redraw_sprites(cr=cr) def _expose_cb(self, win, event): ''' Callback to handle window expose events ''' self.do_expose_event(event) return True # Handle the expose-event by drawing def do_expose_event(self, event): # Create the cairo context cr = self._canvas.window.cairo_create() # Restrict Cairo to the exposed area; avoid extra work cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list if cr is not None: self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): ''' This is the end ''' Gtk.main_quit() def _update_labels(self): ''' Write strings to a label in the toolbar. ''' self.set_label('deck', '%d %s' % (self.deck.cards_remaining(), _('cards'))) self.set_label('status', '') if self.matches == 1: if self.robot_matches > 0: self.set_label('match', '%d (%d) %s' % ( self.matches - self.robot_matches, self.robot_matches, _('match'))) else: self.set_label('match', '%d %s' % (self.matches, _('match'))) else: if self.robot_matches > 0: self.set_label('match', '%d (%d) %s' % ( self.matches - self.robot_matches, self.robot_matches, _('matches'))) else: self.set_label('match', '%d %s' % (self.matches, _('matches'))) def set_label(self, label, s): ''' Update the toolbar labels ''' if label in self._labels: self._labels[label] = s msg = "%s - %s - %s - %s" % (self._labels['deck'], self._labels['match'], self._labels['clock'], self._labels['status']) self._label.spr.set_label(msg) def _restore_clicked(self, saved_selected_indices): ''' Restore the selected cards upon resume or share. ''' j = 0 for i in saved_selected_indices: if i is None: self.clicked[j].reset() else: self.clicked[j].spr = self.deck.index_to_card(i).spr k = self.grid.spr_to_grid(self.clicked[j].spr) self.clicked[j].spr.move(self.grid.match_to_xy(j)) self.clicked[j].pos = self.grid.match_to_xy(j) self.clicked[j].spr.set_layer(2000) j += 1 self.process_selection(None) def _restore_matches(self, saved_match_list_indices): ''' Restore the match list upon resume or share. ''' j = 0 self.match_list = [] for i in saved_match_list_indices: if i is not None: try: self.match_list.append(self.deck.index_to_card(i).spr) except AttributeError: _logger.debug('index %s was not found in deck' % (str(i))) def _restore_word_list(self, saved_word_list): ''' Restore the word list upon resume or share. ''' if len(saved_word_list) == 9: for i in range(3): for j in range(3): self.word_lists[i][j] = saved_word_list[i * 3 + j] def _counter(self): ''' Display of seconds since start_time. ''' seconds = int(GObject.get_current_time() - self.start_time) self.set_label('clock', str(seconds)) if self.robot and self.robot_time < seconds: self._find_a_match(robot_match=True) else: self.timeout_id = GObject.timeout_add(1000, self._counter) def _timer_reset(self): ''' Reset the timer for the robot ''' self.start_time = GObject.get_current_time() self.timeout_id = None self._counter() def _show_matches(self, i): ''' Show all the matches as a simple animation. ''' if i < self.matches and \ i * CARDS_IN_A_MATCH < len(self.match_list): for j in range(CARDS_IN_A_MATCH): self.grid.display_match( self.match_list[i * CARDS_IN_A_MATCH + j], j) self.match_timeout_id = GObject.timeout_add( 2000, self._show_matches, i + 1) def _find_a_match(self, robot_match=False): ''' Check to see whether there are any matches on the board. ''' # Before finding a match, return any cards from the match area if self._matches_on_display: if not self.deck.empty(): self._matches_on_display = False GObject.timeout_add(1000, self.clean_up_match) else: for c in self.clicked: if c.spr is not None: i = self.grid.find_an_empty_slot() if i is not None: c.spr.move(self.grid.grid_to_xy(i)) self.grid.grid[i] = self.deck.spr_to_card(c.spr) c.reset() a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] for i in Permutation(a): # TODO: really should be combination cardarray = [self.grid.grid[i[0]], self.grid.grid[i[1]], self.grid.grid[i[2]]] if self._match_check(cardarray, self.card_type): if robot_match: # Stop animations before moving robot match self.grid.stop_animation = True self._robot_match(i) return True return False def _robot_match(self, i): ''' Robot finds a match ''' for j in range(CARDS_IN_A_MATCH): # WARNING: Sometimes there is a race condition between the # robot delay and shoing a robot match. if self.grid.grid[i[j]] is not None: self.clicked[j].spr = self.grid.grid[i[j]].spr self.grid.grid[i[j]].spr.move(self.grid.match_to_xy(j)) else: _logger.error('in robot match, grid[%d] is None' % (i[j])) self.grid.grid[i[j]] = None self.robot_matches += 1 self._test_for_a_match() self._smiley[-1].spr.set_layer(100) self._matches_on_display = True def _match_check(self, cardarray, card_type): ''' For each attribute, either it is the same or different. ''' for a in cardarray: if a is None: return False if (cardarray[0].shape + cardarray[1].shape + cardarray[2].shape) % 3 != 0: self._failure = 0 return False if (cardarray[0].color + cardarray[1].color + cardarray[2].color) % 3 != 0: self._failure = 1 return False if (cardarray[0].fill + cardarray[1].fill + cardarray[2].fill) % 3 != 0: self._failure = 2 return False # Special case: only check number when shapes are the same if card_type == 'word': if cardarray[0].shape == cardarray[1].shape and \ cardarray[0].shape == cardarray[2].shape and \ (cardarray[0].num + cardarray[1].num + cardarray[2].num) % 3\ != 0: return False else: if (cardarray[0].num + cardarray[1].num + cardarray[2].num) % 3 != 0: self._failure = 3 return False self._failure = None return True def _choose_custom_card(self): ''' Select a custom card from the Journal ''' chooser = None name = None if hasattr(mime, 'GENERIC_TYPE_IMAGE'): # See #2398 if 'image/svg+xml' not in \ mime.get_generic_type(mime.GENERIC_TYPE_IMAGE).mime_types: mime.get_generic_type( mime.GENERIC_TYPE_IMAGE).mime_types.append('image/svg+xml') chooser = ObjectChooser(parent=self.activity, what_filter=mime.GENERIC_TYPE_IMAGE) else: try: chooser = ObjectChooser(parent=self, what_filter=None) except TypeError: chooser = ObjectChooser( None, self.activity, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT) if chooser is not None: try: result = chooser.run() if result == Gtk.ResponseType.ACCEPT: jobject = chooser.get_selected_object() if jobject and jobject.file_path: name = jobject.metadata['title'] mime_type = jobject.metadata['mime_type'] _logger.debug('result of choose: %s (%s)' % (name, str(mime_type))) finally: chooser.destroy() del chooser if name is not None: self._find_custom_paths(jobject) def _find_custom_paths(self, jobject): ''' Associate a Journal object with a card ''' found_a_sequence = False if self.custom_paths[0] is None: basename, suffix, i = _find_the_number_in_the_name( jobject.metadata['title']) ''' If this is the first card, try to find paths for other custom cards based on the name; else just load the card. ''' if i >= 0: dsobjects, nobjects = datastore.find( {'mime_type': [str(jobject.metadata['mime_type'])]}) self.custom_paths = [] if nobjects > 0: for j in range(DECKSIZE): for i in range(nobjects): if dsobjects[i].metadata['title'] == \ _construct_a_name(basename, j + 1, suffix): self.custom_paths.append(dsobjects[i]) break if len(self.custom_paths) < 9: for i in range(3, 81): self.custom_paths.append( self.custom_paths[int(i / 27)]) elif len(self.custom_paths) < 27: for i in range(9, 81): self.custom_paths.append( self.custom_paths[int(i / 9)]) elif len(self.custom_paths) < 81: for i in range(9, 81): self.custom_paths.append( self.custom_paths[int(i / 3)]) found_a_sequence = True self.activity.metadata['custom_object'] = jobject.object_id self.activity.metadata['custom_mime_type'] = \ jobject.metadata['mime_type'] if not found_a_sequence: grid_index = self.grid.spr_to_grid(self._edit_card.spr) self.custom_paths[grid_index] = jobject self.activity.metadata['custom_' + str(grid_index)] = \ jobject.object_id self.card_type = 'custom' self.activity.button_custom.set_icon('new-custom-game') self.activity.button_custom.set_tooltip(_('New custom game')) return def _in_motion(self, spr): ''' Is the sprite in a grid or match position or in motion? ''' x, y = spr.get_xy() if self.grid.xy_in_match((x, y)): return False if self.grid.xy_in_grid((x, y)): return False return True def help_animation(self): ''' Simple explanatory animation at start of play ''' for i in range(22): path = os.path.join(activity.get_bundle_path(), 'images', 'help-%d.svg' % i) svg_str = svg_from_file(path) pixbuf = svg_str_to_pixbuf(svg_str, int(self._width / 2), int(self._height / 2)) self._help.append(Sprite(self._sprites, int(self._width / 4), int(self._height / 4), pixbuf)) self._help[-1].hide() self._help_index = 0 self._stop_help = False self._help[self._help_index].set_layer(5000) self._help_timeout_id = GObject.timeout_add(2000, self._help_next) def _help_next(self): ''' Load the next frame in the animation ''' self._help[self._help_index].hide() if self._stop_help: self._help = [] return self._help_index += 1 self._help_index %= len(self._help) self._help[self._help_index].set_layer(5000) if self._help_index in [0, 9, 10, 20, 21]: self._help_timeout_id = GObject.timeout_add(2000, self._help_next) else: self._help_timeout_id = GObject.timeout_add(1000, self._help_next)
class Game(): def __init__(self, canvas, parent=None, path=None, colors=['#A0FFA0', '#FF8080']): self._canvas = canvas self._parent = parent self._parent.show_all() self._path = path self._colors = ['#FFFFFF'] self._colors.append(colors[0]) self._colors.append(colors[1]) self._canvas.set_can_focus(True) self._canvas.connect("draw", self._draw_cb) self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.connect("button-press-event", self._button_press_cb) self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - (GRID_CELL_SIZE * 1.5) self._scale = self._height / (4 * DOT_SIZE * 1.3) self._dot_size = int(DOT_SIZE * self._scale) self._space = int(self._dot_size / 5.) self._start_time = 0 self._timeout_id = None self._level = 3 self._game = 0 self._correct = 0 self._correct_for_level = 0 self._high_score_count = self.load_highscore() # Find the image files self._PATHS = glob(os.path.join(self._path, 'images'), '.png') self._CPATHS = glob(os.path.join(self._path, 'color-images'), '*.svg') # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) self._dots = [] self._opts = [] self._question = [] self._gameover = [] self._score = [] self._highscore = [] yoffset = int(self._space / 2.) for y in range(3): for x in range(6): xoffset = int((self._width - 6 * self._dot_size - 5 * self._space) / 2.) self._dots.append( Sprite(self._sprites, xoffset + x * (self._dot_size + self._space), y * (self._dot_size + self._space) + yoffset, self._new_dot_surface(color=self._colors[0]))) self._dots[-1].type = -1 # No image self._dots[-1].set_label_attributes(72) y = 3 for x in range(3): self._opts.append( Sprite(self._sprites, xoffset + x * (self._dot_size + self._space), y * (self._dot_size + self._space) + yoffset, self._new_dot_surface(color=self._colors[0]))) self._opts[-1].type = -1 # No image self._opts[-1].set_label_attributes(72) self._opts[-1].hide() for x in range(1, 6): self._question.append( Sprite(self._sprites, xoffset + (x) * (self._dot_size - 2), y * (self._dot_size - 120 + self._space) + yoffset, self._new_dot_surface(color=self._colors[2]))) self._question[-1].type = -1 # No image self._question[-1].set_label_attributes(72) self._question[-1].hide() def _all_clear(self): ''' Things to reinitialize when starting up a new game. ''' if self._timeout_id is not None: GLib.source_remove(self._timeout_id) # Auto advance levels if self._correct > 3 and self._level < len(self._dots): self._level += 3 self._correct_for_level = 0 self._set_label('') for question_shape in self._question: question_shape.hide() for gameover_shape in self._gameover: gameover_shape.hide() for score_shape in self._score: score_shape.hide() for highscore_shape in self._highscore: highscore_shape.hide() for i in range(3): self._opts[i].hide() self._opts[i].type = -1 self._opts[i].set_label('') for dot in self._dots: dot.type = -1 if self._game == 2 or self._dots.index(dot) < self._level: dot.set_shape(self._new_dot_surface( self._colors[abs(dot.type)])) dot.set_label('?') dot.set_layer(100) else: dot.hide() self._dance_counter = 0 self._dance_step() def _dance_step(self): ''' Short animation before loading new game ''' if self._game == 2: for i in range(len(self._dots)): self._dots[i].set_shape(self._new_dot_surface( self._colors[int(uniform(0, 3))])) else: for i in range(self._level): self._dots[i].set_shape(self._new_dot_surface( self._colors[int(uniform(0, 3))])) self._dance_counter += 1 if self._dance_counter < 10: self._timeout_id = GLib.timeout_add(500, self._dance_step) else: self._new_game() def new_game(self, game=None, restart=True): ''' Start a new game. ''' if game is not None: self._game = game self._level = 3 self._correct = 0 self._correct_for_level = 0 if restart: if self._correct < 10: self._all_clear() else: self._game_over() def _image_in_dots(self, n): for i in range(self._level): if self._dots[i].type == n: return True return False def _image_in_opts(self, n): for i in range(3): if self._opts[i].type == n: return True return False def _choose_random_images(self): ''' Choose images at random ''' maxi = len(self._PATHS) for i in range(self._level): if self._dots[i].type == -1: n = int(uniform(0, maxi)) while self._image_in_dots(n): n = int(uniform(0, maxi)) self._dots[i].type = n self._dots[i].set_shape(self._new_dot_surface( image=self._dots[i].type)) self._dots[i].set_layer(100) self._dots[i].set_label('') def _load_image_from_list(self): if self._recall_counter == len(self._recall_list): self._timeout_id = GLib.timeout_add( 1000, self._ask_the_question) return for dot in self._dots: dot.type = self._recall_list[self._recall_counter] dot.set_shape(self._new_dot_surface(image=dot.type)) dot.set_layer(100) dot.set_label('') self._recall_counter += 1 self._timeout_id = GLib.timeout_add( 1000, self._load_image_from_list) def _find_repeat(self): ''' Find an image that repeats ''' for i in range(self._level): for j in range(self._level - i - 1): if self._dots[i].type == self._dots[j].type: return i return None def _new_game(self, restore=False): ''' Load game images and then ask a question... ''' if self._game in [0, 1]: self._choose_random_images() else: # game 2 # generate a random list self._recall_list = [] for i in range(12): n = int(uniform(0, len(self._PATHS))) while n in self._recall_list: n = int(uniform(0, len(self._PATHS))) self._recall_list.append(n) self._recall_counter = 0 self._load_image_from_list() if self._game == 0: if not restore: # Repeat at least one of the images self._repeat = int(uniform(0, self._level)) n = (self._repeat + int(uniform(1, self._level))) % self._level _logger.debug('repeat=%d, n=%d' % (self._repeat, n)) self._dots[self._repeat].set_shape(self._new_dot_surface( image=self._dots[n].type)) self._dots[self._repeat].type = self._dots[n].type else: # Find repeated image, as that is the answer self._repeat = self._find_repeat() if self._repeat is None: _logger.debug('could not find repeat') self._repeat = 0 if self._game in [0, 1]: self._timeout_id = GLib.timeout_add( 3000, self._ask_the_question) def _ask_the_question(self): ''' Each game has a challenge ''' self._timeout_id = None for i in range(3): self._opts[i].set_label('') # Hide the dots if self._game == 2: for dot in self._dots: dot.hide() else: for i in range(self._level): self._dots[i].hide() if self._game == 0: text = [] text.append("¿") text.append(" recall ") text.append(" repeated ") text.append(" image ") text.append("?") i = 0 for question_shape in self._question: question_shape.type = -1 question_shape.set_shape(self._new_dot_surface( self._colors[2])) question_shape.set_label(text[i]) question_shape.set_layer(100) i += 1 # Show the possible solutions for i in range(3): n = int(uniform(0, len(self._PATHS))) if self._level == 3: while(n == self._dots[self._repeat].type or self._image_in_opts(n)): n = int(uniform(0, len(self._PATHS))) else: while(n == self._dots[self._repeat].type or not self._image_in_dots(n) or self._image_in_opts(n)): n = int(uniform(0, len(self._PATHS))) self._opts[i].type = n self._answer = int(uniform(0, 3)) self._opts[self._answer].type = self._dots[self._repeat].type for i in range(3): self._opts[i].set_shape(self._new_dot_surface( image=self._opts[i].type)) self._opts[i].set_layer(100) elif self._game == 1: text = [ "¿", " recall ", " not shown ", " image ", "?" ] i = 0 for question_shape in self._question: question_shape.type = -1 question_shape.set_shape(self._new_dot_surface( self._colors[2])) question_shape.set_label(text[i]) question_shape.set_layer(100) i += 1 # Show the possible solutions for i in range(3): n = int(uniform(0, len(self._PATHS))) while(not self._image_in_dots(n) or self._image_in_opts(n)): n = int(uniform(0, len(self._PATHS))) self._opts[i].type = n self._answer = int(uniform(0, 3)) n = int(uniform(0, len(self._PATHS))) while(self._image_in_dots(n)): n = int(uniform(0, len(self._PATHS))) self._opts[self._answer].type = n for i in range(3): self._opts[i].set_shape(self._new_dot_surface( image=self._opts[i].type)) self._opts[i].set_layer(100) elif self._game == 2: text = [ "¿", " what was ", " displayed " + (str(int(self._level / 3))) + " ", " time(s) ago ", "?" ] i = 0 for question_shape in self._question: question_shape.type = -1 question_shape.set_shape(self._new_dot_surface( self._colors[2])) question_shape.set_label(text[i]) question_shape.set_layer(100) i += 1 # Show the possible solutions for i in range(3): self._answer = len(self._recall_list) - \ int(self._level / 3) - 1 n = int(uniform(0, len(self._recall_list))) while n == self._answer: n = int(uniform(0, len(self._recall_list))) self._opts[i].type = n i = int(uniform(0, 3)) self._opts[i].type = self._recall_list[self._answer] for i in range(3): self._opts[i].set_shape(self._new_dot_surface( image=self._opts[i].type)) self._opts[i].set_layer(100) else: for question_shape in self._question: question_shape.hide() def restore_game(self, dot_list, correct=0, level=3, game=0): ''' Restore a game from the Journal ''' # TODO: Save/restore recall list for game 2 self._correct = correct self._level = level self._game = game for i, dot in enumerate(dot_list): self._dots[i].type = dot if dot == -1: self._dots[i].hide() self._new_game(restore=True) def save_game(self): ''' Return dot list for saving to Journal ''' dot_list = [] for dot in self._dots: dot_list.append(dot.type) return dot_list, self._correct, self._level, self._game def _set_label(self, string): ''' Set the label in the toolbar or the window frame. ''' self._parent.status.set_label(string) def _button_press_cb(self, win, event): if self._timeout_id is not None: _logger.debug('still in timeout... ignoring click') return for question_shape in self._question: question_shape.hide() win.grab_focus() x, y = list(map(int, event.get_coords())) spr = self._sprites.find_sprite((x, y)) if spr == None: return current = self._correct if self._game in [0, 1]: for i in range(3): if self._opts[i] == spr: break self._opts[i].set_shape(self._new_dot_surface( color=self._colors[0])) if i == self._answer: self._opts[i].set_label('☻') self._correct += 1 self._correct_for_level += 1 else: self._opts[i].set_label('☹') self._correct_for_level = 0 else: for i in range(3): if self._opts[i] == spr: break self._opts[i].set_shape(self._new_dot_surface( color=self._colors[0])) if self._opts[i].type == self._recall_list[self._answer]: self._opts[i].set_label('☻') self._correct += 2 self._correct_for_level += 1 else: self._opts[i].set_label('☹') self._correct_for_level = 0 if self._game in [0, 1]: for i in range(self._level): self._dots[i].set_layer(100) else: for question_shape in self._question: question_shape.hide() for dot in self._dots: dot.set_shape(self._new_dot_surface( image=self._recall_list[self._answer])) dot.set_layer(100) if self._correct > current: self._timeout_id = GLib.timeout_add(1000, self.new_game) else: self._timeout_id = GLib.timeout_add(2000, self._game_over) return True def _game_over(self): for opt in self._opts: opt.hide() opt.type = -1 for dot in self._dots: dot.hide() for question_shape in self._question: question_shape.hide() self.save_highscore() yoffset = int(self._space / 4.) xoffset = int((self._width - 6 * self._dot_size - 5 * self._space) / 2.) y = 1 i = 0 for x in range(2, 6): self._gameover.append( Sprite(self._sprites, xoffset + (x - 0.25) * (self._dot_size - 15), y * (self._dot_size - 90 + self._space) + yoffset, self._new_dot_surface(color=self._colors[1]))) self._gameover[-1].type = -1 # No image self._gameover[-1].set_label_attributes(72) i += 1 text = [ "☻", " Game ", " Over ", "☻" ] self.rings(len(text), text, self._gameover) y = 2 for x in range(2, 5): self._score.append( Sprite(self._sprites, xoffset + (x + 0.25) * (self._dot_size - 15), y * (self._dot_size - 30 + self._space) + yoffset, self._new_dot_surface(color=self._colors[1]))) self._score[-1].type = -1 # No image self._score[-1].set_label_attributes(72) text = [ " your ", " score: ", (f" {self._correct} ") ] self.rings(len(text), text, self._score) y = 3 if self._correct > self._high_score_count: self._high_score_count = self._correct for x in range(2, 5): self._highscore.append( Sprite(self._sprites, xoffset + (x + 0.25) * (self._dot_size - 15), y * (self._dot_size - 20 + self._space) + yoffset, self._new_dot_surface(color=self._colors[1]))) self._highscore[-1].type = -1 # No image self._highscore[-1].set_label_attributes(72) text = [ " high ", " score: ", (f" {self._high_score_count} ") ] self.rings(len(text), text, self._highscore) self._correct = 0 self._timeout_id = GLib.timeout_add(5000, self.new_game) def rings(self, num, text, shape): i = 0 for x in range(num): shape[x].type = -1 shape[x].set_shape(self._new_dot_surface( self._colors[2])) shape[x].set_label(text[i]) shape[x].set_layer(100) i += 1 def _draw_cb(self, win, context): self.do_draw(win, context) def do_draw(self, win, cr): ''' Handle the draw-event by drawing ''' # Restrict Cairo to the exposed area alloc = win.get_allocation() cr.rectangle(0, 0, alloc.width, alloc.height) cr.set_source_rgb(1, 1, 1) cr.fill() cr.rectangle(0, 0, alloc.width, alloc.height) cr.clip() # Refresh sprite list self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): Gtk.main_quit() def _new_dot_surface(self, color='#000000', image=None, color_image=None): ''' generate a dot of a color color ''' self._dot_cache = {} if color_image is not None: if color_image + 10000 in self._dot_cache: return self._dot_cache[color_image + 10000] pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._path, self._CPATHS[color_image]), self._svg_width, self._svg_height) elif image is not None: if image in self._dot_cache: return self._dot_cache[image] pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._path, self._PATHS[image]), self._svg_width, self._svg_height) else: if color in self._dot_cache: return self._dot_cache[color] self._stroke = color self._fill = color self._svg_width = self._dot_size self._svg_height = self._dot_size # i = self._colors.index(color) pixbuf = svg_str_to_pixbuf( self._header() + self._circle(self._dot_size / 2., self._dot_size / 2., self._dot_size / 2.) + self._footer()) surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self._svg_width, self._svg_height) context = cairo.Context(surface) Gdk.cairo_set_source_pixbuf(context, pixbuf, 0, 0) context.rectangle(0, 0, self._svg_width, self._svg_height) context.fill() if color_image is not None: self._dot_cache[color_image + 10000] = surface elif image is not None: self._dot_cache[image] = surface else: self._dot_cache[color] = surface return surface def _line(self, vertical=True): ''' Generate a center line ''' if vertical: self._svg_width = 3 self._svg_height = self._height return svg_str_to_pixbuf( self._header() + self._rect(3, self._height, 0, 0) + self._footer()) else: self._svg_width = self._width self._svg_height = 3 return svg_str_to_pixbuf( self._header() + self._rect(self._width, 3, 0, 0) + self._footer()) def _header(self): return '<svg\n' + 'xmlns:svg="http://www.w3.org/2000/svg"\n' + \ 'xmlns="http://www.w3.org/2000/svg"\n' + \ 'xmlns:xlink="http://www.w3.org/1999/xlink"\n' + \ 'version="1.1"\n' + 'width="' + str(self._svg_width) + '"\n' + \ 'height="' + str(self._svg_height) + '">\n' def _rect(self, w, h, x, y): svg_string = ' <rect\n' svg_string += ' width="%f"\n' % (w) svg_string += ' height="%f"\n' % (h) svg_string += ' rx="%f"\n' % (0) svg_string += ' ry="%f"\n' % (0) svg_string += ' x="%f"\n' % (x) svg_string += ' y="%f"\n' % (y) svg_string += 'style="fill:#000000;stroke:#000000;"/>\n' return svg_string def _circle(self, r, cx, cy): return '<circle style="fill:' + str(self._fill) + ';stroke:' + \ str(self._stroke) + ';" r="' + str(r - 0.5) + '" cx="' + \ str(cx) + '" cy="' + str(cy) + '" />\n' def _footer(self): return '</svg>\n' def save_highscore(self): file_path = os.path.join(get_activity_root(), 'data', 'highscore') highscore = [0] if os.path.exists(file_path): with open(file_path, "r") as fp: highscore = fp.readlines() int_highscore = int(highscore[0]) if not int_highscore > self._correct: with open(file_path, "w") as fp: fp.write(str(self._correct)) def load_highscore(self): file_path = os.path.join(get_activity_root(), 'data', 'highscore') if os.path.exists(file_path): try: with open(file_path, "r") as fp: highscore = fp.readlines() return int(highscore[0]) except ValueError or IndexError: return 0 return 0
class Game(): """ The game play -- called from within Sugar or GNOME """ def __init__(self, canvas, path, parent=None): """ Initialize the playing surface """ self.path = path self.activity = parent # starting from command line # we have to do all the work that was done in CardSortActivity.py if parent is None: self.sugar = False self.canvas = canvas # starting from Sugar else: self.sugar = True self.canvas = canvas parent.show_all() self.canvas.set_flags(gtk.CAN_FOCUS) self.canvas.add_events(gtk.gdk.BUTTON_PRESS_MASK) self.canvas.add_events(gtk.gdk.BUTTON_RELEASE_MASK) self.canvas.connect("expose-event", self._expose_cb) self.canvas.connect("button-press-event", self._button_press_cb) self.canvas.connect("button-release-event", self._button_release_cb) self.canvas.connect("key_press_event", self._keypress_cb) self.width = gtk.gdk.screen_width() self.height = gtk.gdk.screen_height() - GRID_CELL_SIZE self.card_dim = CARD_DIM self.scale = 0.6 * self.height / (self.card_dim * 3) # Initialize the sprite repository self.sprites = Sprites(self.canvas) # Initialize the grid self.mode = 'rectangle' self.grid = Grid(self, self.mode) self.bounds = LEVEL_BOUNDS[0] self.level = 0 # Start solving the puzzle self.press = None self.release = None self.start_drag = [0, 0] def _button_press_cb(self, win, event): win.grab_focus() x, y = map(int, event.get_coords()) self.start_drag = [x, y] spr = self.sprites.find_sprite((x, y)) if spr is None: self.press = None self.release = None return True # take note of card under button press self.press = spr return True def _button_release_cb(self, win, event): win.grab_focus() x, y = map(int, event.get_coords()) spr = self.sprites.find_sprite((x, y)) if spr is None: self.press = None self.release = None return True # take note of card under button release self.release = spr # if press and release are the same card (click), then rotate if self.press == self.release: self.press.set_layer(0) self.grid.card_table[self.grid.grid[self.grid.spr_to_i( self.press)]].rotate_ccw() if self.mode == 'hexagon': # Rotate a second time self.grid.card_table[self.grid.grid[self.grid.spr_to_i( self.press)]].rotate_ccw() self.press.set_layer(100) else: self.grid.swap(self.press, self.release, self.mode) self.press = None self.release = None if self.test() == True: if self.level < 2: gobject.timeout_add(3000, self.activity.change_play_level_cb, None) return True def _keypress_cb(self, area, event): """ Keypress is used to ... """ k = gtk.gdk.keyval_name(event.keyval) def _expose_cb(self, win, event): ''' Callback to handle window expose events ''' self.do_expose_event(event) return True def do_expose_event(self, event): ''' Handle the expose-event by drawing ''' # Restrict Cairo to the exposed area cr = self.canvas.window.cairo_create() cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list self.sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): gtk.main_quit() def mask(self, level): """ mask out cards not on play level """ self.grid.hide_list(MASKS[level]) self.bounds = LEVEL_BOUNDS[level] self.level = level def test(self): """ Test the grid to see if the level is solved """ if self.mode != 'rectangle': return False for i in range(24): if i not in MASKS[self.level]: if not self.test_card(i): return False return True def test_card(self, i): """ Test a card with its neighbors; tests are bounded by the level """ row = int(i/6) col = i%6 if row > self.bounds[0][0] and row <= self.bounds[0][1]: if C[self.grid.grid[i]][rotate_index(0, self.grid.card_table[self.grid.grid[i]].orientation)] != \ C[self.grid.grid[i - 6]][rotate_index(1, self.grid.card_table[self.grid.grid[i - 6]].orientation)]: return False if C[self.grid.grid[i]][rotate_index(3, self.grid.card_table[self.grid.grid[i]].orientation)] != \ C[self.grid.grid[i - 6]][rotate_index(2, self.grid.card_table[self.grid.grid[i - 6]].orientation)]: return False if col > self.bounds[2][0] and col <= self.bounds[2][1]: if C[self.grid.grid[i]][rotate_index(3, self.grid.card_table[self.grid.grid[i]].orientation)] != \ C[self.grid.grid[i - 1]][rotate_index(0, self.grid.card_table[self.grid.grid[i - 1]].orientation)]: return False if C[self.grid.grid[i]][rotate_index(2, self.grid.card_table[self.grid.grid[i]].orientation)] != \ C[self.grid.grid[i - 1]][rotate_index(1, self.grid.card_table[self.grid.grid[i - 1]].orientation)]: return False return True
class Game(): def __init__(self, canvas, parent=None, path=None, root=None, mode='array', colors=['#A0FFA0', '#FF8080']): self._canvas = canvas self._parent = parent self._path = path self._root = root self._mode = mode self.current_image = 0 self.playing = False self._timeout_id = None self._prev_mouse_pos = (0, 0) self._start_time = 0 self._colors = ['#FFFFFF'] self._colors.append(colors[0]) self._colors.append(colors[1]) self._canvas.add_events( Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK | Gdk.EventMask.BUTTON_MOTION_MASK | Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.POINTER_MOTION_HINT_MASK | Gdk.EventMask.TOUCH_MASK) self._canvas.connect('draw', self.__draw_cb) self._canvas.connect('event', self.__event_cb) self.configure(move=False) self.we_are_sharing = False self._start_time = 0 self._timeout_id = None # Find the image files self._PATHS = glob.glob(os.path.join(self._path, 'images', '*.svg')) # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) a = max(Gdk.Screen.width(), Gdk.Screen.height()) b = min(Gdk.Screen.width(), Gdk.Screen.height()) self._bg_pixbufs = [] if self._parent.tablet_mode: # text on top # landscape self._bg_pixbufs.append(svg_str_to_pixbuf(genhole( a, a, 3 * style.GRID_CELL_SIZE, style.DEFAULT_SPACING, a - 3 * style.GRID_CELL_SIZE, style.GRID_CELL_SIZE * 3 + style.DEFAULT_SPACING))) # portrait self._bg_pixbufs.append(svg_str_to_pixbuf(genhole( a, a, 3 * style.GRID_CELL_SIZE, style.DEFAULT_SPACING, b - 3 * style.GRID_CELL_SIZE, style.GRID_CELL_SIZE * 3 + style.DEFAULT_SPACING))) else: # text on bottom # landscape self._bg_pixbufs.append(svg_str_to_pixbuf(genhole( a, a, 3 * style.GRID_CELL_SIZE, b - style.GRID_CELL_SIZE * 4 - style.DEFAULT_SPACING, a - 3 * style.GRID_CELL_SIZE, b - style.GRID_CELL_SIZE - style.DEFAULT_SPACING))) # portrait self._bg_pixbufs.append(svg_str_to_pixbuf(genhole( a, a, 3 * style.GRID_CELL_SIZE, a - style.GRID_CELL_SIZE * 4 - style.DEFAULT_SPACING, b - 3 * style.GRID_CELL_SIZE, a - style.GRID_CELL_SIZE - style.DEFAULT_SPACING))) if Gdk.Screen.width() > Gdk.Screen.height(): self._bg = Sprite(self._sprites, 0, 0, self._bg_pixbufs[0]) else: self._bg = Sprite(self._sprites, 0, 0, self._bg_pixbufs[1]) self._bg.set_layer(-2) self._bg.type = 'background' size = 3 * self._dot_size + 4 * self._space x = int((Gdk.Screen.width() - size) / 2.) self._dots = [] self._Dots = [] # larger dots for linear mode X = int((Gdk.Screen.width() - self._dot_size * 3) / 2.) Y = style.GRID_CELL_SIZE + self._yoff if self._parent.tablet_mode: yoffset = self._space * 2 + self._yoff else: yoffset = self._yoff for y in range(3): for x in range(3): xoffset = int((self._width - 3 * self._dot_size - 2 * self._space) / 2.) self._dots.append( Sprite(self._sprites, xoffset + x * (self._dot_size + self._space), y * (self._dot_size + self._space) + yoffset, self._new_dot_surface(color=self._colors[0]))) self._dots[-1].type = -1 # No image self._dots[-1].set_label_attributes(72) self._dots[-1].set_label('?') self._Dots.append( Sprite( self._sprites, X, Y, self._new_dot_surface(color=self._colors[0], large=True))) self._Dots[-1].type = -1 # No image self._Dots[-1].set_label_attributes(72 * 3) self._Dots[-1].set_label('?') self.number_of_images = len(self._PATHS) if USE_ART4APPS: self._art4apps = Art4Apps() self.number_of_images = len(self._art4apps.get_words()) self._record_pixbufs = [] for icon in ['media-audio', 'media-audio-recording']: self._record_pixbufs.append( GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._root, 'icons', icon + '.svg'), style.GRID_CELL_SIZE, style.GRID_CELL_SIZE)) self._play_pixbufs = [] for icon in ['play-inactive', 'play']: self._play_pixbufs.append( GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._root, 'icons', icon + '.svg'), style.GRID_CELL_SIZE, style.GRID_CELL_SIZE)) self._speak_pixbufs = [] for icon in ['speak-inactive', 'speak']: self._speak_pixbufs.append( GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._root, 'icons', icon + '.svg'), style.GRID_CELL_SIZE, style.GRID_CELL_SIZE)) left = style.GRID_CELL_SIZE right = Gdk.Screen.width() - 2 * style.GRID_CELL_SIZE y0 = style.DEFAULT_SPACING + style.DEFAULT_PADDING y1 = y0 + style.GRID_CELL_SIZE y2 = y1 + style.GRID_CELL_SIZE if not self._parent.tablet_mode: dy = Gdk.Screen.height() - 4 * style.GRID_CELL_SIZE - \ 2 * style.DEFAULT_SPACING y0 += dy y1 += dy y2 += dy y3 = int((Gdk.Screen.height() - 2 * style.GRID_CELL_SIZE) / 2) self._record = Sprite( self._sprites, right, y0, self._record_pixbufs[RECORD_OFF]) self._record.set_layer(1) self._record.type = 'record' self._play = Sprite( self._sprites, right, y1, self._play_pixbufs[PLAY_OFF]) self._play.set_layer(1) self._play.type = 'play-inactive' self._speak = Sprite( self._sprites, right, y2, self._speak_pixbufs[SPEAK_OFF]) self._speak.set_layer(1) self._speak.type = 'speak-inactive' self._next_prev_pixbufs = [] for icon in ['go-previous', 'go-next', 'go-previous-inactive', 'go-next-inactive']: self._next_prev_pixbufs.append( GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._root, 'icons', icon + '.svg'), style.GRID_CELL_SIZE, style.GRID_CELL_SIZE)) self._prev = Sprite( self._sprites, left, y3, self._next_prev_pixbufs[PREV_INACTIVE]) self._prev.set_layer(1) self._prev.type = 'prev' if self._mode == 'array': self._prev.hide() self._next = Sprite( self._sprites, right, y3, self._next_prev_pixbufs[NEXT]) self._next.set_layer(1) self._next.type = 'next' if self._mode == 'array': self._next.hide() def configure(self, move=True): self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - style.GRID_CELL_SIZE if not move: if self._height < self._width: self._scale = self._height / (3 * DOT_SIZE * 1.2) else: self._scale = self._width / (3 * DOT_SIZE * 1.2) self._scale /= 1.5 self._dot_size = int(DOT_SIZE * self._scale) if self._parent.tablet_mode: # text on top self._yoff = style.GRID_CELL_SIZE * 3 + style.DEFAULT_SPACING else: self._yoff = style.DEFAULT_SPACING self._space = int(self._dot_size / 5.) return left = style.GRID_CELL_SIZE right = Gdk.Screen.width() - 2 * style.GRID_CELL_SIZE y0 = style.DEFAULT_SPACING + style.DEFAULT_PADDING y1 = y0 + style.GRID_CELL_SIZE y2 = y1 + style.GRID_CELL_SIZE if not self._parent.tablet_mode: dy = Gdk.Screen.height() - 4 * style.GRID_CELL_SIZE - \ 2 * style.DEFAULT_SPACING y0 += dy y1 += dy y2 += dy y3 = int((Gdk.Screen.height() - 2 * style.GRID_CELL_SIZE) / 2) self._record.move((right, y0)) self._play.move((right, y1)) self._speak.move((right, y2)) self._prev.move((left, y3)) self._next.move((right, y3)) # Move the dots X = int((Gdk.Screen.width() - self._dot_size * 3) / 2.) Y = style.GRID_CELL_SIZE + self._yoff if self._parent.tablet_mode: yoffset = self._space * 2 + self._yoff else: yoffset = self._yoff for y in range(3): for x in range(3): xoffset = int((self._width - 3 * self._dot_size - 2 * self._space) / 2.) self._dots[x + y * 3].move( (xoffset + x * (self._dot_size + self._space), y * (self._dot_size + self._space) + yoffset)) self._Dots[x + y * 3].move((X, Y)) # switch orientation the bg sprite if Gdk.Screen.width() > Gdk.Screen.height(): self._bg.set_image(self._bg_pixbufs[0]) else: self._bg.set_image(self._bg_pixbufs[1]) self._bg.set_layer(-2) def set_speak_icon_state(self, state): if state: self._speak.set_image(self._speak_pixbufs[SPEAK_ON]) self._speak.type = 'speak' else: self._speak.set_image(self._speak_pixbufs[SPEAK_OFF]) self._speak.type = 'speak-inactive' self._speak.set_layer(1) def set_record_icon_state(self, state): if state: self._record.set_image(self._record_pixbufs[RECORD_ON]) else: self._record.set_image(self._record_pixbufs[RECORD_OFF]) self._record.set_layer(1) def set_play_icon_state(self, state): if state: self._play.set_image(self._play_pixbufs[PLAY_ON]) self._play.type = 'play' else: self._play.set_image(self._play_pixbufs[PLAY_OFF]) self._play.type = 'play-inactive' self._play.set_layer(1) def autoplay(self): self.set_mode('linear') # forces current image to 0 self.playing = True self._autonext(next=False) def stop(self): self.playing = False if self._parent.audio_process is not None: self._parent.audio_process.terminate() self._parent.audio_process = None if self._timeout_id is not None: GObject.source_remove(self._timeout_id) self._timeout_id = None self._parent.autoplay_button.set_icon_name('media-playback-start') self._parent.autoplay_button.set_tooltip(_('Play')) self._parent.array_button.set_sensitive(True) def _autonext(self, next=True): self._timeout_id = None if not self.playing: return if next: self._Dots[self.current_image].hide() self.current_image += 1 self._Dots[self.current_image].set_layer(100) if self.current_image == 8: self._next.set_image( self._next_prev_pixbufs[NEXT_INACTIVE]) self._next.set_layer(1) self._prev.set_image(self._next_prev_pixbufs[PREV]) self._prev.set_layer(1) self._parent.check_audio_status() self._parent.check_text_status() GObject.idle_add(self._play_sound) def _poll_audio(self): if self._parent.audio_process is None: # Already stopped? return if self._parent.audio_process.poll() is None: GObject.timeout_add(200, self._poll_audio) else: self._parent.audio_process = None self._next_image() def _play_sound(self): self._start_time = time.time() # Either play back a recording or speak the text if self._play.type == 'play': self._parent.playback_recording_cb() self._poll_audio() elif self._speak.type == 'speak': bounds = self._parent.text_buffer.get_bounds() text = self._parent.text_buffer.get_text( bounds[0], bounds[1], True) speak(text) self._next_image() def _next_image(self): accumulated_time = int(time.time() - self._start_time) if accumulated_time < 5: pause = 5 - accumulated_time else: pause = 1 if self.playing and self.current_image < 8: self._timeout_id = GObject.timeout_add(pause * 1000, self._autonext) else: self.stop() def __event_cb(self, win, event): ''' The mouse button was pressed. Is it on a sprite? or there was a gesture. ''' left = right = False if event.type in (Gdk.EventType.TOUCH_BEGIN, Gdk.EventType.TOUCH_CANCEL, Gdk.EventType.TOUCH_END, Gdk.EventType.BUTTON_PRESS, Gdk.EventType.BUTTON_RELEASE): x = int(event.get_coords()[1]) y = int(event.get_coords()[2]) if event.type in (Gdk.EventType.TOUCH_BEGIN, Gdk.EventType.BUTTON_PRESS): self._prev_mouse_pos = (x, y) elif event.type in (Gdk.EventType.TOUCH_END, Gdk.EventType.BUTTON_RELEASE): if self._parent.audio_process is not None: self._parent.audio_process.terminate() self._parent.audio_process = None terminated_audio = True else: terminated_audio = False if self.playing: self.stop() new_mouse_pos = (x, y) mouse_movement = (new_mouse_pos[0] - self._prev_mouse_pos[0], new_mouse_pos[1] - self._prev_mouse_pos[1]) # horizontal gestures only if (abs(mouse_movement[0]) / 5) > abs(mouse_movement[1]): if abs(mouse_movement[0]) > abs(mouse_movement[1]): if mouse_movement[0] < 0: right = True else: left = True if event.type in (Gdk.EventType.TOUCH_END, Gdk.EventType.BUTTON_RELEASE): spr = self._sprites.find_sprite((x, y)) if left or right or spr is not None: if spr.type in ['record', 'play', 'play-inactive', 'speak', 'speak-inactive']: if spr.type == 'record': self._parent.record_cb() elif spr.type == 'play' and not terminated_audio: self._parent.playback_recording_cb() elif spr.type == 'speak': bounds = self._parent.text_buffer.get_bounds() text = self._parent.text_buffer.get_text( bounds[0], bounds[1], True) speak(text) return elif self._mode == 'array': return self._parent.speak_text_cb() if self._parent.recording: self._parent.record_cb() if (left or spr.type == 'prev') and self.current_image > 0: self._Dots[self.current_image].hide() self.current_image -= 1 self._Dots[self.current_image].set_layer(100) if self.current_image == 0: self._prev.set_image( self._next_prev_pixbufs[PREV_INACTIVE]) self._next.set_image(self._next_prev_pixbufs[NEXT]) elif (right or spr.type == 'next') and self.current_image < 8: self._Dots[self.current_image].hide() self.current_image += 1 self._Dots[self.current_image].set_layer(100) if self.current_image == 8: self._next.set_image( self._next_prev_pixbufs[NEXT_INACTIVE]) self._prev.set_image(self._next_prev_pixbufs[PREV]) elif spr.type not in ['prev', 'background'] and \ self.current_image < 8: self._Dots[self.current_image].hide() self.current_image += 1 self._Dots[self.current_image].set_layer(100) if self.current_image == 8: self._next.set_image( self._next_prev_pixbufs[NEXT_INACTIVE]) self._prev.set_image(self._next_prev_pixbufs[PREV]) self._parent.check_audio_status() self._parent.check_text_status() self._prev.set_layer(1) self._next.set_layer(1) return False def get_mode(self): return self._mode def set_mode(self, mode): self.current_image = 0 self._prev.set_image(self._next_prev_pixbufs[PREV_INACTIVE]) self._next.set_image(self._next_prev_pixbufs[NEXT]) if mode == 'array': self._mode = 'array' self._prev.hide() self._next.hide() else: self._mode = 'linear' self._prev.set_layer(1) self._next.set_layer(1) for i in range(9): if self._mode == 'array': self._dots[i].set_layer(100) self._Dots[i].hide() else: self._dots[i].hide() if self.current_image == i: self._Dots[i].set_layer(100) else: self._Dots[i].hide() def _all_clear(self): ''' Things to reinitialize when starting up a new game. ''' if self._timeout_id is not None: GObject.source_remove(self._timeout_id) self.set_mode(self._mode) if self._mode == 'array': for dot in self._dots: if dot.type != -1: dot.type = -1 dot.set_shape(self._new_dot_surface( self._colors[abs(dot.type)])) dot.set_label('?') else: for dot in self._Dots: if dot.type != -1: dot.type = -1 dot.set_shape(self._new_dot_surface( self._colors[abs(dot.type)], large=True)) dot.set_label('?') self._dance_counter = 0 self._dance_step() def _dance_step(self): ''' Short animation before loading new game ''' if self._mode == 'array': for dot in self._dots: dot.set_shape(self._new_dot_surface( self._colors[int(uniform(0, 3))])) else: self._Dots[0].set_shape(self._new_dot_surface( self._colors[int(uniform(0, 3))], large=True)) self._dance_counter += 1 if self._dance_counter < 10: self._timeout_id = GObject.timeout_add(500, self._dance_step) else: self._new_images() def new_game(self): ''' Start a new game. ''' self._all_clear() def _new_images(self): ''' Select pictures at random ''' used_images = [0] * self.number_of_images for i in range(9): random_selection = int(uniform(0, self.number_of_images)) while used_images[random_selection] != 0: random_selection = int(uniform(0, self.number_of_images)) used_images[random_selection] = 1 self._dots[i].set_label('') self._dots[i].type = random_selection self._dots[i].set_shape(self._new_dot_surface( image=self._dots[i].type)) self._Dots[i].set_label('') self._Dots[i].type = self._dots[i].type self._Dots[i].set_shape(self._new_dot_surface( image=self._Dots[i].type, large=True)) if self._mode == 'array': self._dots[i].set_layer(100) self._Dots[i].hide() else: if self.current_image == i: self._Dots[i].set_layer(100) else: self._Dots[i].hide() self._dots[i].hide() if self.we_are_sharing: self._parent.send_new_images() def restore_game(self, dot_list): ''' Restore a game from the Journal or share ''' self.set_mode(self._mode) for i, dot in enumerate(dot_list): self._dots[i].type = dot self._dots[i].set_shape(self._new_dot_surface( image=self._dots[i].type)) self._dots[i].set_label('') self._Dots[i].type = dot self._Dots[i].set_shape(self._new_dot_surface( image=self._Dots[i].type, large=True)) self._Dots[i].set_label('') if self._mode == 'array': self._dots[i].set_layer(100) self._Dots[i].hide() else: if self.current_image == i: self._Dots[i].set_layer(100) else: self._Dots[i].hide() self._dots[i].hide() def save_game(self): ''' Return dot list for saving to Journal or sharing ''' dot_list = [] for dot in self._dots: dot_list.append(dot.type) return dot_list def set_sharing(self, share=True): self.we_are_sharing = share def _grid_to_dot(self, pos): ''' calculate the dot index from a column and row in the grid ''' return pos[0] + pos[1] * 3 def _dot_to_grid(self, dot): ''' calculate the grid column and row for a dot ''' return [dot % 3, int(dot / 3)] def __draw_cb(self, canvas, cr): self._sprites.redraw_sprites(cr=cr) def __expose_cb(self, win, event): ''' Callback to handle window expose events ''' self.do_expose_event(event) return True # Handle the expose-event by drawing def do_expose_event(self, event): # Create the cairo context cr = self._canvas.window.cairo_create() # Restrict Cairo to the exposed area; avoid extra work cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list if cr is not None: self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): Gtk.main_quit() def export(self): ''' Write dot to cairo surface. ''' if self._mode == 'array': w = h = int(4 * self._space + 3 * self._dot_size) png_surface = cairo.ImageSurface(cairo.FORMAT_RGB24, w, h) cr = cairo.Context(png_surface) cr.set_source_rgb(192, 192, 192) cr.rectangle(0, 0, w, h) cr.fill() for i in range(9): y = self._space + int(i / 3.) * (self._dot_size + self._space) x = self._space + (i % 3) * (self._dot_size + self._space) cr.save() cr.set_source_surface(self._dots[i].images[0], x, y) cr.rectangle(x, y, self._dot_size, self._dot_size) cr.fill() cr.restore() else: w = h = int(2 * self._space + 3 * self._dot_size) png_surface = cairo.ImageSurface(cairo.FORMAT_RGB24, w, h) cr = cairo.Context(png_surface) cr.set_source_rgb(192, 192, 192) cr.rectangle(0, 0, w, h) cr.fill() y = self._space x = self._space cr.save() cr.set_source_surface(self._Dots[self.current_image].images[0], x, y) cr.rectangle(x, y, 3 * self._dot_size, 3 * self._dot_size) cr.fill() cr.restore() return png_surface def _new_dot_surface(self, color='#000000', image=None, large=False): ''' generate a dot of a color color ''' if large: size = self._dot_size * 3 else: size = self._dot_size self._svg_width = size self._svg_height = size if image is None: # color dot self._stroke = color self._fill = color pixbuf = svg_str_to_pixbuf( self._header() + self._circle(size / 2., size / 2., size / 2.) + self._footer()) else: if USE_ART4APPS: word = self._art4apps.get_words()[image] try: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( self._art4apps.get_image_filename(word), size, size) except Exception, e: _logger.error('new dot surface %s %s: %s' % (image, word, e)) word = 'zebra' # default in case image is not found pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( self._art4apps.get_image_filename(word), size, size) else:
class Spirolaterals: def __init__(self, canvas, colors, parent, score=0, delay=500, pattern=1, last=None): self._canvas = canvas self._colors = colors self._parent = parent self.delay = delay self.score = score self.pattern = pattern self.last_pattern = last self._running = False self._turtle_canvas = None self._user_numbers = [1, 1, 1, 3, 2] self._active_index = 0 self._sprites = Sprites(self._canvas) self._sprites.set_delay(True) size = max(Gdk.Screen.width(), Gdk.Screen.height()) cr = self._canvas.get_property('window').cairo_create() self._turtle_canvas = cr.get_target().create_similar( cairo.CONTENT_COLOR, size, size) self._canvas.connect('draw', self.__draw_cb) self._cr = cairo.Context(self._turtle_canvas) self._cr.set_line_cap(1) # Set the line cap to be round self._sprites.set_cairo_context(self._cr) self._canvas.set_can_focus(True) self._canvas.grab_focus() self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.connect('button-press-event', self._button_press_cb) self._canvas.connect('key_press_event', self._keypress_cb) self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - style.GRID_CELL_SIZE if self._width < self._height: self.i = 1 else: self.i = 0 self._calculate_scale_and_offset() self._numbers = [] self._glownumbers = [] self._create_number_sprites() self._create_turtle_sprites() self._create_results_sprites() self._set_color(colors[0]) self._set_pen_size(4) self.reset_level() def _calculate_scale_and_offset(self): self.offset = 0 if self.i == 0: self.scale = self._height / (900. - style.GRID_CELL_SIZE) * 1.25 self.offset = ( self._width - (self.sx(X1[self.i] + X2[self.i]) + self.ss(BS[self.i]))) / 2. else: self.scale = self._width / 900. self.offset = (self._width - (self.sx(X1[self.i]) + self.ss(BS[self.i]))) / 2. def reset_level(self): self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - style.GRID_CELL_SIZE if self._width < self._height: self.i = 1 else: self.i = 0 self._calculate_scale_and_offset() self._show_background_graphics() self._show_user_numbers() self._get_goal() self._draw_goal() self._reset_sprites() if self.score > 0: self._parent.update_score(int(self.score)) def _reset_sprites(self): x = self.sx(TX[self.i] - TS[self.i] / 2) y = self.sy(TY[self.i]) self._target_turtle.move((x, y)) x = self.sx(UX[self.i] - US[self.i] / 2) y = self.sy(UY[self.i]) self._user_turtles[0].move((x, y)) for i in range(5): for j in range(5): if self.i == 0: x = self.sx( NX[self.i]) + i * (self.ss(NS[self.i] + NO[self.i])) y = self.sy(NY[self.i]) else: x = self.sx(NX[self.i]) y = self.sy( NY[self.i]) + i * (self.ss(NS[self.i] + NO[self.i])) self._numbers[i][j].move((x, y)) self._glownumbers[i][j].move((x, y)) x = 0 y = self.sy(GY[self.i]) self._success.move((x, y)) self._success.hide() self._failure.move((x, y)) self._failure.hide() self._splot.hide() if self.last_pattern == self.pattern: self._parent.cyan.set_sensitive(True) def _keypress_cb(self, area, event): ''' Keypress: moving the slides with the arrow keys ''' k = Gdk.keyval_name(event.keyval) if k in ['1', '2', '3', '4', '5']: self.do_stop() i = self._active_index j = int(k) - 1 self._numbers[i][self._user_numbers[i] - 1].set_layer(HIDDEN_LAYER) self._numbers[i][j].set_layer(NUMBER_LAYER) self._user_numbers[i] = j + 1 self.inval(self._numbers[i][j].rect) elif k in ['KP_Up', 'j', 'Up']: self.do_stop() i = self._active_index j = self._user_numbers[i] if j < 5: j += 1 self._numbers[i][self._user_numbers[i] - 1].set_layer(HIDDEN_LAYER) self._numbers[i][j - 1].set_layer(NUMBER_LAYER) self._user_numbers[i] = j self.inval(self._numbers[i][j].rect) elif k in ['KP_Down', 'k', 'Down']: self.do_stop() i = self._active_index j = self._user_numbers[i] if j > 0: j -= 1 self._numbers[i][self._user_numbers[i] - 1].set_layer(HIDDEN_LAYER) self._numbers[i][j - 1].set_layer(NUMBER_LAYER) self._user_numbers[i] = j self.inval(self._numbers[i][j].rect) elif k in ['KP_Left', 'h', 'Left']: self.do_stop() self._active_index -= 1 self._active_index %= 5 elif k in ['KP_Right', 'l', 'Right']: self.do_stop() self._active_index += 1 self._active_index %= 5 elif k in ['Return', 'KP_Page_Up', 'KP_End']: self.do_run() elif k in ['space', 'Esc', 'KP_Page_Down', 'KP_Home']: self.do_stop() else: logging.debug(k) self._canvas.grab_focus() def _button_press_cb(self, win, event): ''' Callback to handle the button presses ''' win.grab_focus() x, y = map(int, event.get_coords()) self.press = self._sprites.find_sprite((x, y)) if self.press is not None and self.press.type == 'number': self.do_stop() i = int(self.press.name.split(',')[0]) self._active_index = i j = int(self.press.name.split(',')[1]) j1 = (j + 1) % 5 self._numbers[i][j1].set_layer(NUMBER_LAYER) self._numbers[i][j].set_layer(HIDDEN_LAYER) self._user_numbers[i] = j1 + 1 self.inval(self._numbers[i][j].rect) def _create_results_sprites(self): x = 0 y = self.sy(GY[self.i]) self._success = Sprite(self._sprites, x, y, self._parent.good_job_pixbuf()) self._success.hide() self._failure = Sprite(self._sprites, x, y, self._parent.try_again_pixbuf()) self._failure.hide() def _create_turtle_sprites(self): x = self.sx(TX[self.i] - TS[self.i] / 2) y = self.sy(TY[self.i]) pixbuf = self._parent.turtle_pixbuf() self._target_turtle = Sprite(self._sprites, x, y, pixbuf) self._user_turtles = [] x = self.sx(UX[self.i] - US[self.i] / 2) y = self.sy(UY[self.i]) self._user_turtles.append(Sprite(self._sprites, x, y, pixbuf)) pixbuf = pixbuf.rotate_simple(270) self._user_turtles.append(Sprite(self._sprites, x, y, pixbuf)) pixbuf = pixbuf.rotate_simple(270) self._user_turtles.append(Sprite(self._sprites, x, y, pixbuf)) pixbuf = pixbuf.rotate_simple(270) self._user_turtles.append(Sprite(self._sprites, x, y, pixbuf)) self._show_turtle(0) self._splot = Sprite(self._sprites, 0, 0, self._parent.splot_pixbuf()) self._splot.hide() def _show_splot(self, x, y, dd, h): for i in range(4): self._user_turtles[i].hide() if h == 0: self._splot.move((x - int(dd / 2), y)) elif h == 1: self._splot.move((x - dd, y - int(dd / 2))) elif h == 2: self._splot.move((x - int(dd / 2), y - dd)) elif h == 3: self._splot.move((x, y - int(dd / 2))) self._splot.set_layer(SUCCESS_LAYER) self._failure.set_layer(SUCCESS_LAYER) def _show_turtle(self, t): for i in range(4): if i == t: self._user_turtles[i].set_layer(TURTLE_LAYER) else: self._user_turtles[i].hide() def _reset_user_turtle(self): x = self.sx(UX[self.i] - US[self.i] / 2) y = self.sy(UY[self.i]) self._user_turtles[0].move((x, y)) self._show_turtle(0) def _create_number_sprites(self): for i in range(5): self._numbers.append([]) self._glownumbers.append([]) for j in range(5): if self.i == 0: x = self.sx( NX[self.i]) + i * (self.ss(NS[self.i] + NO[self.i])) y = self.sy(NY[self.i]) else: x = self.sx(NX[self.i]) y = self.sy( NY[self.i]) + i * (self.ss(NS[self.i] + NO[self.i])) number = Sprite( self._sprites, x, y, self._parent.number_pixbuf(self.ss(NS[self.i]), j + 1, self._parent.sugarcolors[1])) number.type = 'number' number.name = '%d,%d' % (i, j) self._numbers[i].append(number) number = Sprite( self._sprites, x, y, self._parent.number_pixbuf(self.ss(NS[self.i]), j + 1, '#FFFFFF')) number.type = 'number' number.name = '%d,%d' % (i, j) self._glownumbers[i].append(number) def _show_user_numbers(self): # Hide the numbers for i in range(5): for j in range(5): self._numbers[i][j].set_layer(HIDDEN_LAYER) self._glownumbers[i][j].set_layer(HIDDEN_LAYER) # Show user numbers self._numbers[0][self._user_numbers[0] - 1].set_layer(NUMBER_LAYER) self._numbers[1][self._user_numbers[1] - 1].set_layer(NUMBER_LAYER) self._numbers[2][self._user_numbers[2] - 1].set_layer(NUMBER_LAYER) self._numbers[3][self._user_numbers[3] - 1].set_layer(NUMBER_LAYER) self._numbers[4][self._user_numbers[4] - 1].set_layer(NUMBER_LAYER) def _show_background_graphics(self): self._draw_pixbuf(self._parent.background_pixbuf(), 0, 0, self._width, self._height) self._draw_pixbuf(self._parent.box_pixbuf(self.ss(BS[self.i])), self.sx(X1[self.i]), self.sy(Y1[self.i]), self.ss(BS[self.i]), self.ss(BS[self.i])) self._draw_pixbuf(self._parent.box_pixbuf(self.ss(BS[self.i])), self.sx(X2[self.i]), self.sy(Y2[self.i]), self.ss(BS[self.i]), self.ss(BS[self.i])) self._draw_text(self.pattern, self.sx(X1[self.i]), self.sy(Y1[self.i]), self.ss(LS[self.i])) def _set_pen_size(self, ps): self._cr.set_line_width(ps) def _set_color(self, color): r = color[0] / 255. g = color[1] / 255. b = color[2] / 255. self._cr.set_source_rgb(r, g, b) def _draw_line(self, x1, y1, x2, y2): self._cr.move_to(x1, y1) self._cr.line_to(x2, y2) self._cr.stroke() def ss(self, f): # scale size function return int(f * self.scale) def sx(self, f): # scale x function return int(f * self.scale + self.offset) def sy(self, f): # scale y function return int(f * self.scale) def _draw_pixbuf(self, pixbuf, x, y, w, h): self._cr.save() self._cr.translate(x + w / 2., y + h / 2.) self._cr.translate(-x - w / 2., -y - h / 2.) Gdk.cairo_set_source_pixbuf(self._cr, pixbuf, x, y) self._cr.rectangle(x, y, w, h) self._cr.fill() self._cr.restore() def _draw_text(self, label, x, y, size): pl = PangoCairo.create_layout(self._cr) fd = Pango.FontDescription('Sans') fd.set_size(int(size) * Pango.SCALE) pl.set_font_description(fd) if type(label) == str or type(label) == unicode: pl.set_text(label.replace('\0', ' '), -1) elif type(label) == float or type(label) == int: pl.set_text(str(label), -1) else: pl.set_text(str(label), -1) self._cr.save() self._cr.translate(x, y) self._cr.set_source_rgb(1, 1, 1) PangoCairo.update_layout(self._cr, pl) PangoCairo.show_layout(self._cr, pl) self._cr.restore() def inval(self, r): self._canvas.queue_draw_area(r[0], r[1], r[2], r[3]) def inval_all(self): self._canvas.queue_draw_area(0, 0, self._width, self._height) def __draw_cb(self, canvas, cr): cr.set_source_surface(self._turtle_canvas) cr.paint() self._sprites.redraw_sprites(cr=cr) def do_stop(self): self._parent.green.set_sensitive(True) self._running = False def do_run(self): self._show_background_graphics() # TODO: Add turtle graphics self._success.hide() self._failure.hide() self._splot.hide() self._get_goal() self._draw_goal() self.inval_all() self._running = True self.loop = 0 self._active_index = 0 self.step = 0 self._set_pen_size(4) self._set_color(self._colors[0]) x1 = self.sx(UX[self.i]) y1 = self.sy(UY[self.i]) dd = self.ss(US[self.i]) self._numbers[0][self._user_numbers[0] - 1].set_layer(HIDDEN_LAYER) self._glownumbers[0][self._user_numbers[0] - 1].set_layer(NUMBER_LAYER) self._user_turtles[0].move((int(x1 - dd / 2), y1)) self._show_turtle(0) if self._running: GObject.timeout_add(self.delay, self._do_step, x1, y1, dd, 0) def _do_step(self, x1, y1, dd, h): if not self._running: return if self.loop > 3: return if h == 0: # up x2 = x1 y2 = y1 - dd self._user_turtles[h].move((int(x2 - dd / 2), int(y2 - dd))) elif h == 1: # right x2 = x1 + dd y2 = y1 self._user_turtles[h].move((int(x2), int(y2 - dd / 2))) elif h == 2: # down x2 = x1 y2 = y1 + dd self._user_turtles[h].move((int(x2 - dd / 2), int(y2))) elif h == 3: # left x2 = x1 - dd y2 = y1 self._user_turtles[h].move((int(x2 - dd), int(y2 - dd / 2))) self._show_turtle(h) if x2 < self.sx(X2[self.i]) or \ x2 > self.sx(X2[self.i] + BS[self.i]) or \ y2 < self.sy(Y2[self.i]) or \ y2 > self.sy(Y2[self.i] + BS[self.i]): self.do_stop() self._show_splot(x2, y2, dd, h) self._draw_line(x1, y1, x2, y2) self.inval_all() self.step += 1 i = self._active_index if self.step == self._user_numbers[i]: number = self._user_numbers[i] - 1 self._numbers[i][number].set_layer(NUMBER_LAYER) self._glownumbers[i][number].set_layer(HIDDEN_LAYER) h += 1 h %= 4 self.step = 0 self._active_index += 1 if self._active_index == 5: self.loop += 1 self._active_index = 0 else: i = self._active_index number = self._user_numbers[i] - 1 self._numbers[i][number].set_layer(HIDDEN_LAYER) self._glownumbers[i][number].set_layer(NUMBER_LAYER) if self.loop < 4 and self._running: GObject.timeout_add(self.delay, self._do_step, x2, y2, dd, h) elif self.loop == 4: # Test to see if we win self._running = False self._parent.green.set_sensitive(True) self._reset_user_turtle() self._show_user_numbers() self._test_level() def _test_level(self): success = True for i in range(5): if self._user_numbers[i] != self._goal[i]: success = False break if success: self._do_success() else: self._do_fail() def _do_success(self): self._success.set_layer(SUCCESS_LAYER) self._parent.cyan.set_sensitive(True) if self.last_pattern != self.pattern: self.score += 6 self.last_pattern = self.pattern self._parent.update_score(int(self.score)) def _do_fail(self): self._failure.set_layer(SUCCESS_LAYER) self._parent.cyan.set_sensitive(False) def do_slider(self, value): self.delay = int(value) def do_button(self, bu): self._success.hide() self._failure.hide() if bu == 'cyan': # Next level self.do_stop() self._splot.hide() self.pattern += 1 if self.pattern == 123: self.pattern = 1 self._get_goal() self._show_background_graphics() self._draw_goal() self._reset_user_turtle() self.inval_all() self._parent.cyan.set_sensitive(False) elif bu == 'green': # Run level self._parent.green.set_sensitive(False) self.do_run() elif bu == 'red': # Stop level self.do_stop() def _draw_goal(self): # draws the left hand pattern x1 = self.sx(TX[self.i]) y1 = self.sy(TY[self.i]) dd = self.ss(TS[self.i]) dx = 0 dy = -dd for i in range(4): for j in self._goal: for k in range(j): x2 = x1 + dx y2 = y1 + dy self._set_pen_size(4) self._set_color(self._colors[0]) self._draw_line(x1, y1, x2, y2) x1 = x2 y1 = y2 if dy == -dd: dx = dd dy = 0 elif dx == dd: dx = 0 dy = dd elif dy == dd: dx = -dd dy = 0 else: dx = 0 dy = -dd def _get_goal(self): fname = os.path.join('data', 'patterns.dat') try: f = open(fname, 'r') for n in range(0, self.pattern): s = f.readline() s = s[0:5] except: s = 11132 self.pattern = 1 f.close l = [int(c) for c in str(s)] self._goal = l
class Game(): def __init__(self, canvas, parent=None, path=None, colors=['#A0FFA0', '#FF8080']): self._canvas = canvas self._parent = parent self._parent.show_all() self._path = path self._colors = ['#FFFFFF'] self._colors.append(colors[0]) self._colors.append(colors[1]) self._canvas.set_can_focus(True) self._canvas.connect("draw", self._draw_cb) self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.connect("button-press-event", self._button_press_cb) self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - (GRID_CELL_SIZE * 1.5) self._scale = self._height / (4 * DOT_SIZE * 1.3) self._dot_size = int(DOT_SIZE * self._scale) self._space = int(self._dot_size / 5.) self.we_are_sharing = False self._start_time = 0 self._timeout_id = None self._level = 3 self._game = 0 self._correct = 0 # Find the image files self._PATHS = glob(os.path.join(self._path, 'images'), '.png') self._CPATHS = glob(os.path.join(self._path, 'color-images'), '*.svg') # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) self._dots = [] self._opts = [] yoffset = int(self._space / 2.) self._line = Sprite( self._sprites, 0, int(3 * (self._dot_size + self._space) + yoffset / 2.), self._line(vertical=False)) for y in range(3): for x in range(6): xoffset = int((self._width - 6 * self._dot_size - \ 5 * self._space) / 2.) self._dots.append( Sprite(self._sprites, xoffset + x * (self._dot_size + self._space), y * (self._dot_size + self._space) + yoffset, self._new_dot_surface(color=self._colors[0]))) self._dots[-1].type = -1 # No image self._dots[-1].set_label_attributes(72) y = 3 for x in range(3): self._opts.append( Sprite(self._sprites, xoffset + x * (self._dot_size + self._space), y * (self._dot_size + self._space) + yoffset, self._new_dot_surface(color=self._colors[0]))) self._opts[-1].type = -1 # No image self._opts[-1].set_label_attributes(72) self._opts[-1].hide() def _all_clear(self): ''' Things to reinitialize when starting up a new game. ''' if self._timeout_id is not None: GObject.source_remove(self._timeout_id) # Auto advance levels if self._correct > 3 and self._level < len(self._dots): self._level += 3 self._correct = 0 self._set_label('') for i in range(3): self._opts[i].hide() self._opts[i].type = -1 self._opts[i].set_label('') for dot in self._dots: dot.type = -1 if self._game == 2 or self._dots.index(dot) < self._level: dot.set_shape( self._new_dot_surface(self._colors[abs(dot.type)])) dot.set_label('?') dot.set_layer(100) else: dot.hide() self._dance_counter = 0 self._dance_step() def _dance_step(self): ''' Short animation before loading new game ''' if self._game == 2: for i in range(len(self._dots)): self._dots[i].set_shape( self._new_dot_surface(self._colors[int(uniform(0, 3))])) else: for i in range(self._level): self._dots[i].set_shape( self._new_dot_surface(self._colors[int(uniform(0, 3))])) self._dance_counter += 1 if self._dance_counter < 10: self._timeout_id = GObject.timeout_add(500, self._dance_step) else: self._new_game() def new_game(self, game=None, restart=True): ''' Start a new game. ''' if game is not None: self._game = game self._level = 3 self._correct = 0 if restart: self._all_clear() def _image_in_dots(self, n): for i in range(self._level): if self._dots[i].type == n: return True return False def _image_in_opts(self, n): for i in range(3): if self._opts[i].type == n: return True return False def _choose_random_images(self): ''' Choose images at random ''' if self._game == 3: maxi = len(self._CPATHS) else: maxi = len(self._PATHS) for i in range(self._level): if self._dots[i].type == -1: n = int(uniform(0, maxi)) while self._image_in_dots(n): n = int(uniform(0, maxi)) self._dots[i].type = n if self._game == 3: self._dots[i].set_shape( self._new_dot_surface(color_image=self._dots[i].type)) else: self._dots[i].set_shape( self._new_dot_surface(image=self._dots[i].type)) self._dots[i].set_layer(100) self._dots[i].set_label('') def _load_image_from_list(self): if self._recall_counter == len(self._recall_list): self._timeout_id = GObject.timeout_add(1000, self._ask_the_question) return for dot in self._dots: dot.type = self._recall_list[self._recall_counter] dot.set_shape(self._new_dot_surface(image=dot.type)) dot.set_layer(100) dot.set_label('') self._recall_counter += 1 self._timeout_id = GObject.timeout_add(1000, self._load_image_from_list) def _find_repeat(self): ''' Find an image that repeats ''' for i in range(self._level): for j in range(self._level - i - 1): if self._dots[i].type == self._dots[j].type: return i return None def _new_game(self, restore=False): ''' Load game images and then ask a question... ''' if self._game in [0, 1, 3]: self._choose_random_images() else: # game 2 # generate a random list self._recall_list = [] for i in range(12): n = int(uniform(0, len(self._PATHS))) while n in self._recall_list: n = int(uniform(0, len(self._PATHS))) self._recall_list.append(n) self._recall_counter = 0 self._load_image_from_list() if self._game == 0: if not restore: # Repeat at least one of the images self._repeat = int(uniform(0, self._level)) n = (self._repeat + int(uniform(1, self._level))) % self._level _logger.debug('repeat=%d, n=%d' % (self._repeat, n)) self._dots[self._repeat].set_shape( self._new_dot_surface(image=self._dots[n].type)) self._dots[self._repeat].type = self._dots[n].type else: # Find repeated image, as that is the answer self._repeat = self._find_repeat() if self._repeat is None: _logger.debug('could not find repeat') self._repeat = 0 if self.we_are_sharing: _logger.debug('sending a new game') self._parent.send_new_game() if self._game in [0, 1, 3]: self._timeout_id = GObject.timeout_add(3000, self._ask_the_question) def _ask_the_question(self): ''' Each game has a challenge ''' self._timeout_id = None # Hide the dots if self._game == 2: for dot in self._dots: dot.hide() else: for i in range(self._level): self._dots[i].hide() if self._game == 0: self._set_label(_('Recall which image was repeated.')) # Show the possible solutions for i in range(3): n = int(uniform(0, len(self._PATHS))) if self._level == 3: while(n == self._dots[self._repeat].type or \ self._image_in_opts(n)): n = int(uniform(0, len(self._PATHS))) else: while(n == self._dots[self._repeat].type or \ not self._image_in_dots(n) or \ self._image_in_opts(n)): n = int(uniform(0, len(self._PATHS))) self._opts[i].type = n self._answer = int(uniform(0, 3)) self._opts[self._answer].type = self._dots[self._repeat].type for i in range(3): self._opts[i].set_shape( self._new_dot_surface(image=self._opts[i].type)) self._opts[i].set_layer(100) elif self._game == 1: self._set_label(_('Recall which image was not shown.')) # Show the possible solutions for i in range(3): n = int(uniform(0, len(self._PATHS))) while(not self._image_in_dots(n) or \ self._image_in_opts(n)): n = int(uniform(0, len(self._PATHS))) self._opts[i].type = n self._answer = int(uniform(0, 3)) n = int(uniform(0, len(self._PATHS))) while (self._image_in_dots(n)): n = int(uniform(0, len(self._PATHS))) self._opts[self._answer].type = n for i in range(3): self._opts[i].set_shape( self._new_dot_surface(image=self._opts[i].type)) self._opts[i].set_layer(100) elif self._game == 3: self._set_label(_('Recall which image was not shown.')) # Show the possible solutions for i in range(3): n = int(uniform(0, len(self._CPATHS))) while(not self._image_in_dots(n) or \ self._image_in_opts(n)): n = int(uniform(0, len(self._CPATHS))) self._opts[i].type = n self._answer = int(uniform(0, 3)) n = int(uniform(0, len(self._CPATHS))) while (self._image_in_dots(n)): n = int(uniform(0, len(self._CPATHS))) self._opts[self._answer].type = n for i in range(3): self._opts[i].set_shape( self._new_dot_surface(color_image=self._opts[i].type)) self._opts[i].set_layer(100) elif self._game == 2: self._set_label(ngettext( 'Recall which image was displayed %d time ago', 'Recall which image was displayed %d times ago', (int(self._level / 3))) % \ (int(self._level / 3))) # Show the possible solutions for i in range(3): self._answer = len(self._recall_list) - int( self._level / 3) - 1 n = int(uniform(0, len(self._recall_list))) while n == self._answer: n = int(uniform(0, len(self._recall_list))) self._opts[i].type = n i = int(uniform(0, 3)) self._opts[i].type = self._recall_list[self._answer] for i in range(3): self._opts[i].set_shape( self._new_dot_surface(image=self._opts[i].type)) self._opts[i].set_layer(100) def restore_game(self, dot_list, correct=0, level=3, game=0): ''' Restore a game from the Journal or share ''' # TODO: Save/restore recall list for game 2 self._correct = correct self._level = level self._game = game for i, dot in enumerate(dot_list): self._dots[i].type = dot if dot == -1: self._dots[i].hide() self._new_game(restore=True) def save_game(self): ''' Return dot list for saving to Journal or sharing ''' dot_list = [] for dot in self._dots: dot_list.append(dot.type) return dot_list, self._correct, self._level, self._game def _set_label(self, string): ''' Set the label in the toolbar or the window frame. ''' self._parent.status.set_label(string) def _button_press_cb(self, win, event): if self._timeout_id is not None: _logger.debug('still in timeout... ignoring click') return win.grab_focus() x, y = map(int, event.get_coords()) spr = self._sprites.find_sprite((x, y), inverse=True) if spr == None: return if self._game in [0, 1, 3]: for i in range(3): if self._opts[i] == spr: break self._opts[i].set_shape( self._new_dot_surface(color=self._colors[0])) if i == self._answer: self._opts[i].set_label('☻') self._correct += 1 else: self._opts[i].set_label('☹') self._correct = 0 else: for i in range(3): if self._opts[i] == spr: break self._opts[i].set_shape( self._new_dot_surface(color=self._colors[0])) if self._opts[i].type == self._recall_list[self._answer]: self._opts[i].set_label('☻') self._correct += 1 else: self._opts[i].set_label('☹') self._correct = 0 if self._game in [0, 1, 3]: for i in range(self._level): self._dots[i].set_layer(100) else: for dot in self._dots: dot.set_shape( self._new_dot_surface( image=self._recall_list[self._answer])) dot.set_layer(100) if self._correct == 0: self._timeout_id = GObject.timeout_add(5000, self.new_game) else: self._timeout_id = GObject.timeout_add(3000, self.new_game) return True def remote_button_press(self, dot, color): ''' Receive a button press from a sharer ''' self._dots[dot].type = color self._dots[dot].set_shape( self._new_dot_surface(color=self._colors[color])) def set_sharing(self, share=True): _logger.debug('enabling sharing') self.we_are_sharing = share def _draw_cb(self, win, context): self.do_draw(win, context) def do_draw(self, win, cr): ''' Handle the draw-event by drawing ''' # Restrict Cairo to the exposed area alloc = win.get_allocation() cr.rectangle(0, 0, alloc.width, alloc.height) cr.set_source_rgb(1, 1, 1) cr.fill() cr.rectangle(0, 0, alloc.width, alloc.height) cr.clip() # Refresh sprite list self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): Gtk.main_quit() def _new_dot_surface(self, color='#000000', image=None, color_image=None): ''' generate a dot of a color color ''' self._dot_cache = {} if color_image is not None: if color_image + 10000 in self._dot_cache: return self._dot_cache[color_image + 10000] pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._path, self._CPATHS[color_image]), self._svg_width, self._svg_height) elif image is not None: if image in self._dot_cache: return self._dot_cache[image] pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._path, self._PATHS[image]), self._svg_width, self._svg_height) else: if color in self._dot_cache: return self._dot_cache[color] self._stroke = color self._fill = color self._svg_width = self._dot_size self._svg_height = self._dot_size i = self._colors.index(color) pixbuf = svg_str_to_pixbuf( self._header() + \ self._circle(self._dot_size / 2., self._dot_size / 2., self._dot_size / 2.) + \ self._footer()) surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self._svg_width, self._svg_height) context = cairo.Context(surface) Gdk.cairo_set_source_pixbuf(context, pixbuf, 0, 0) context.rectangle(0, 0, self._svg_width, self._svg_height) context.fill() if color_image is not None: self._dot_cache[color_image + 10000] = surface elif image is not None: self._dot_cache[image] = surface else: self._dot_cache[color] = surface return surface def _line(self, vertical=True): ''' Generate a center line ''' if vertical: self._svg_width = 3 self._svg_height = self._height return svg_str_to_pixbuf( self._header() + \ self._rect(3, self._height, 0, 0) + \ self._footer()) else: self._svg_width = self._width self._svg_height = 3 return svg_str_to_pixbuf( self._header() + \ self._rect(self._width, 3, 0, 0) + \ self._footer()) def _header(self): return '<svg\n' + 'xmlns:svg="http://www.w3.org/2000/svg"\n' + \ 'xmlns="http://www.w3.org/2000/svg"\n' + \ 'xmlns:xlink="http://www.w3.org/1999/xlink"\n' + \ 'version="1.1"\n' + 'width="' + str(self._svg_width) + '"\n' + \ 'height="' + str(self._svg_height) + '">\n' def _rect(self, w, h, x, y): svg_string = ' <rect\n' svg_string += ' width="%f"\n' % (w) svg_string += ' height="%f"\n' % (h) svg_string += ' rx="%f"\n' % (0) svg_string += ' ry="%f"\n' % (0) svg_string += ' x="%f"\n' % (x) svg_string += ' y="%f"\n' % (y) svg_string += 'style="fill:#000000;stroke:#000000;"/>\n' return svg_string def _circle(self, r, cx, cy): return '<circle style="fill:' + str(self._fill) + ';stroke:' + \ str(self._stroke) + ';" r="' + str(r - 0.5) + '" cx="' + \ str(cx) + '" cy="' + str(cy) + '" />\n' def _footer(self): return '</svg>\n'
class SlideRule(): def __init__(self, canvas, path, parent=None, sugar=True): """ Handle launch from both within and without of Sugar environment. """ self.SLIDES = { 'C': [C_slide_generator], 'CI': [CI_slide_generator], 'A': [A_slide_generator], 'K': [K_slide_generator], 'S': [S_slide_generator], 'T': [T_slide_generator], 'L': [L_slide_generator], 'LLn': [LLn_slide_generator], 'Log': [Log_slide_generator], 'custom': [Custom_slide_generator] } self.STATORS = { 'D': [D_stator_generator], 'DI': [DI_stator_generator], 'B': [B_stator_generator], 'K2': [K_stator_generator], 'S2': [S_stator_generator], 'T2': [T_stator_generator], 'L2': [L_stator_generator], 'LLn2': [LLn_stator_generator], 'Log2': [Log_stator_generator], 'custom2': [Custom_stator_generator] } self.path = path self.sugar = sugar self.canvas = canvas self.parent = parent parent.show_all() self.canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self.canvas.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK) self.canvas.add_events(Gdk.EventMask.POINTER_MOTION_MASK) self.canvas.add_events(Gdk.EventMask.KEY_PRESS_MASK) self.canvas.connect("draw", self.__draw_cb) self.canvas.connect("button-press-event", self._button_press_cb) self.canvas.connect("button-release-event", self._button_release_cb) self.canvas.connect("motion-notify-event", self._mouse_move_cb) self.canvas.connect("key-press-event", self._keypress_cb) self.canvas.set_can_focus(True) self.canvas.grab_focus() self.width = Gdk.Screen.width() self.height = Gdk.Screen.height() - GRID_CELL_SIZE self.sprites = Sprites(self.canvas) self.slides = [] self.stators = [] self.scale = 1 locale.setlocale(locale.LC_NUMERIC, '') self.decimal_point = locale.localeconv()['decimal_point'] if self.decimal_point == '' or self.decimal_point is None: self.decimal_point = '.' self.error_msg = None self.result_function = [None, None] self.label_function = [None, None] _logger.debug("creating slides, stators, and reticule") self.result_label = Stator(self.sprites, self.path, 'label', int((self.width - 600) / 2), SCREENOFFSET + 4 * SHEIGHT, 800, SHEIGHT) for slide in self.SLIDES: self.make_slide(slide, SLIDE) for stator in self.STATORS: self.make_slide(stator, STATOR) self.reticule = Reticule(self.sprites, self.path, 'reticule', 150, SCREENOFFSET + SHEIGHT, 100, 2 * SHEIGHT) self.reticule.draw(2000) self.press = None self.last = None self.dragpos = 0 # We need textviews for keyboard input from the on-screen keyboard self._set_screen_dpi() font_desc = Pango.font_description_from_string('12') self.text_entries = [] self.text_buffers = [] w = self.reticule.tabs[0].spr.label_safe_width() h = int(self.reticule.tabs[0].spr.label_safe_height() / 2) for i in range(4): # Reticule top & bottom; Slider left & right self.text_entries.append(Gtk.TextView()) self.text_entries[-1].set_justification(Gtk.Justification.CENTER) self.text_entries[-1].set_pixels_above_lines(4) ''' Not necessary (and doesn't work on OS8) self.text_entries[-1].override_background_color( Gtk.StateType.NORMAL, Gdk.RGBA(0, 0, 0, 0)) ''' self.text_entries[-1].modify_font(font_desc) self.text_buffers.append(self.text_entries[-1].get_buffer()) self.text_entries[-1].set_size_request(w, h) self.text_entries[-1].show() self.parent.fixed.put(self.text_entries[-1], 0, 0) self.parent.fixed.show() self.text_entries[-1].connect('focus-out-event', self._text_focus_out_cb) self.reticule.add_textview(self.text_entries[0], i=BOTTOM) self.reticule.add_textview(self.text_entries[1], i=TOP) self.reticule.set_fixed(self.parent.fixed) for slide in self.slides: slide.add_textview(self.text_entries[2], i=LEFT) slide.add_textview(self.text_entries[3], i=RIGHT) slide.set_fixed(self.parent.fixed) if not self.sugar: self.update_textview_y_offset(self.parent.menu_height) self.active_slide = self.name_to_slide('C') self.active_stator = self.name_to_stator('D') self.update_slide_labels() self.update_result_label() def update_textview_y_offset(self, dy): ''' Need to account for menu height in GNOME ''' self.reticule.tabs[0].textview_y_offset += dy self.reticule.tabs[1].textview_y_offset += dy for slide in self.slides: slide.tabs[0].textview_y_offset += dy slide.tabs[1].textview_y_offset += dy def _text_focus_out_cb(self, widget=None, event=None): ''' One of the four textviews was in focus ''' i = None if widget in self.text_entries: i = self.text_entries.index(widget) bounds = self.text_buffers[i].get_bounds() text = self.text_buffers[i].get_text(bounds[0], bounds[1], True) text = text.strip() self._process_numeric_input(i, text) def _set_screen_dpi(self): dpi = _get_screen_dpi() font_map_default = PangoCairo.font_map_get_default() font_map_default.set_resolution(dpi) def __draw_cb(self, canvas, cr): self.sprites.redraw_sprites(cr=cr) # Handle the expose-event by drawing def do_expose_event(self, event): # Create the cairo context cr = self.canvas.window.cairo_create() print('set cr in do_expose') self.sprites.set_cairo_context(cr) # Restrict Cairo to the exposed area; avoid extra work cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list self.sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): Gtk.main_quit() def _keypress_cb(self, area, event): """ Keypress: moving the slides with the arrow keys """ k = Gdk.keyval_name(event.keyval) if not self.sugar: return if k == 'a': self.parent.show_a() elif k == 'k': self.parent.show_k() elif k in ['c', 'asterisk', 'x']: self.parent.show_c() elif k in ['i', '/']: self.parent.show_ci() elif k == 's': self.parent.show_s() elif k == 't': self.parent.show_t() elif k in ['l', 'plus']: self.parent.show_l() elif k in ['Left', 'less']: if self.last is not None: self._move_slides(self.last, -1) elif k in ['Right', 'greater']: if self.last is not None: self._move_slides(self.last, 1) elif k in ['Home', 'Pause', 'Up', '^']: self._move_slides( self.name_to_stator('D').spr, -self.name_to_stator('D').spr.get_xy()[0]) elif k == 'r': self.reticule.move(150, self.reticule.spr.get_xy()[1]) self.update_slide_labels() self.update_result_label() elif k in ['Down', 'v']: self.parent.realign_cb() self.reticule.move(150, self.reticule.spr.get_xy()[1]) self.update_slide_labels() self.update_result_label() return True def _process_numeric_input(self, i, text): try: n = float(text.replace(self.decimal_point, '.')) if i == 0: self._move_reticule_to_stator_value(n) elif i == 1: self._move_reticule_to_slide_value(n) elif i == 2: self._move_slide_to_stator_value(n) elif i == 3: self._move_slide_to_stator_value(self._left_from_right(n)) except ValueError: self.result_label.spr.labels[0] = _('NaN') + ' ' + text return def _process_text_field(self, text_field): """ Process input from numeric text fields: could be a function. """ try: my_min = "def f(): return " + text_field.replace('import', '') userdefined = {} exec(my_min, globals(), userdefined) return list(userdefined.values())[0]() except OverflowError as e: self.result_label.spr.labels[0] = _('Overflow Error') + \ ': ' + str(e) self.result_label.draw(1000) except NameError as e: self.result_label.spr.labels[0] = _('Name Error') + ': ' + str(e) self.result_label.draw(1000) except ZeroDivisionError as e: self.result_label.spr.labels[0] = _('Can not divide by zero') + \ ': ' + str(e) self.result_label.draw(1000) except TypeError as e: self.result_label.spr.labels[0] = _('Type Error') + ': ' + str(e) self.result_label.draw(1000) except ValueError as e: self.result_label.spr.labels[0] = _('Type Error') + ': ' + str(e) self.result_label.draw(1000) except SyntaxError as e: self.result_label.spr.labels[0] = _('Syntax Error') + ': ' + str(e) self.result_label.draw(1000) except: traceback.print_exc() return None def make_slide(self, name, slide, custom_strings=None): """ Create custom slide and stator from text entered on toolbar. """ if custom_strings is not None: result = self._process_text_field(custom_strings[FMIN]) else: result = self._process_text_field(DEFINITIONS[name][FMIN]) if result is None: return try: min_value = float(result) except ValueError as e: self.result_label.spr.labels[0] = _('Value Error') + ': ' + str(e) self.result_label.draw(1000) return if custom_strings is not None: result = self._process_text_field(custom_strings[FMAX]) else: result = self._process_text_field(DEFINITIONS[name][FMAX]) if result is None: return try: max_value = float(result) except ValueError: self.result_label.spr.labels[0] = _('Value Error') + ': ' + str(e) self.result_label.draw(1000) return if custom_strings is not None: result = self._process_text_field(custom_strings[FSTEP]) else: result = self._process_text_field(DEFINITIONS[name][FSTEP]) if result is None: return try: step_value = float(result) except ValueError: self.result_label.spr.labels[0] = _('Value Error') + ': ' + str(e) self.result_label.draw(1000) return if custom_strings is not None: offset_string = custom_strings[FOFFSET] else: offset_string = DEFINITIONS[name][FOFFSET] if custom_strings is not None: label_string = custom_strings[FDISPLAY] else: label_string = DEFINITIONS[name][FDISPLAY] if name == 'custom' or name == 'custom2': if custom_strings is not None: self.result_function[slide] = custom_strings[FRESULT] self.label_function[slide] = custom_strings[FDISPLAY] else: self.result_function[slide] = DEFINITIONS[name][FRESULT] self.label_function[slide] = DEFINITIONS[name][FDISPLAY] if slide == SLIDE: custom_slide = \ CustomSlide(self.sprites, self.path, name, 0, SCREENOFFSET + SHEIGHT, self.SLIDES[name][0], self._calc_slide_value, offset_string, label_string, min_value, max_value, step_value) if custom_slide.error_msg is not None: self.result_label.spr.set_label(custom_slide.error_msg) self.result_label.draw(1000) if self.name_to_slide(name) is not None and \ self.name_to_slide(name).name == name: i = self.slides.index(self.name_to_slide(name)) active = False if self.active_slide == self.slides[i]: active = True self.slides[i].hide() self.slides[i] = custom_slide if active: self.active_slide = self.slides[i] if self.sugar: self.parent.set_slide() else: self.slides.append(custom_slide) self.active_slide = self.name_to_slide(name) else: custom_stator = \ CustomStator(self.sprites, name, 0, SCREENOFFSET + 2* SHEIGHT, self.STATORS[name][0], self._calc_stator_value, self._calc_stator_result, offset_string, label_string, min_value, max_value, step_value) if self.name_to_stator(name) is not None and \ self.name_to_stator(name).name == name: i = self.stators.index(self.name_to_stator(name)) active = False if self.active_stator == self.stators[i]: active = True self.stators[i].hide() self.stators[i] = custom_stator if active: self.active_stator = self.stators[i] if self.sugar: self.parent.set_stator() else: self.stators.append(custom_stator) self.active_stator = self.name_to_stator(name) if self.sugar and name == 'custom' and hasattr(self.parent, 'sr'): self.parent.show_u(slide) if slide == SLIDE and custom_slide.error_msg is not None: self.result_label.spr.set_label(custom_slide.error_msg) self.result_label.draw(1000) if slide == STATOR and custom_stator.error_msg is not None: self.result_label.spr.set_label(custom_stator.error_msg) self.result_label.draw(1000) def name_to_slide(self, name): for slide in self.slides: if name == slide.name: return slide return None def name_to_stator(self, name): for stator in self.stators: if name == stator.name: return stator return None def sprite_in_stators(self, sprite): for stator in self.stators: if stator.match(sprite): return True return False def find_stator(self, sprite): for stator in self.stators: if stator.match(sprite): return stator return None def sprite_in_slides(self, sprite): for slide in self.slides: if slide.match(sprite): return True return False def find_slide(self, sprite): for slide in self.slides: if slide.match(sprite): return slide return None def _button_press_cb(self, win, event): win.grab_focus() x, y = list(map(int, event.get_coords())) self.dragpos = x spr = self.sprites.find_sprite((x, y)) self.press = spr return True def _mouse_move_cb(self, win, event): """ Drag a rule with the mouse. """ if self.press is None: self.dragpos = 0 return True win.grab_focus() x, y = list(map(int, event.get_coords())) dx = x - self.dragpos self._move_slides(self.press, dx) self.dragpos = x def _move_reticule_to_slide_value(self, value): rx = self.reticule.spr.get_xy()[0] - self.active_slide.spr.get_xy()[0] self.reticule.move_relative( self._calc_dx_from_value(value, self.active_slide.name, rx), 0) self.update_slide_labels() self.update_result_label() def _move_reticule_to_stator_value(self, value): rx = self.reticule.spr.get_xy()[0] - self.active_stator.spr.get_xy()[0] self.reticule.move_relative( self._calc_dx_from_value(value, self.active_stator.name, rx), 0) self.update_slide_labels() self.update_result_label() def _move_slide_to_stator_value(self, value): rx = self.active_slide.spr.get_xy()[0] - \ self.active_stator.spr.get_xy()[0] self.active_slide.move_relative( self._calc_dx_from_value(value, self.active_stator.name, rx), 0) self.update_slide_labels() self.update_result_label() def _calc_dx_from_value(self, value, name, rx): if name in ['C', 'D']: if value <= 0: return 0 return log(value, 10) * SCALE - rx elif name in ['CI', 'DI']: if value == 0: return 0 return log(10 / value, 10) * SCALE - rx elif name in ['A', 'B']: if value <= 0: return 0 return log(pow(value, 1 / 2.), 10) * SCALE - rx elif name in ['K', 'K2']: if value <= 0: return 0 return log(pow(value, 1 / 3.), 10) * SCALE - rx elif name in ['L', 'L2']: return (value / 10.) * SCALE - rx elif name in ['LLn', 'LLn2']: return log(exp(value), 10) * SCALE - rx elif name in ['Log', 'Log2']: return pow(10, log(value, 10)) * SCALE - rx else: return 0 def align_slides(self): """ Move slide to align with stator """ slidex = self.active_slide.spr.get_xy()[0] statorx = self.active_stator.spr.get_xy()[0] dx = statorx - slidex print('calling active slide', dx, 0) self.active_slide.move_relative(dx, 0) def _move_slides(self, sprite, dx): if self.sprite_in_stators(sprite): self.active_stator.move_relative(dx, 0) self.active_slide.move_relative(dx, 0) self.reticule.move_relative(dx, 0) elif self.reticule.match(sprite): self.reticule.move_relative(dx, 0) elif self.sprite_in_slides(sprite): self.find_slide(sprite).move_relative(dx, 0) self.update_slide_labels() self.update_result_label() def _left_from_right(self, v_right): if self.active_stator.name == 'L2': return v_right - 10 elif self.active_stator.name == 'D': return v_right / 10. elif self.active_stator.name == 'B': return v_right / 100. elif self.active_stator.name == 'K2': return v_right / 1000. elif self.active_stator.name == 'DI': return v_right * 10. elif self.active_stator.name == 'LLn2': return v_right - round(log(10), 2) else: return v_right def _right_from_left(self, v_left): if self.active_stator.name == 'L2': return 10 + v_left elif self.active_stator.name == 'D': return v_left * 10. elif self.active_stator.name == 'B': return v_left * 100. elif self.active_stator.name == 'K2': return v_left * 1000. elif self.active_stator.name == 'DI': return v_left / 10. elif self.active_stator.name == 'LLn2': return round(log(10), 2) + v_left else: return v_left def update_slide_labels(self): """ Based on the current alignment of the rules, calculate labels. """ v_left = self.active_stator.calculate() v_right = self._right_from_left(v_left) label_left = str(v_left).replace('.', self.decimal_point) label_right = str(v_right).replace('.', self.decimal_point) self.active_slide.label(label_left, i=LEFT) self.active_slide.label(label_right, i=RIGHT) self.reticule.label(str(self.active_stator.result()).replace( '.', self.decimal_point), i=BOTTOM) self.reticule.label(str(self.active_slide.calculate()).replace( '.', self.decimal_point), i=TOP) def _button_release_cb(self, win, event): if self.press == None: return True if self.press == self.active_slide.spr: self.last = self.active_slide.tabs[LEFT].spr elif self.press == self.active_stator.spr: self.last = None else: self.last = self.press self.press = None self.update_result_label() def update_result_label(self): """ Update toolbar label with result of calculation. """ s = '' if self.active_stator.name == 'D': dx = self.name_to_stator('D').spr.get_xy()[0] S = self.active_slide.calculate() R = self._calc_stator_result('D') if self.active_slide.name == 'A': if self.name_to_slide('A').spr.get_xy()[0] == dx: s = " √ %0.2f = %0.2f\t\t%0.2f² = %0.2f" % (S, R, R, S) elif self.sugar: self.parent.set_function_unknown() elif self.active_slide.name == 'K': if self.name_to_slide('K').spr.get_xy()[0] == dx: s = " ∛ %0.2f = %0.2f\t\t%0.2f³ = %0.2f" % (S, R, R, S) elif self.sugar: self.parent.set_function_unknown() elif self.active_slide.name == 'S': if self.name_to_slide('S').spr.get_xy()[0] == dx: s = " sin(%0.2f) = %0.2f\t\tasin(%0.2f) = %0.2f" % \ (S, R/10, R/10, S) elif self.sugar: self.parent.set_function_unknown() elif self.active_slide.name == 'T': if self.name_to_slide('T').spr.get_xy()[0] == dx: s = " tan(%0.2f) = %0.2f\t\tatan(%0.2f) = %0.2f" % \ (S, R/10, R/10, S) elif self.sugar: self.parent.set_function_unknown() elif self.active_slide.name == 'C': D = str(self._calc_stator_value('D')) s = "%s × %s = %s\t\t%s / %s = %s" % (D, S, R, R, S, D) elif self.active_slide.name == 'CI': D = str(self._calc_stator_value('D')) s = "%s / %s = %s\t\t%s × %s = %s" % (D, S, R / 10, R / 10, S, D) elif self.active_stator.name == 'L2': if self.active_slide.name == 'L': # use n dash to display a minus sign L2 = self._calc_stator_value('L2') if L2 < 0: L2str = "–" + str(-L2) else: L2str = str(L2) L = self._calc_slide_value('L') if L < 0: operator1 = "–" operator2 = "+" Lstr = str(-L) else: operator1 = "+" operator2 = "–" Lstr = str(L) LL = self._calc_stator_result('L2') if LL < 0: LLstr = "–" + str(-LL) else: LLstr = str(LL) s = "%s %s %s = %s\t\t%s %s %s = %s" % (L2str, operator1, Lstr, LLstr, LLstr, operator2, Lstr, L2str) elif self.active_stator.name == 'LLn2' and \ self.active_slide.name == 'C': dx = self.name_to_stator('LLn2').spr.get_xy()[0] S = self.active_slide.calculate() R = self._calc_stator_result('LLn2') if self.name_to_slide('C').spr.get_xy()[0] == dx: s = " ln(%0.2f) = %0.2f\t\texp(%0.2f) = %0.2f" % (S, R, R, S) elif self.sugar: self.parent.set_function_unknown() if self.active_slide.name == 'custom' or \ self.active_stator.name == 'custom2': if self.error_msg is not None: s = self.error_msg else: s = '' self.result_label.draw(1000) self.result_label.spr.set_label(s.replace('.', self.decimal_point)) def _top_slide_offset(self, x): """ Calcualate the offset between the top and bottom slides """ x2, y2 = self.active_slide.spr.get_xy() return x2 - x # Calculate the value of individual slides and stators: # (a) the offset of the reticule along the slide # (b) the offset of the slide along the stator # (c) the offset of the reticule along the reticule def _r_offset(self, slide): return self.reticule.spr.get_xy()[0] - slide.spr.get_xy()[0] def _calc_slide_value(self, name=None): if name is None: name = self.active_slide.name return self.function_calc(name, self._r_offset(self.name_to_slide(name)), SLIDE) def _calc_stator_value(self, name=None): if name is None: name = self.active_stator.name return self.function_calc( name, self._top_slide_offset(self.name_to_stator(name).spr.get_xy()[0]), STATOR) def _calc_stator_result(self, name=None): if name is None: name = self.active_stator.name return self.function_calc(name, self._r_offset(self.name_to_stator(name)), STATOR) def function_calc(self, name, dx, slide): self.error_msg = None if name in ['custom', 'custom2']: my_result = "def f(x): return " + \ self.result_function[slide].replace('import','') my_label = "def f(x): return " + \ self.label_function[slide].replace('import','') else: my_result = "def f(x): return " + DEFINITIONS[name][FRESULT] my_label = "def f(x): return " + DEFINITIONS[name][FDISPLAY] # Some slides handle wrap-around rescale = 1 offset = 0 if name in ['C', 'D', 'CI', 'DI', 'LLn', 'LLn2', 'Log', 'Log2']: if dx < 0: rescale = 0.1 dx += SCALE elif name in ['A', 'B']: if dx < 0: rescale = 0.01 dx += SCALE elif name in ['K', 'K2']: if dx < 0: rescale = 0.001 dx += SCALE elif name in ['L', 'L2']: rescale = 10 if dx < 0: dx += SCALE offset = -10 userdefined = {} try: exec(my_result, globals(), userdefined) result = list(userdefined.values())[0](float(dx) / SCALE) * rescale +\ offset except OverflowError as e: self.error_msg = _('Overflow Error') + ': ' + str(e) return '?' except NameError as e: self.error_msg = _('Name Error') + ': ' + str(e) return '?' except ZeroDivisionError as e: self.error_msg = _('Can not divide by zero') + ': ' + str(e) return '?' except TypeError as e: self.error_msg = _('Type Error') + ': ' + str(e) return '?' except ValueError as e: self.error_msg = _('Type Error') + ': ' + str(e) return '?' except SyntaxError as e: self.error_msg = _('Syntax Error') + ': ' + str(e) return '?' except: traceback.print_exc() return None # Some special cases to fine-tune the label display precision precision = 2 if name in ['A', 'B', 'K', 'K2']: if result > 50: precision = 1 elif name in ['S', 'S2']: if result > 60: precision = 1 if name in ['K', 'K2']: if result > 500: precision = 0 userdefined = {} try: exec(my_label, globals(), userdefined) label = list(userdefined.values())[0](result) if type(label) == float: return round(label, precision) else: return label except OverflowError as e: self.error_msg = _('Overflow Error') + ': ' + str(e) except NameError as e: self.error_msg = _('Name Error') + ': ' + str(e) except ZeroDivisionError as e: self.error_msg = _('Can not divide by zero') + ': ' + str(e) except TypeError as e: self.error_msg = _('Type Error') + ': ' + str(e) except ValueError as e: self.error_msg = _('Type Error') + ': ' + str(e) except SyntaxError as e: self.error_msg = _('Syntax Error') + ': ' + str(e) except: traceback.print_exc() return None return '??'
class Yupana(): def __init__(self, canvas, parent=None, colors=['#A0FFA0', '#FF8080']): self._activity = parent self._colors = ['#FFFFFF'] self._colors.append(colors[0]) self._colors.append(colors[1]) self._colors.append('#000000') self._canvas = canvas if parent is not None: parent.show_all() self._parent = parent self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.connect("draw", self.__draw_cb) self._canvas.connect("button-press-event", self._button_press_cb) self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - (GRID_CELL_SIZE * 1.5) self._scale = self._width / (20 * DOT_SIZE * 1.1) self._dot_size = int(DOT_SIZE * self._scale) self._space = int(self._dot_size / 5.) self.we_are_sharing = False self._sum = 0 self._mode = 'ten' self.custom = [1, 1, 1, 1, 10] # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) Sprite(self._sprites, 0, 0, self._box(self._width, self._height, color=colors[1])) self._number_box = Sprite( self._sprites, 0, 0, self._box(self._width, 2 * self._dot_size, color=colors[1])) self._number_box.set_label_attributes(48) self._dots = [] for p in range(SIX): y = self._height - self._space Sprite(self._sprites, 0, y, self._line(vertical=False)) x = int(p * self._width / 6) + self._space y -= self._dot_size for d in range(3): # bottom of fives row self._dots.append( Sprite(self._sprites, x, y, self._new_dot(self._colors[0]))) self._dots[-1].type = 0 # not set # self._dots[-1].set_label_color('white') x += self._dot_size + self._space x = int((p * self._width / 6.) + self._dot_size / 2.) + self._space y -= self._dot_size + self._space for d in range(2): # top of fives row self._dots.append( Sprite(self._sprites, x, y, self._new_dot(self._colors[0]))) self._dots[-1].type = 0 # not set # self._dots[-1].set_label_color('white') x += self._dot_size + self._space y -= self._dot_size Sprite(self._sprites, 0, y, self._line(vertical=False)) x = int((p * self._width / 6.) + self._dot_size / 2.) + self._space y -= self._dot_size for d in range(2): # bottom of threes row self._dots.append( Sprite(self._sprites, x, y, self._new_dot(self._colors[0]))) self._dots[-1].type = 0 # not set # self._dots[-1].set_label_color('white') x += self._dot_size + self._space x = int((p * self._width / 6.) + self._dot_size) + self._space y -= self._dot_size + self._space for d in range(1): # top of threes row self._dots.append( Sprite(self._sprites, x, y, self._new_dot(self._colors[0]))) self._dots[-1].type = 0 # not set # self._dots[-1].set_label_color('white') x += self._dot_size + self._space y -= self._dot_size Sprite(self._sprites, 0, y, self._line(vertical=False)) x = int((p * self._width / 6.) + self._dot_size / 2.) + self._space y -= self._dot_size for d in range(2): # twos row self._dots.append( Sprite(self._sprites, x, y, self._new_dot(self._colors[0]))) self._dots[-1].type = 0 # not set # self._dots[-1].set_label_color('white') x += self._dot_size + self._space y -= self._dot_size Sprite(self._sprites, 0, y, self._line(vertical=False)) x = int((p * self._width / 6.) + self._dot_size) + self._space y -= self._dot_size for d in range(1): # ones row self._dots.append( Sprite(self._sprites, x, y, self._new_dot(self._colors[0]))) self._dots[-1].type = 0 # not set # self._dots[-1].set_label_color('white') x += self._dot_size + self._space y -= self._dot_size Sprite(self._sprites, 0, y, self._line(vertical=False)) for p in range(SIX - 1): x = int((p + 1) * self._width / 6) Sprite(self._sprites, x - 1, y, self._line(vertical=True)) # and initialize a few variables we'll need. self._all_clear() def _all_clear(self): ''' Things to reinitialize when starting up a new yupana. ''' self._sum = 0 for dot in self._dots: if dot.type > 0: dot.type = 0 dot.set_shape(self._new_dot(self._colors[0])) dot.set_label('') self.set_label(str(self._sum)) if self.we_are_sharing: _logger.debug('sending new label to the share') self._parent.send_label(str(self._sum)) def _initiating(self): return self._activity._collab.props.leader def new_yupana(self, mode=None): ''' Create a new yupana. ''' self._all_clear() if mode is not None: self._mode = mode o = (SIX - 1) * (TEN + 1) # only label units if mode == 'ten': for i in range(TEN + 1): self._dots[o + i].set_label('1') self._dots[o - 1].set_label('10') elif mode == 'twenty': for i in range(TEN + 1): if i in [7, 10]: self._dots[o + i].set_label('1') else: self._dots[o + i].set_label('2') self._dots[o - 1].set_label('20') elif mode == 'factor': for i in range(TEN + 1): if i in [10]: self._dots[o + i].set_label('1') elif i in [8, 9]: self._dots[o + i].set_label('2') elif i in [5, 6, 7]: self._dots[o + i].set_label('3') else: self._dots[o + i].set_label('5') self._dots[o - 1].set_label('10') elif mode == 'fibonacci': for i in range(TEN + 1): if i in [10]: self._dots[o + i].set_label('1') elif i in [8, 9]: self._dots[o + i].set_label('2') elif i in [5, 6, 7]: self._dots[o + i].set_label('5') else: self._dots[o + i].set_label('20') self._dots[o - 1].set_label('60') else: # custom for i in range(TEN + 1): if i in [10]: self._dots[o + i].set_label(str(self.custom[0])) elif i in [8, 9]: self._dots[o + i].set_label(str(self.custom[1])) elif i in [5, 6, 7]: self._dots[o + i].set_label(str(self.custom[2])) else: self._dots[o + i].set_label(str(self.custom[3])) self._dots[o - 1].set_label(str(self.custom[4])) if self.we_are_sharing: _logger.debug('sending a new yupana') self._parent.send_new_yupana() def restore_yupana(self, mode, dot_list): ''' Restore a yumpana from the Journal or share ''' for i, dot in enumerate(dot_list): self._dots[i].type = dot self._dots[i].set_shape( self._new_dot(self._colors[self._dots[i].type])) if self._dots[i].type == 1: self._sum += self._calc_bead_value(i) self._mode = mode self.set_label(str(self._sum)) if self.we_are_sharing: _logger.debug('sending new label to the share') self._parent.send_label(str(self._sum)) def save_yupana(self): ''' Return dot list and orientation for saving to Journal or sharing ''' dot_list = [] for dot in self._dots: dot_list.append(dot.type) return [self._mode, dot_list] def set_label(self, string): ''' Set the label in the toolbar or the window frame. ''' self._number_box.set_label(string) def get_label(self): return self._number_box.get_label() def _button_press_cb(self, win, event): win.grab_focus() x, y = list(map(int, event.get_coords())) spr = self._sprites.find_sprite((x, y)) if spr == None: return if spr.type is not None: spr.type += 1 spr.type %= 2 spr.set_shape(self._new_dot(self._colors[spr.type])) if self.we_are_sharing: _logger.debug('sending a click to the share') self._parent.send_dot_click(self._dots.index(spr), spr.type) if spr.type == 1: self._sum += self._calc_bead_value(self._dots.index(spr)) else: self._sum -= self._calc_bead_value(self._dots.index(spr)) self.set_label(str(self._sum)) if self.we_are_sharing: _logger.debug('sending new label to the share') self._parent.send_label(str(self._sum)) return True def _calc_bead_value(self, i): ''' Calculate a bead value based on the index and the mode ''' e = 5 - i / (TEN + 1) m = i % 11 if self._mode == 'ten': return 10**e elif self._mode == 'twenty': if m in [7, 10]: return 20**e else: return (20**e) * 2 elif self._mode == 'factor': if m in [10]: return 10**e elif m in [8, 9]: return (10**e) * 2 elif m in [5, 6, 7]: return (10**e) * 3 else: return (10**e) * 5 elif self._mode == 'fibonacci': if m in [10]: return 60**e elif m in [8, 9]: return (60**e) * 2 elif m in [5, 6, 7]: return (60**e) * 5 else: return (60**e) * 20 else: # custom if m in [10]: return (self.custom[4]**e) * self.custom[0] elif m in [8, 9]: return (self.custom[4]**e) * self.custom[1] elif m in [5, 6, 7]: return (self.custom[4]**e) * self.custom[2] else: return (self.custom[4]**e) * self.custom[3] def remote_button_press(self, dot, color): ''' Receive a button press from a sharer ''' self._dots[dot].type = color self._dots[dot].set_shape(self._new_dot(self._colors[color])) def set_sharing(self, share=True): _logger.debug('enabling sharing') self.we_are_sharing = share def _grid_to_dot(self, pos): ''' calculate the dot index from a column and row in the grid ''' return pos[0] + pos[1] * TEN def _dot_to_grid(self, dot): ''' calculate the grid column and row for a dot ''' return [dot % TEN, int(dot / TEN)] def __draw_cb(self, canvas, cr): self._sprites.redraw_sprites(cr=cr) def do_expose_event(self, event): ''' Handle the expose-event by drawing ''' # Restrict Cairo to the exposed area cr = self._canvas.window.cairo_create() cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): Gtk.main_quit() def _new_dot(self, color): ''' generate a dot of a color color ''' def darken(color): ''' return a darker color than color ''' gdk_fill_color = Gdk.color_parse(self._fill) gdk_fill_dark_color = Gdk.Color(int(gdk_fill_color.red * 0.5), int(gdk_fill_color.green * 0.5), int(gdk_fill_color.blue * 0.5)).to_string() return str(gdk_fill_dark_color) self._dot_cache = {} if not color in self._dot_cache: self._stroke = color self._fill = color self._fill_dark = darken(color) self._svg_width = self._dot_size self._svg_height = self._dot_size if color in ['#FFFFFF', '#000000']: pixbuf = svg_str_to_pixbuf( self._header() + \ self._circle(self._dot_size / 2., self._dot_size / 2., self._dot_size / 2.) + \ self._footer()) else: pixbuf = svg_str_to_pixbuf( self._header() + \ self._def(self._dot_size) + \ self._gradient(self._dot_size / 2., self._dot_size / 2., self._dot_size / 2.) + \ self._footer()) surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self._svg_width, self._svg_height) context = cairo.Context(surface) Gdk.cairo_set_source_pixbuf(context, pixbuf, 0, 0) context.rectangle(0, 0, self._svg_width, self._svg_height) context.fill() self._dot_cache[color] = surface return self._dot_cache[color] def _line(self, vertical=True): ''' Generate a center line ''' if vertical: self._svg_width = 3 self._svg_height = self._dot_size * 10 + self._space * 2 return svg_str_to_pixbuf( self._header() + \ self._rect(3, self._dot_size * 10 + self._space * 2, 0, 0) + \ self._footer()) else: self._svg_width = self._width self._svg_height = 3 return svg_str_to_pixbuf( self._header() + \ self._rect(self._width, 3, 0, 0) + \ self._footer()) def _box(self, w, h, color='white'): ''' Generate a box ''' self._svg_width = w self._svg_height = h return svg_str_to_pixbuf( self._header() + \ self._rect(self._svg_width, self._svg_height, 0, 0, color=color) + \ self._footer()) def _header(self): return '<svg\n' + 'xmlns:svg="http://www.w3.org/2000/svg"\n' + \ 'xmlns="http://www.w3.org/2000/svg"\n' + \ 'xmlns:xlink="http://www.w3.org/1999/xlink"\n' + \ 'version="1.1"\n' + 'width="' + str(self._svg_width) + '"\n' + \ 'height="' + str(self._svg_height) + '">\n' def _rect(self, w, h, x, y, color='black'): svg_string = ' <rect\n' svg_string += ' width="%f"\n' % (w) svg_string += ' height="%f"\n' % (h) svg_string += ' rx="%f"\n' % (0) svg_string += ' ry="%f"\n' % (0) svg_string += ' x="%f"\n' % (x) svg_string += ' y="%f"\n' % (y) if color == 'black': svg_string += 'style="fill:#000000;stroke:#000000;"/>\n' elif color == 'white': svg_string += 'style="fill:#ffffff;stroke:#ffffff;"/>\n' else: svg_string += 'style="fill:%s;stroke:%s;"/>\n' % (color, color) return svg_string def _circle(self, r, cx, cy): scale = (DOT_SIZE * self._scale) / 55. return '\ <g transform="matrix(%f,0,0,%f,0,0)">\ <path\ d="m 35.798426,4.2187227 c -2.210658,0.9528967 -4.993612,-0.9110169 -7.221856,0 C 23.805784,6.1692574 20.658687,10.945585 17.543179,15.051507 13.020442,21.012013 7.910957,27.325787 6.7103942,34.711004 6.0558895,38.737163 6.434461,43.510925 8.917073,46.747431 c 3.604523,4.699107 15.24614,7.62307 16.048569,7.62307 0.802429,0 8.366957,0.46766 12.036427,-1.203642 2.841316,-1.294111 5.173945,-3.766846 6.820641,-6.419428 2.543728,-4.097563 3.563068,-9.062928 4.21275,-13.841891 C 49.107723,25.018147 48.401726,15.967648 47.433639,9.0332932 47.09109,6.5796321 43.508442,7.2266282 42.329009,5.7211058 41.256823,4.3524824 42.197481,1.860825 40.813604,0.80840168 40.384481,0.48205899 39.716131,0.42556727 39.208747,0.60779459 37.650593,1.1674066 37.318797,3.5633724 35.798426,4.2187227 z"\ style="fill:#be9e00;fill-opacity:1;stroke:%s;stroke-width:3.0" />\ </g>' % (scale, scale, self._colors[1]) def _gradient(self, r, cx, cy): scale = (DOT_SIZE * self._scale) / 55. return '\ <defs>\ <linearGradient\ id="linearGradient3769">\ <stop\ id="stop3771"\ style="stop-color:#ffff00;stop-opacity:1"\ offset="0" />\ <stop\ id="stop3773"\ style="stop-color:#ffff00;stop-opacity:0"\ offset="1" />\ </linearGradient>\ <linearGradient\ x1="10.761448"\ y1="41.003559"\ x2="56.70686"\ y2="41.003559"\ id="linearGradient2999"\ xlink:href="#linearGradient3769"\ gradientUnits="userSpaceOnUse"\ gradientTransform="matrix(0.93094239,0,0,0.93094239,-3.9217825,-2.4013121)" />\ </defs>\ <g transform="matrix(%f,0,0,%f,0,0)">\ <path\ d="m 35.798426,4.2187227 c -2.210658,0.9528967 -4.993612,-0.9110169 -7.221856,0 C 23.805784,6.1692574 20.658687,10.945585 17.543179,15.051507 13.020442,21.012013 7.910957,27.325787 6.7103942,34.711004 6.0558895,38.737163 6.434461,43.510925 8.917073,46.747431 c 3.604523,4.699107 15.24614,7.62307 16.048569,7.62307 0.802429,0 8.366957,0.46766 12.036427,-1.203642 2.841316,-1.294111 5.173945,-3.766846 6.820641,-6.419428 2.543728,-4.097563 3.563068,-9.062928 4.21275,-13.841891 C 49.107723,25.018147 48.401726,15.967648 47.433639,9.0332932 47.09109,6.5796321 43.508442,7.2266282 42.329009,5.7211058 41.256823,4.3524824 42.197481,1.860825 40.813604,0.80840168 40.384481,0.48205899 39.716131,0.42556727 39.208747,0.60779459 37.650593,1.1674066 37.318797,3.5633724 35.798426,4.2187227 z"\ style="fill:#fffec2;fill-opacity:1;stroke:#878600;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />\ <path\ d="m 15.11608,18.808876 c 1.271657,-1.444003 4.153991,-3.145785 5.495465,-1.7664 2.950062,3.033434 -6.07961,8.17155 -4.219732,11.972265 0.545606,1.114961 2.322391,1.452799 3.532799,1.177599 5.458966,-1.241154 6.490591,-12.132334 12.070397,-11.677864 1.584527,0.129058 2.526156,2.269906 2.845867,3.827199 0.453143,2.207236 -1.962667,6.182399 -1.570133,6.574932 0.392533,0.392533 2.371401,0.909584 3.140266,0.196266 1.91857,-1.779962 -0.490667,-7.752531 0.09813,-7.850664 0.5888,-0.09813 4.421663,2.851694 5.789865,5.004799 0.583188,0.917747 -0.188581,2.956817 0.8832,3.140266 2.128963,0.364398 1.601562,-5.672021 3.729066,-5.299199 1.836829,0.321884 1.450925,3.532631 1.471999,5.397332 0.06743,5.965698 -0.565586,12.731224 -4.317865,17.369596 -3.846028,4.75426 -10.320976,8.31978 -16.388263,7.556266 C 22.030921,53.720741 16.615679,52.58734 11.485147,49.131043 7.9833717,46.771994 6.8028191,42.063042 6.5784815,37.846738 6.3607378,33.754359 8.3381535,29.765466 10.111281,26.070741 c 1.271951,-2.650408 2.940517,-4.917813 5.004799,-7.261865 z"\ style="fill:url(#linearGradient2999);fill-opacity:1;stroke:none" />\ <path\ d="m 32.382709,4.7758124 c -0.123616,1.0811396 1.753928,2.8458658 2.728329,2.9439992 0.974405,0.098134 6.718874,0.7298319 9.159392,-0.1962668 0.820281,-0.3112699 0.968884,-0.9547989 0.974407,-1.4719993 0.02053,-1.9240971 0.03247,-4.7715376 -3.507853,-5.49546551 C 39.556079,0.11012647 37.217081,1.4131653 35.500801,2.2243463 34.054814,2.9077752 32.496703,3.7788369 32.382709,4.7758124 z"\ style="fill:#b69556;fill-opacity:1;stroke:#b69556;stroke-width:1.31189477px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /></g>' % ( scale, scale) def _def(self, r): return ' <defs>\ <linearGradient\ id="linearGradient3755">\ <stop\ id="stop3757"\ style="stop-color:%s;stop-opacity:1"\ offset="0" />\ <stop\ id="stop3759"\ style="stop-color:%s;stop-opacity:1"\ offset="1" />\ </linearGradient>\ <radialGradient\ cx="0"\ cy="0"\ r="%f"\ fx="%f"\ fy="%f"\ id="radialGradient3761"\ xlink:href="#linearGradient3755"\ gradientUnits="userSpaceOnUse" />\ </defs>\ ' % (self._fill, self._fill_dark, r, r / 3, r / 3) def _footer(self): return '</svg>\n'
class Game(): def __init__(self, canvas, parent=None, colors=['#A0FFA0', '#FF8080']): self._activity = parent self._colors = [colors[0]] self._colors.append(colors[1]) self._colors.append('#FFFFFF') self._colors.append('#000000') self._colors.append('#FF0000') self._colors.append('#FF8000') self._colors.append('#FFFF00') self._colors.append('#00FF00') self._colors.append('#00FFFF') self._colors.append('#0000FF') self._colors.append('#FF00FF') self._canvas = canvas if parent is not None: parent.show_all() self._parent = parent self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK) self._canvas.add_events(Gdk.EventMask.POINTER_MOTION_MASK) self._canvas.connect("draw", self.__draw_cb) self._canvas.connect("button-press-event", self._button_press_cb) self._canvas.connect("button-release-event", self._button_release_cb) self._canvas.connect("motion-notify-event", self._mouse_move_cb) self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - GRID_CELL_SIZE scale = [ self._width / (10 * DOT_SIZE * 1.2), self._height / (6 * DOT_SIZE * 1.2) ] self._scale = min(scale) self._dot_size = int(DOT_SIZE * self._scale) self._space = int(self._dot_size / 5.) self._orientation = 'horizontal' self.we_are_sharing = False self.playing_with_robot = False self._press = False self.last_spr = None self._timer = None self.roygbiv = False # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) self._dots = [] for y in range(SIX): for x in range(TEN): xoffset = int((self._width - TEN * self._dot_size - \ (TEN - 1) * self._space) / 2.) self._dots.append( Sprite(self._sprites, xoffset + x * (self._dot_size + self._space), y * (self._dot_size + self._space), self._new_dot(self._colors[2]))) self._dots[-1].type = 2 # not set self._dots[-1].set_label_attributes(40) self.vline = Sprite(self._sprites, int(self._width / 2.) - 1, 0, self._line(vertical=True)) n = SIX / 2. self.hline = Sprite( self._sprites, 0, int(self._dot_size * n + self._space * (n - 0.5)) - 1, self._line(vertical=False)) self.hline.hide() # and initialize a few variables we'll need. self._all_clear() def _all_clear(self): ''' Things to reinitialize when starting up a new game. ''' for dot in self._dots: dot.type = 2 dot.set_shape(self._new_dot(self._colors[2])) dot.set_label('') self._set_orientation() def _set_orientation(self): ''' Set bar and message for current orientation ''' if self._orientation == 'horizontal': self.hline.hide() self.vline.set_layer(1000) elif self._orientation == 'vertical': self.hline.set_layer(1000) self.vline.hide() else: self.hline.set_layer(1000) self.vline.set_layer(1000) ''' if self._orientation == 'horizontal': self._set_label( _('Click on the dots to make a horizontal reflection.')) elif self._orientation == 'vertical': self._set_label( _('Click on the dots to make a vertical reflection.')) else: self._set_label( _('Click on the dots to make a bilateral reflection.')) ''' def _initiating(self): return self._activity.initiating def new_game(self, orientation='horizontal'): ''' Start a new game. ''' self._orientation = orientation self._all_clear() # Fill in a few dots to start for i in range(int(TEN * SIX / 2)): n = int(uniform(0, TEN * SIX)) if self.roygbiv: self._dots[n].type = int(uniform(2, len(self._colors))) else: self._dots[n].type = int(uniform(0, 4)) self._dots[n].set_shape( self._new_dot(self._colors[self._dots[n].type])) if self.we_are_sharing: _logger.debug('sending a new game') self._parent.send_new_game() def restore_game(self, dot_list, orientation): ''' Restore a game from the Journal or share ''' for i, dot in enumerate(dot_list): self._dots[i].type = dot self._dots[i].set_shape( self._new_dot(self._colors[self._dots[i].type])) self._orientation = orientation self._set_orientation() def save_game(self): ''' Return dot list and orientation for saving to Journal or sharing ''' dot_list = [] for dot in self._dots: dot_list.append(dot.type) return [dot_list, self._orientation] def _set_label(self, string): ''' Set the label in the toolbar or the window frame. ''' self._activity.status.set_label(string) def _button_press_cb(self, win, event): win.grab_focus() x, y = list(map(int, event.get_coords())) self._press = True spr = self._sprites.find_sprite((x, y)) if spr == None: return True self.last_spr = spr if spr.type is not None: self._increment_dot(spr) return True def _button_release_cb(self, win, event): self._press = False self._stop_increment_dot() def _increment_dot_cb(self, spr): spr.type += 1 if self.roygbiv: if spr.type >= len(self._colors): spr.type = 2 else: spr.type %= 4 spr.set_shape(self._new_dot(self._colors[spr.type])) if self.playing_with_robot: self._robot_play(spr) self._test_game_over() if self.we_are_sharing: _logger.debug('sending a click to the share') self._parent.send_dot_click(self._dots.index(spr), spr.type) return True # call again def _increment_dot(self, spr): self._stop_increment_dot() if self._increment_dot_cb(spr): self._timer = GLib.timeout_add(1000, self._increment_dot_cb, spr) def _stop_increment_dot(self): if not self._timer is None: GLib.source_remove(self._timer) self._timer = None def _mouse_move_cb(self, win, event): """ Drag a tile with the mouse. """ if not self._press: return x, y = list(map(int, event.get_coords())) spr = self._sprites.find_sprite((x, y)) if spr == self.last_spr: return True if spr is None: return True if spr.type is not None: self.last_spr = spr self._increment_dot(spr) def _robot_play(self, dot): ''' Robot reflects dot clicked. ''' x, y = self._dot_to_grid(self._dots.index(dot)) if self._orientation == 'horizontal': x = TEN - x - 1 i = self._grid_to_dot((x, y)) self._dots[i].type = dot.type self._dots[i].set_shape(self._new_dot(self._colors[dot.type])) if self.we_are_sharing: _logger.debug('sending a robot click to the share') self._parent.send_dot_click(i, dot.type) elif self._orientation == 'vertical': y = SIX - y - 1 i = self._grid_to_dot((x, y)) self._dots[i].type = dot.type self._dots[i].set_shape(self._new_dot(self._colors[dot.type])) if self.we_are_sharing: _logger.debug('sending a robot click to the share') self._parent.send_dot_click(i, dot.type) else: x = TEN - x - 1 i = self._grid_to_dot((x, y)) self._dots[i].type = dot.type self._dots[i].set_shape(self._new_dot(self._colors[dot.type])) if self.we_are_sharing: _logger.debug('sending a robot click to the share') self._parent.send_dot_click(i, dot.type) y = SIX - y - 1 i = self._grid_to_dot((x, y)) self._dots[i].type = dot.type self._dots[i].set_shape(self._new_dot(self._colors[dot.type])) if self.we_are_sharing: _logger.debug('sending a robot click to the share') self._parent.send_dot_click(i, dot.type) x = TEN - x - 1 i = self._grid_to_dot((x, y)) self._dots[i].type = dot.type self._dots[i].set_shape(self._new_dot(self._colors[dot.type])) if self.we_are_sharing: _logger.debug('sending a robot click to the share') self._parent.send_dot_click(i, dot.type) def remote_button_press(self, dot, color): ''' Receive a button press from a sharer ''' self._dots[dot].type = color self._dots[dot].set_shape(self._new_dot(self._colors[color])) def set_sharing(self, share=True): _logger.debug('enabling sharing') self.we_are_sharing = share def _smile(self): for dot in self._dots: dot.set_label(':)') def _test_game_over(self): ''' Check to see if game is over ''' if self._orientation == 'horizontal': for y in range(SIX): for x in range(SIX): if self._dots[y * TEN + x].type != \ self._dots[y * TEN + TEN - x - 1].type: self._set_label(_('keep trying')) return False self._set_label(_('good work')) self._smile() return True if self._orientation == 'vertical': for y in range(int(SIX / 2)): for x in range(TEN): if self._dots[y * TEN + x].type != \ self._dots[(SIX - y - 1) * TEN + x].type: self._set_label(_('keep trying')) return False self._set_label(_('good work')) else: for y in range(SIX): for x in range(SIX): if self._dots[y * TEN + x].type != \ self._dots[y * TEN + TEN - x - 1].type: self._set_label(_('keep trying')) return False for y in range(int(SIX / 2)): for x in range(TEN): if self._dots[y * TEN + x].type != \ self._dots[(SIX - y - 1) * TEN + x].type: self._set_label(_('keep trying')) return False self._set_label(_('good work')) self._smile() return True def __draw_cb(self, canvas, cr): self._sprites.redraw_sprites(cr=cr) def _grid_to_dot(self, pos): ''' calculate the dot index from a column and row in the grid ''' return pos[0] + pos[1] * TEN def _dot_to_grid(self, dot): ''' calculate the grid column and row for a dot ''' return [dot % TEN, int(dot / TEN)] def _expose_cb(self, win, event): self.do_expose_event(event) def do_expose_event(self, event): ''' Handle the expose-event by drawing ''' # Restrict Cairo to the exposed area cr = self._canvas.window.cairo_create() cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): Gtk.main_quit() def _new_dot(self, color): ''' generate a dot of a color color ''' self._dot_cache = {} if not color in self._dot_cache: self._stroke = color self._fill = color self._svg_width = self._dot_size self._svg_height = self._dot_size pixbuf = svg_str_to_pixbuf( self._header() + \ self._circle(self._dot_size / 2., self._dot_size / 2., self._dot_size / 2.) + \ self._footer()) surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self._svg_width, self._svg_height) context = cairo.Context(surface) Gdk.cairo_set_source_pixbuf(context, pixbuf, 0, 0) context.rectangle(0, 0, self._svg_width, self._svg_height) context.fill() self._dot_cache[color] = surface return self._dot_cache[color] def _line(self, vertical=True): ''' Generate a center line ''' if vertical: self._svg_width = 3 self._svg_height = self._height return svg_str_to_pixbuf( self._header() + \ self._rect(3, self._height, 0, 0) + \ self._footer()) else: self._svg_width = self._width self._svg_height = 3 return svg_str_to_pixbuf( self._header() + \ self._rect(self._width, 3, 0, 0) + \ self._footer()) def _header(self): return '<svg\n' + 'xmlns:svg="http://www.w3.org/2000/svg"\n' + \ 'xmlns="http://www.w3.org/2000/svg"\n' + \ 'xmlns:xlink="http://www.w3.org/1999/xlink"\n' + \ 'version="1.1"\n' + 'width="' + str(self._svg_width) + '"\n' + \ 'height="' + str(self._svg_height) + '">\n' def _rect(self, w, h, x, y): svg_string = ' <rect\n' svg_string += ' width="%f"\n' % (w) svg_string += ' height="%f"\n' % (h) svg_string += ' rx="%f"\n' % (0) svg_string += ' ry="%f"\n' % (0) svg_string += ' x="%f"\n' % (x) svg_string += ' y="%f"\n' % (y) svg_string += 'style="fill:#000000;stroke:#000000;"/>\n' return svg_string def _circle(self, r, cx, cy): return '<circle style="fill:' + str(self._fill) + ';stroke:' + \ str(self._stroke) + ';" r="' + str(r - 0.5) + '" cx="' + \ str(cx) + '" cy="' + str(cy) + '" />\n' def _footer(self): return '</svg>\n'
class PortfolioActivity(activity.Activity): ''' Make a slideshow from starred Journal entries. ''' def __init__(self, handle): ''' Initialize the toolbars and the work surface ''' super(PortfolioActivity, self).__init__(handle) self._tmp_path = get_path(activity, 'instance') self._hw = get_hardware() self._setup_toolbars() self._setup_canvas() self._setup_workspace() self._thumbs = [] self._thumbnail_mode = False def _setup_canvas(self): ''' Create a canvas ''' self._canvas = gtk.DrawingArea() self._canvas.set_size_request(gtk.gdk.screen_width(), gtk.gdk.screen_height()) self.set_canvas(self._canvas) self._canvas.show() self.show_all() self._canvas.set_flags(gtk.CAN_FOCUS) self._canvas.add_events(gtk.gdk.BUTTON_PRESS_MASK) self._canvas.add_events(gtk.gdk.POINTER_MOTION_MASK) self._canvas.add_events(gtk.gdk.BUTTON_RELEASE_MASK) self._canvas.add_events(gtk.gdk.KEY_PRESS_MASK) self._canvas.connect("expose-event", self._expose_cb) self._canvas.connect("button-press-event", self._button_press_cb) self._canvas.connect("button-release-event", self._button_release_cb) self._canvas.connect("motion-notify-event", self._mouse_move_cb) def _setup_workspace(self): ''' Prepare to render the datastore entries. ''' self._colors = profile.get_color().to_string().split(',') # Use the lighter color for the text background if lighter_color(self._colors) == 0: tmp = self._colors[0] self._colors[0] = self._colors[1] self._colors[1] = tmp self._width = gtk.gdk.screen_width() self._height = gtk.gdk.screen_height() self._scale = gtk.gdk.screen_height() / 900. if self._hw[0:2] == 'xo': titlef = 18 descriptionf = 12 else: titlef = 36 descriptionf = 24 # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) self._title = Sprite( self._sprites, 0, 0, svg_str_to_pixbuf( genblank(self._width, int(TITLEH * self._scale), self._colors))) self._title.set_label_attributes(int(titlef * self._scale), rescale=False) self._preview = Sprite( self._sprites, int( (self._width - int(PREVIEWW * self._scale)) / 2), int(PREVIEWY * self._scale), svg_str_to_pixbuf( genblank(int(PREVIEWW * self._scale), int(PREVIEWH * self._scale), self._colors))) self._full_screen = Sprite( self._sprites, int((self._width - int(FULLW * self._scale)) / 2), int(PREVIEWY * self._scale), svg_str_to_pixbuf( genblank(int(FULLW * self._scale), int(FULLH * self._scale), self._colors))) self._description = Sprite( self._sprites, int(DESCRIPTIONX * self._scale), int(DESCRIPTIONY * self._scale), svg_str_to_pixbuf( genblank(int(self._width - (2 * DESCRIPTIONX * self._scale)), int(DESCRIPTIONH * self._scale), self._colors))) self._description.set_label_attributes(int(descriptionf * self._scale)) self._description2 = Sprite( self._sprites, int(SHORTX * self._scale), int(SHORTY * self._scale), svg_str_to_pixbuf( genblank(int(self._width - (2 * SHORTX * self._scale)), int(SHORTH * self._scale), self._colors))) self._description2.set_label_attributes(int(descriptionf * self._scale)) self._my_canvas = Sprite( self._sprites, 0, 0, gtk.gdk.Pixmap(self._canvas.window, self._width, self._height, -1)) self._my_gc = self._my_canvas.images[0].new_gc() self._my_canvas.set_layer(BOTTOM) self._clear_screen() self._find_starred() self.i = 0 self._show_slide() self._playing = False self._rate = 10 def _setup_toolbars(self): ''' Setup the toolbars. ''' self.max_participants = 1 # no sharing if HAVE_TOOLBOX: toolbox = ToolbarBox() # Activity toolbar activity_button = ActivityToolbarButton(self) toolbox.toolbar.insert(activity_button, 0) activity_button.show() self.set_toolbar_box(toolbox) toolbox.show() self.toolbar = toolbox.toolbar adjust_toolbar = gtk.Toolbar() adjust_toolbar_button = ToolbarButton( label=_('Adjust'), page=adjust_toolbar, icon_name='preferences-system') adjust_toolbar.show_all() adjust_toolbar_button.show() else: # Use pre-0.86 toolbar design primary_toolbar = gtk.Toolbar() toolbox = activity.ActivityToolbox(self) self.set_toolbox(toolbox) toolbox.add_toolbar(_('Page'), primary_toolbar) adjust_toolbar = gtk.Toolbar() toolbox.add_toolbar(_('Adjust'), adjust_toolbar) toolbox.show() toolbox.set_current_toolbar(1) self.toolbar = primary_toolbar self._prev_button = button_factory('go-previous-inactive', _('Prev slide'), self._prev_cb, self.toolbar, accelerator='<Ctrl>P') self._next_button = button_factory('go-next', _('Next slide'), self._next_cb, self.toolbar, accelerator='<Ctrl>N') separator_factory(self.toolbar) self._auto_button = button_factory('media-playlist-repeat', _('Autoplay'), self._autoplay_cb, self.toolbar) if HAVE_TOOLBOX: toolbox.toolbar.insert(adjust_toolbar_button, -1) label = label_factory(_('Adjust playback speed'), adjust_toolbar) label.show() self._unit_combo = combo_factory(UNITS, TEN, _('Adjust playback speed'), self._unit_combo_cb, adjust_toolbar) self._unit_combo.show() separator_factory(self.toolbar) self._thumb_button = button_factory('thumbs-view', _('Thumbnail view'), self._thumbs_cb, self.toolbar) button_factory('view-fullscreen', _('Fullscreen'), self.do_fullscreen_cb, self.toolbar, accelerator='<Alt>Return') separator_factory(self.toolbar) self._save_button = button_factory('save-as-html', _('Save as HTML'), self._save_as_html_cb, self.toolbar) if HAVE_TOOLBOX: separator_factory(toolbox.toolbar, False, True) stop_button = StopButton(self) stop_button.props.accelerator = '<Ctrl>q' toolbox.toolbar.insert(stop_button, -1) stop_button.show() def _expose_cb(self, win, event): ''' Have to refresh after a change in window status. ''' self._sprites.redraw_sprites() return True def _destroy_cb(self, win, event): ''' Clean up on the way out. ''' gtk.main_quit() def _find_starred(self): ''' Find all the favorites in the Journal. ''' self._dsobjects, self._nobjects = datastore.find({'keep': '1'}) return def _prev_cb(self, button=None): ''' The previous button has been clicked; goto previous slide. ''' if self.i > 0: self.i -= 1 self._show_slide() def _next_cb(self, button=None): ''' The next button has been clicked; goto next slide. ''' if self.i < self._nobjects - 1: self.i += 1 self._show_slide() def _autoplay_cb(self, button=None): ''' The autoplay button has been clicked; step through slides. ''' if self._playing: self._stop_autoplay() else: if self._thumbnail_mode: self._set_view_mode(self._current_slide) self._playing = True self._auto_button.set_icon('media-playback-pause') self._loop() def _stop_autoplay(self): ''' Stop autoplaying. ''' self._playing = False self._auto_button.set_icon('media-playlist-repeat') if hasattr(self, '_timeout_id') and self._timeout_id is not None: gobject.source_remove(self._timeout_id) def _loop(self): ''' Show a slide and then call oneself with a timeout. ''' self.i += 1 if self.i == self._nobjects: self.i = 0 self._show_slide() self._timeout_id = gobject.timeout_add(int(self._rate * 1000), self._loop) def _bump_test(self): ''' Test for accelerometer event (XO 1.75 only). ''' fh = open('/sys/devices/platform/lis3lv02d/position') string = fh.read() xyz = string[1:-2].split(',') dx = int(xyz[0]) fh.close() if dx > 250: self.i += 1 if self.i == self._nobjects: self.i = 0 self._show_slide() elif dx < -250: self.i -= 1 if self.i < 0: self.i = self._nobjects - 1 self._show_slide() elif not self._thumbnail_mode: self._bump_id = gobject.timeout_add(int(100), self._bump_test) def _save_as_html_cb(self, button=None): ''' Export an HTML version of the slideshow to the Journal. ''' self._save_button.set_icon('save-in-progress') results = save_html(self._dsobjects, profile.get_nick_name(), self._colors, self._tmp_path) html_file = os.path.join(self._tmp_path, 'tmp.html') tmp_file = open(html_file, 'w') tmp_file.write(results) tmp_file.close() dsobject = datastore.create() dsobject.metadata['title'] = profile.get_nick_name() + ' ' + \ _('Portfolio') dsobject.metadata['icon-color'] = profile.get_color().to_string() dsobject.metadata['mime_type'] = 'text/html' dsobject.set_file_path(html_file) dsobject.metadata['activity'] = 'org.laptop.WebActivity' datastore.write(dsobject) dsobject.destroy() gobject.timeout_add(250, self._save_button.set_icon, 'save-as-html') return def _clear_screen(self): ''' Clear the screen to the darker of the two XO colors. ''' self._my_gc.set_foreground(self._my_gc.get_colormap().alloc_color( self._colors[0])) self._my_canvas.images[0].draw_rectangle(self._my_gc, True, 0, 0, self._width, self._height) self._title.hide() self._full_screen.hide() self._preview.hide() self._description.hide() if hasattr(self, '_thumbs'): for thumbnail in self._thumbs: thumbnail[0].hide() self.invalt(0, 0, self._width, self._height) # Reset drag settings self._press = None self._release = None self._dragpos = [0, 0] self._total_drag = [0, 0] self.last_spr_moved = None def _show_slide(self): ''' Display a title, preview image, and decription for slide i. ''' self._clear_screen() if self._nobjects == 0: self._prev_button.set_icon('go-previous-inactive') self._next_button.set_icon('go-next-inactive') self._description.set_label( _('Do you have any items in your Journal starred?')) self._description.set_layer(MIDDLE) return if self.i == 0: self._prev_button.set_icon('go-previous-inactive') else: self._prev_button.set_icon('go-previous') if self.i == self._nobjects - 1: self._next_button.set_icon('go-next-inactive') else: self._next_button.set_icon('go-next') pixbuf = None media_object = False try: pixbuf = gtk.gdk.pixbuf_new_from_file_at_size( self._dsobjects[self.i].file_path, int(PREVIEWW * self._scale), int(PREVIEWH * self._scale)) media_object = True except: pixbuf = get_pixbuf_from_journal(self._dsobjects[self.i], 300, 225) if pixbuf is not None: if not media_object: self._preview.images[0] = pixbuf.scale_simple( int(PREVIEWW * self._scale), int(PREVIEWH * self._scale), gtk.gdk.INTERP_TILES) self._full_screen.hide() self._preview.set_layer(MIDDLE) else: self._full_screen.images[0] = pixbuf.scale_simple( int(FULLW * self._scale), int(FULLH * self._scale), gtk.gdk.INTERP_TILES) self._full_screen.set_layer(MIDDLE) self._preview.hide() else: if self._preview is not None: self._preview.hide() self._full_screen.hide() self._title.set_label(self._dsobjects[self.i].metadata['title']) self._title.set_layer(MIDDLE) if 'description' in self._dsobjects[self.i].metadata: if media_object: self._description2.set_label( self._dsobjects[self.i].metadata['description']) self._description2.set_layer(MIDDLE) self._description.set_label('') self._description.hide() else: self._description.set_label( self._dsobjects[self.i].metadata['description']) self._description.set_layer(MIDDLE) self._description2.set_label('') self._description2.hide() else: self._description.set_label('') self._description.hide() self._description2.set_label('') self._description2.hide() if self._hw == XO175: self._bump_id = gobject.timeout_add(int(500), self._bump_test) def _thumbs_cb(self, button=None): ''' Toggle between thumbnail view and slideshow view. ''' if self._thumbnail_mode: self._set_view_mode(self._current_slide) self._show_slide() else: self._stop_autoplay() self._current_slide = self.i self._thumbnail_mode = True self._clear_screen() self._prev_button.set_icon('go-previous-inactive') self._next_button.set_icon('go-next-inactive') self._thumb_button.set_icon('slide-view') self._thumb_button.set_tooltip(_('Slide view')) n = int(sqrt(self._nobjects) + 0.5) w = int(self._width / n) h = int(w * 0.75) # maintain 4:3 aspect ratio x_off = int((self._width - n * w) / 2) x = x_off y = 0 for i in range(self._nobjects): self.i = i self._show_thumb(x, y, w, h) x += w if x + w > self._width: x = x_off y += h self.i = 0 # Reset position in slideshow to the beginning return False def _show_thumb(self, x, y, w, h): ''' Display a preview image and title as a thumbnail. ''' if len(self._thumbs) < self.i + 1: # Create a Sprite for this thumbnail pixbuf = None try: pixbuf = gtk.gdk.pixbuf_new_from_file_at_size( self._dsobjects[self.i].file_path, int(w), int(h)) except: pixbuf = get_pixbuf_from_journal(self._dsobjects[self.i], int(w), int(h)) pixbuf_thumb = pixbuf.scale_simple(int(w), int(h), gtk.gdk.INTERP_TILES) self._thumbs.append( [Sprite(self._sprites, x, y, pixbuf_thumb), x, y, self.i]) self._thumbs[-1][0].set_label(str(self.i + 1)) self._thumbs[self.i][0].set_layer(TOP) def do_fullscreen_cb(self, button): ''' Hide the Sugar toolbars. ''' self.fullscreen() def invalt(self, x, y, w, h): ''' Mark a region for refresh ''' self._canvas.window.invalidate_rect( gtk.gdk.Rectangle(int(x), int(y), int(w), int(h)), False) def _spr_to_thumb(self, spr): ''' Find which entry in the thumbnails table matches spr. ''' for i, thumb in enumerate(self._thumbs): if spr == thumb[0]: return i return -1 def _spr_is_thumbnail(self, spr): ''' Does spr match an entry in the thumbnails table? ''' if self._spr_to_thumb(spr) == -1: return False else: return True def _button_press_cb(self, win, event): ''' The mouse button was pressed. Is it on a thumbnail sprite? ''' win.grab_focus() x, y = map(int, event.get_coords()) self._dragpos = [x, y] self._total_drag = [0, 0] spr = self._sprites.find_sprite((x, y)) self._press = None self._release = None # Are we clicking on a thumbnail? if not self._spr_is_thumbnail(spr): return False _logger.debug('found a thumbnail') self.last_spr_moved = spr self._press = spr self._press.set_layer(DRAG) return False def _mouse_move_cb(self, win, event): """ Drag a thumbnail with the mouse. """ spr = self._press if spr is None: self._dragpos = [0, 0] return False win.grab_focus() x, y = map(int, event.get_coords()) dx = x - self._dragpos[0] dy = y - self._dragpos[1] spr.move_relative([dx, dy]) self._dragpos = [x, y] self._total_drag[0] += dx self._total_drag[1] += dy return False def _button_release_cb(self, win, event): ''' Button event is used to swap slides or goto next slide. ''' win.grab_focus() self._dragpos = [0, 0] x, y = map(int, event.get_coords()) if self._thumbnail_mode: # Drop the dragged thumbnail below the other thumbnails so # that you can find the thumbnail beneath it. self._press.set_layer(UNDRAG) i = self._spr_to_thumb(self._press) spr = self._sprites.find_sprite((x, y)) if self._spr_is_thumbnail(spr): self._release = spr # If we found a thumbnail and it is not the one we # dragged, swap their positions. if not self._press == self._release: j = self._spr_to_thumb(self._release) self._thumbs[i][0] = self._release self._thumbs[j][0] = self._press tmp = self._dsobjects[i] self._dsobjects[i] = self._dsobjects[j] self._dsobjects[j] = tmp self._thumbs[j][0].move( (self._thumbs[j][1], self._thumbs[j][2])) self._thumbs[i][0].move((self._thumbs[i][1], self._thumbs[i][2])) self._press.set_layer(TOP) self._press = None self._release = None else: self._next_cb() return False def _set_view_mode(self, i): ''' Switch to slide-viewing mode. ''' self._thumbnail_mode = False self.i = i self._thumb_button.set_icon('thumbs-view') self._thumb_button.set_tooltip(_('Thumbnail view')) def _unit_combo_cb(self, arg=None): ''' Read value of predefined conversion factors from combo box ''' if hasattr(self, '_unit_combo'): active = self._unit_combo.get_active() if active in UNIT_DICTIONARY: self._rate = UNIT_DICTIONARY[active][1]
class Game: def __init__(self, canvas, parent=None, path=None, colors=["#A0FFA0", "#FF8080"]): self._canvas = canvas self._parent = parent self._parent.show_all() self._path = path self._colors = ["#FFFFFF"] self._colors.append(colors[0]) self._colors.append(colors[1]) self._canvas.set_can_focus(True) self._canvas.connect("expose-event", self._expose_cb) self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.connect("button-press-event", self._button_press_cb) self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - (GRID_CELL_SIZE * 1.5) self._scale = self._height / (4 * DOT_SIZE * 1.3) self._dot_size = int(DOT_SIZE * self._scale) self._space = int(self._dot_size / 5.0) self.we_are_sharing = False self._start_time = 0 self._timeout_id = None self._level = 3 self._game = 0 self._correct = 0 # Find the image files self._PATHS = glob.glob(os.path.join(self._path, "images", "*.png")) self._CPATHS = glob.glob(os.path.join(self._path, "color-images", "*.svg")) # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) self._dots = [] self._opts = [] yoffset = int(self._space / 2.0) self._line = Sprite( self._sprites, 0, int(3 * (self._dot_size + self._space) + yoffset / 2.0), self._line(vertical=False) ) for y in range(3): for x in range(6): xoffset = int((self._width - 6 * self._dot_size - 5 * self._space) / 2.0) self._dots.append( Sprite( self._sprites, xoffset + x * (self._dot_size + self._space), y * (self._dot_size + self._space) + yoffset, self._new_dot_surface(color=self._colors[0]), ) ) self._dots[-1].type = -1 # No image self._dots[-1].set_label_attributes(72) y = 3 for x in range(3): self._opts.append( Sprite( self._sprites, xoffset + x * (self._dot_size + self._space), y * (self._dot_size + self._space) + yoffset, self._new_dot_surface(color=self._colors[0]), ) ) self._opts[-1].type = -1 # No image self._opts[-1].set_label_attributes(72) self._opts[-1].hide() def _all_clear(self): """ Things to reinitialize when starting up a new game. """ if self._timeout_id is not None: GObject.source_remove(self._timeout_id) # Auto advance levels if self._correct > 3 and self._level < len(self._dots): self._level += 3 self._correct = 0 self._set_label("") for i in range(3): self._opts[i].hide() self._opts[i].type = -1 self._opts[i].set_label("") for dot in self._dots: dot.type = -1 if self._game == 2 or self._dots.index(dot) < self._level: dot.set_shape(self._new_dot_surface(self._colors[abs(dot.type)])) dot.set_label("?") dot.set_layer(100) else: dot.hide() self._dance_counter = 0 self._dance_step() def _dance_step(self): """ Short animation before loading new game """ if self._game == 2: for i in range(len(self._dots)): self._dots[i].set_shape(self._new_dot_surface(self._colors[int(uniform(0, 3))])) else: for i in range(self._level): self._dots[i].set_shape(self._new_dot_surface(self._colors[int(uniform(0, 3))])) self._dance_counter += 1 if self._dance_counter < 10: self._timeout_id = GObject.timeout_add(500, self._dance_step) else: self._new_game() def new_game(self, game=None, restart=True): """ Start a new game. """ if game is not None: self._game = game self._level = 3 self._correct = 0 if restart: self._all_clear() def _image_in_dots(self, n): for i in range(self._level): if self._dots[i].type == n: return True return False def _image_in_opts(self, n): for i in range(3): if self._opts[i].type == n: return True return False def _choose_random_images(self): """ Choose images at random """ if self._game == 3: maxi = len(self._CPATHS) else: maxi = len(self._PATHS) for i in range(self._level): if self._dots[i].type == -1: n = int(uniform(0, maxi)) while self._image_in_dots(n): n = int(uniform(0, maxi)) self._dots[i].type = n if self._game == 3: self._dots[i].set_shape(self._new_dot_surface(color_image=self._dots[i].type)) else: self._dots[i].set_shape(self._new_dot_surface(image=self._dots[i].type)) self._dots[i].set_layer(100) self._dots[i].set_label("") def _load_image_from_list(self): if self._recall_counter == len(self._recall_list): self._timeout_id = GObject.timeout_add(1000, self._ask_the_question) return for dot in self._dots: dot.type = self._recall_list[self._recall_counter] dot.set_shape(self._new_dot_surface(image=dot.type)) dot.set_layer(100) dot.set_label("") self._recall_counter += 1 self._timeout_id = GObject.timeout_add(1000, self._load_image_from_list) def _find_repeat(self): """ Find an image that repeats """ for i in range(self._level): for j in range(self._level - i - 1): if self._dots[i].type == self._dots[j].type: return i return None def _new_game(self, restore=False): """ Load game images and then ask a question... """ if self._game in [0, 1, 3]: self._choose_random_images() else: # game 2 # generate a random list self._recall_list = [] for i in range(12): n = int(uniform(0, len(self._PATHS))) while n in self._recall_list: n = int(uniform(0, len(self._PATHS))) self._recall_list.append(n) self._recall_counter = 0 self._load_image_from_list() if self._game == 0: if not restore: # Repeat at least one of the images self._repeat = int(uniform(0, self._level)) n = (self._repeat + int(uniform(1, self._level))) % self._level _logger.debug("repeat=%d, n=%d" % (self._repeat, n)) self._dots[self._repeat].set_shape(self._new_dot_surface(image=self._dots[n].type)) self._dots[self._repeat].type = self._dots[n].type else: # Find repeated image, as that is the answer self._repeat = self._find_repeat() if self._repeat is None: _logger.debug("could not find repeat") self._repeat = 0 if self.we_are_sharing: _logger.debug("sending a new game") self._parent.send_new_game() if self._game in [0, 1, 3]: self._timeout_id = GObject.timeout_add(3000, self._ask_the_question) def _ask_the_question(self): """ Each game has a challenge """ self._timeout_id = None # Hide the dots if self._game == 2: for dot in self._dots: dot.hide() else: for i in range(self._level): self._dots[i].hide() if self._game == 0: self._set_label(_("Recall which image was repeated.")) # Show the possible solutions for i in range(3): n = int(uniform(0, len(self._PATHS))) if self._level == 3: while n == self._dots[self._repeat].type or self._image_in_opts(n): n = int(uniform(0, len(self._PATHS))) else: while n == self._dots[self._repeat].type or not self._image_in_dots(n) or self._image_in_opts(n): n = int(uniform(0, len(self._PATHS))) self._opts[i].type = n self._answer = int(uniform(0, 3)) self._opts[self._answer].type = self._dots[self._repeat].type for i in range(3): self._opts[i].set_shape(self._new_dot_surface(image=self._opts[i].type)) self._opts[i].set_layer(100) elif self._game == 1: self._set_label(_("Recall which image was not shown.")) # Show the possible solutions for i in range(3): n = int(uniform(0, len(self._PATHS))) while not self._image_in_dots(n) or self._image_in_opts(n): n = int(uniform(0, len(self._PATHS))) self._opts[i].type = n self._answer = int(uniform(0, 3)) n = int(uniform(0, len(self._PATHS))) while self._image_in_dots(n): n = int(uniform(0, len(self._PATHS))) self._opts[self._answer].type = n for i in range(3): self._opts[i].set_shape(self._new_dot_surface(image=self._opts[i].type)) self._opts[i].set_layer(100) elif self._game == 3: self._set_label(_("Recall which image was not shown.")) # Show the possible solutions for i in range(3): n = int(uniform(0, len(self._CPATHS))) while not self._image_in_dots(n) or self._image_in_opts(n): n = int(uniform(0, len(self._CPATHS))) self._opts[i].type = n self._answer = int(uniform(0, 3)) n = int(uniform(0, len(self._CPATHS))) while self._image_in_dots(n): n = int(uniform(0, len(self._CPATHS))) self._opts[self._answer].type = n for i in range(3): self._opts[i].set_shape(self._new_dot_surface(color_image=self._opts[i].type)) self._opts[i].set_layer(100) elif self._game == 2: self._set_label( ngettext( "Recall which image was displayed %d time ago", "Recall which image was displayed %d times ago", (int(self._level / 3)), ) % (int(self._level / 3)) ) # Show the possible solutions for i in range(3): self._answer = len(self._recall_list) - int(self._level / 3) - 1 n = int(uniform(0, len(self._recall_list))) while n == self._answer: n = int(uniform(0, len(self._recall_list))) self._opts[i].type = n i = int(uniform(0, 3)) self._opts[i].type = self._recall_list[self._answer] for i in range(3): self._opts[i].set_shape(self._new_dot_surface(image=self._opts[i].type)) self._opts[i].set_layer(100) def restore_game(self, dot_list, correct=0, level=3, game=0): """ Restore a game from the Journal or share """ # TODO: Save/restore recall list for game 2 self._correct = correct self._level = level self._game = game for i, dot in enumerate(dot_list): self._dots[i].type = dot if dot == -1: self._dots[i].hide() self._new_game(restore=True) def save_game(self): """ Return dot list for saving to Journal or sharing """ dot_list = [] for dot in self._dots: dot_list.append(dot.type) return dot_list, self._correct, self._level, self._game def _set_label(self, string): """ Set the label in the toolbar or the window frame. """ self._parent.status.set_label(string) def _button_press_cb(self, win, event): if self._timeout_id is not None: _logger.debug("still in timeout... ignoring click") return win.grab_focus() x, y = map(int, event.get_coords()) spr = self._sprites.find_sprite((x, y), inverse=True) if spr == None: return if self._game in [0, 1, 3]: for i in range(3): if self._opts[i] == spr: break self._opts[i].set_shape(self._new_dot_surface(color=self._colors[0])) if i == self._answer: self._opts[i].set_label("☻") self._correct += 1 else: self._opts[i].set_label("☹") self._correct = 0 else: for i in range(3): if self._opts[i] == spr: break self._opts[i].set_shape(self._new_dot_surface(color=self._colors[0])) if self._opts[i].type == self._recall_list[self._answer]: self._opts[i].set_label("☻") self._correct += 1 else: self._opts[i].set_label("☹") self._correct = 0 if self._game in [0, 1, 3]: for i in range(self._level): self._dots[i].set_layer(100) else: for dot in self._dots: dot.set_shape(self._new_dot_surface(image=self._recall_list[self._answer])) dot.set_layer(100) if self._correct == 0: self._timeout_id = GObject.timeout_add(5000, self.new_game) else: self._timeout_id = GObject.timeout_add(3000, self.new_game) return True def remote_button_press(self, dot, color): """ Receive a button press from a sharer """ self._dots[dot].type = color self._dots[dot].set_shape(self._new_dot_surface(color=self._colors[color])) def set_sharing(self, share=True): _logger.debug("enabling sharing") self.we_are_sharing = share def _expose_cb(self, win, event): self.do_expose_event(event) def do_expose_event(self, event): """ Handle the expose-event by drawing """ # Restrict Cairo to the exposed area cr = self._canvas.get_window().cairo_create() cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): Gtk.main_quit() def _new_dot_surface(self, color="#000000", image=None, color_image=None): """ generate a dot of a color color """ self._dot_cache = {} if color_image is not None: if color_image + 10000 in self._dot_cache: return self._dot_cache[color_image + 10000] pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._path, self._CPATHS[color_image]), self._svg_width, self._svg_height ) elif image is not None: if image in self._dot_cache: return self._dot_cache[image] pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._path, self._PATHS[image]), self._svg_width, self._svg_height ) else: if color in self._dot_cache: return self._dot_cache[color] self._stroke = color self._fill = color self._svg_width = self._dot_size self._svg_height = self._dot_size i = self._colors.index(color) pixbuf = svg_str_to_pixbuf( self._header() + self._circle(self._dot_size / 2.0, self._dot_size / 2.0, self._dot_size / 2.0) + self._footer() ) surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self._svg_width, self._svg_height) context = cairo.Context(surface) context = Gdk.CairoContext(context) context.set_source_pixbuf(pixbuf, 0, 0) context.rectangle(0, 0, self._svg_width, self._svg_height) context.fill() if color_image is not None: self._dot_cache[color_image + 10000] = surface elif image is not None: self._dot_cache[image] = surface else: self._dot_cache[color] = surface return surface def _line(self, vertical=True): """ Generate a center line """ if vertical: self._svg_width = 3 self._svg_height = self._height return svg_str_to_pixbuf(self._header() + self._rect(3, self._height, 0, 0) + self._footer()) else: self._svg_width = self._width self._svg_height = 3 return svg_str_to_pixbuf(self._header() + self._rect(self._width, 3, 0, 0) + self._footer()) def _header(self): return ( "<svg\n" + 'xmlns:svg="http://www.w3.org/2000/svg"\n' + 'xmlns="http://www.w3.org/2000/svg"\n' + 'xmlns:xlink="http://www.w3.org/1999/xlink"\n' + 'version="1.1"\n' + 'width="' + str(self._svg_width) + '"\n' + 'height="' + str(self._svg_height) + '">\n' ) def _rect(self, w, h, x, y): svg_string = " <rect\n" svg_string += ' width="%f"\n' % (w) svg_string += ' height="%f"\n' % (h) svg_string += ' rx="%f"\n' % (0) svg_string += ' ry="%f"\n' % (0) svg_string += ' x="%f"\n' % (x) svg_string += ' y="%f"\n' % (y) svg_string += 'style="fill:#000000;stroke:#000000;"/>\n' return svg_string def _circle(self, r, cx, cy): return ( '<circle style="fill:' + str(self._fill) + ";stroke:" + str(self._stroke) + ';" r="' + str(r - 0.5) + '" cx="' + str(cx) + '" cy="' + str(cy) + '" />\n' ) def _footer(self): return "</svg>\n"
class Game(): def __init__(self, canvas, parent=None, colors=['#A0FFA0', '#FF8080']): self._activity = parent self._colors = colors self._canvas = canvas parent.show_all() self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.connect("draw", self.__draw_cb) self._canvas.connect("button-press-event", self._button_press_cb) self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - (GRID_CELL_SIZE * 1.5) self._scale = self._height / (14.0 * DOT_SIZE * 1.2) self._scale_gameover = self._height / (4.0 * DOT_SIZE_GAMEOVER * 1.2) self._dot_size = int(DOT_SIZE * self._scale) self._dot_size_gameover = int(DOT_SIZE_GAMEOVER * self._scale) self._turtle_offset = 0 self._space = int(self._dot_size / 5.) self._space_gameover = int(self._dot_size_gameover / 5.) self._orientation = 0 self.level = 0 self.custom_strategy = None self.strategies = [ BEGINNER_STRATEGY, INTERMEDIATE_STRATEGY, EXPERT_STRATEGY, self.custom_strategy ] self.strategy = self.strategies[self.level] self._timeout_id = None self.best_time = self.load_best_time() # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) self._dots = [] self._gameover = [] self._your_time = [] self._best_time = [] self._win_lose = [] for y in range(THIRTEEN): for x in range(THIRTEEN): offset_x = int((self._width - THIRTEEN * (self._dot_size + \ self._space) - self._space) / 2.) if y % 2 == 1: offset_x += int((self._dot_size + self._space) / 2.) if x == 0 or y == 0 or x == THIRTEEN - 1 or y == THIRTEEN - 1: self._dots.append( Sprite(self._sprites, offset_x + x * (self._dot_size + self._space), y * (self._dot_size + self._space), self._new_dot('#B0B0B0', self._dot_size))) else: self._dots.append( Sprite( self._sprites, offset_x + x * (self._dot_size + self._space), y * (self._dot_size + self._space), self._new_dot(self._colors[FILL], self._dot_size))) self._dots[-1].type = False # not set # Put a turtle at the center of the screen... self._turtle_images = [] self._rotate_turtle(self._new_turtle()) self._turtle = Sprite(self._sprites, 0, 0, self._turtle_images[0]) self._move_turtle(self._dots[int(THIRTEEN * THIRTEEN / 2)].get_xy()) # ...and initialize. self._all_clear() def _move_turtle(self, pos): ''' Move turtle and add its offset ''' self._turtle.move(pos) self._turtle.move_relative( (-self._turtle_offset, -self._turtle_offset)) def _all_clear(self): ''' Things to reinitialize when starting up a new game. ''' # Clear dots for gameover_shape in self._gameover: gameover_shape.hide() for win_lose_shape in self._win_lose: win_lose_shape.hide() for your_time_shape in self._your_time: your_time_shape.hide() for highscore_shape in self._best_time: highscore_shape.hide() for dot in self._dots: if dot.type: dot.type = False dot.set_shape(self._new_dot(self._colors[FILL], self._dot_size)) dot.set_label('') dot.set_layer(100) self._turtle.set_layer(100) # Recenter the turtle self._move_turtle(self._dots[int(THIRTEEN * THIRTEEN / 2)].get_xy()) self._turtle.set_shape(self._turtle_images[0]) self._set_label('') if self._timeout_id is not None: GLib.source_remove(self._timeout_id) self._timeout_id = None def new_game(self, saved_state=None): ''' Start a new game. ''' self.gameover_flag = False self.game_lost = False self._all_clear() # Fill in a few dots to start for i in range(15): n = int(uniform(0, THIRTEEN * THIRTEEN)) if self._dots[n].type is not None: self._dots[n].type = True self._dots[n].set_shape( self._new_dot(self._colors[STROKE], self._dot_size)) # Calculate the distances to the edge self._initialize_weights() self.game_start_time = time.time() self.strategy = self.strategies[self.level] self._timeout_id = None def _set_label(self, string): ''' Set the label in the toolbar or the window frame. ''' self._activity.status.set_label(string) def _button_press_cb(self, win, event): win.grab_focus() x, y = list(map(int, event.get_coords())) spr = self._sprites.find_sprite((x, y), inverse=True) if spr == None: return if spr.type is not None and not spr.type: spr.type = True spr.set_shape(self._new_dot(self._colors[STROKE], self._dot_size)) self._weights[self._dots.index(spr)] = 1000 self._test_game_over(self._move_the_turtle()) return True def _find_the_turtle(self): turtle_pos = self._turtle.get_xy() turtle_dot = None for dot in self._dots: pos = dot.get_xy() # Turtle is offset if pos[0] == turtle_pos[0] + self._turtle_offset and \ pos[1] == turtle_pos[1] + self._turtle_offset: turtle_dot = self._dots.index(dot) break if turtle_dot is None: _logger.debug('Cannot find the turtle...') return None return turtle_dot def _move_the_turtle(self): ''' Move the turtle after each click ''' self._turtle_dot = self._find_the_turtle() if self._turtle_dot is None: return # Given the col and row of the turtle, do something new_dot = self._grid_to_dot( self._my_strategy_import(self.strategy, self._dot_to_grid(self._turtle_dot))) self._move_turtle(self._dots[new_dot].get_xy()) # And set the orientation self._turtle.set_shape(self._turtle_images[self._orientation]) return new_dot def _test_game_over(self, new_dot): ''' Check to see if game is over ''' if new_dot is None: return if self._dots[new_dot].type is None: # Game-over feedback self._once_around = False self.game_stop_time = time.time() self.gameover_flag = True self._happy_turtle_dance() self._timeout_id = GLib.timeout_add(10000, self._game_over) return True c = int(self._turtle_dot / THIRTEEN) % 2 if self._dots[ new_dot + CIRCLE[c][0][0] + THIRTEEN * CIRCLE[c][0][1]].type and \ self._dots[ new_dot + CIRCLE[c][1][0] + THIRTEEN * CIRCLE[c][1][1]].type and \ self._dots[ new_dot + CIRCLE[c][2][0] + THIRTEEN * CIRCLE[c][2][1]].type and \ self._dots[ new_dot + CIRCLE[c][3][0] + THIRTEEN * CIRCLE[c][3][1]].type and \ self._dots[ new_dot + CIRCLE[c][4][0] + THIRTEEN * CIRCLE[c][4][1]].type and \ self._dots[ new_dot + CIRCLE[c][5][0] + THIRTEEN * CIRCLE[c][5][1]].type: # Game-over feedback for dot in self._dots: dot.set_label(':)') self.game_stop_time = time.time() self.gameover_flag = True self._timeout_id = GLib.timeout_add(4000, self._game_over) return True return False def _game_over(self): best_seconds = self.best_time % 60 best_minutes = self.best_time // 60 self.elapsed_time = int(self.game_stop_time - self.game_start_time) second = self.elapsed_time % 60 minute = self.elapsed_time // 60 for dot in self._dots: dot.hide() self._turtle.hide() offset_y = int(self._space_gameover / 4.) offset_x = int((self._width - 6 * self._dot_size_gameover - 5 * self._space_gameover) / 2.) y = 1.5 for x in range(2, 6): self._gameover.append( Sprite( self._sprites, offset_x + (x - 0.50) * self._dot_size_gameover, y * (self._dot_size + self._space) + offset_y, self._new_dot(self._colors[FILL], self._dot_size_gameover))) self._gameover[-1].type = -1 # No image self._gameover[-1].set_label_attributes(72) text = ["☻", " Game ", " Over ", "☻"] self.rings(len(text), text, self._gameover) y = 4.5 for x in range(2, 6): self._win_lose.append( Sprite( self._sprites, offset_x + (x - 0.50) * self._dot_size_gameover, y * (self._dot_size + self._space) + offset_y, self._new_dot(self._colors[FILL], self._dot_size_gameover))) self._win_lose[-1].type = -1 # No image self._win_lose[-1].set_label_attributes(72) text_win_best_time = ["☻", " YOU ", " WON ", "☻"] text_lose = ["☹", " YOU ", " LOST ", "☹"] text_win = ["☻", " GOOD ", " JOB ", "☻"] if self.game_lost: self.rings(len(text_lose), text_lose, self._win_lose) elif self.elapsed_time <= self.best_time: self.rings(len(text_win_best_time), text_win_best_time, self._win_lose) else: self.rings(len(text_win), text_win, self._win_lose) y = 7.5 for x in range(2, 5): self._your_time.append( Sprite( self._sprites, offset_x + x * self._dot_size_gameover, y * (self._dot_size + self._space), self._new_dot(self._colors[FILL], self._dot_size_gameover))) self._your_time[-1].type = -1 # No image self._your_time[-1].set_label_attributes(72) text = [ " your ", " time: ", (' {:02d}:{:02d} '.format(minute, second)) ] self.rings(len(text), text, self._your_time) y = 10.5 for x in range(2, 5): self._best_time.append( Sprite( self._sprites, offset_x + x * self._dot_size_gameover, y * (self._dot_size + self._space), self._new_dot(self._colors[FILL], self._dot_size_gameover))) self._best_time[-1].type = -1 # No image self._best_time[-1].set_label_attributes(72) if self.elapsed_time <= self.best_time and not self.game_lost: self.best_time = self.elapsed_time best_seconds = second best_minutes = minute text = [ " best ", " time: ", (' {:02d}:{:02d} '.format(best_minutes, best_seconds)) ] self.rings(len(text), text, self._best_time) self.save_best_time() self._timeout_id = GLib.timeout_add(7000, self.new_game) def rings(self, num, text, shape): i = 0 for x in range(num): shape[x].type = -1 shape[x].set_shape( self._new_dot(self._colors[FILL], self._dot_size_gameover)) shape[x].set_label(text[i]) shape[x].set_layer(100) i += 1 def _grid_to_dot(self, pos): ''' calculate the dot index from a column and row in the grid ''' return pos[0] + pos[1] * THIRTEEN def _dot_to_grid(self, dot): ''' calculate the grid column and row for a dot ''' return [dot % THIRTEEN, int(dot / THIRTEEN)] def _happy_turtle_dance(self): ''' Turtle dances along the edge ''' self.game_lost = True i = self._find_the_turtle() if i == 0: if self._once_around: return else: self._once_around = True _logger.debug(i) x, y = self._dot_to_grid(i) if y == 0: x += 1 if x == 0: y -= 1 if x == THIRTEEN - 1: y += 1 if y == THIRTEEN - 1: x -= 1 i = self._grid_to_dot((x, y)) self._dots[i].set_label(':)') self._move_turtle(self._dots[i].get_xy()) self._orientation += 1 self._orientation %= 6 self._turtle.set_shape(self._turtle_images[self._orientation]) self._timeout_id = GLib.timeout_add(250, self._happy_turtle_dance) def _ordered_weights(self, pos): ''' Returns the list of surrounding points sorted by their distance to the edge ''' dots = self._surrounding_dots(pos) dots_and_weights = [] for dot in dots: dots_and_weights.append((dot, self._weights[dot])) sorted_dots = sorted(dots_and_weights, key=lambda foo: foo[1]) for i in range(6): dots[i] = sorted_dots[i][0] return dots def _daylight_ahead(self, pos): ''' Returns true if there is a straight path to the edge from the current position/orientation ''' dots = self._surrounding_dots(pos) while True: dot_type = self._dots[dots[self._orientation]].type if dot_type is None: return True elif dot_type: return False else: # keep looking pos = self._dot_to_grid(dots[self._orientation]) dots = self._surrounding_dots(pos) def _surrounding_dots(self, pos): ''' Returns dots surrounding a position in the grid ''' dots = [] evenodd = pos[1] % 2 for i in range(6): col = pos[0] + CIRCLE[evenodd][i][0] row = pos[1] + CIRCLE[evenodd][i][1] dots.append(self._grid_to_dot((col, row))) return dots def _initialize_weights(self): ''' How many steps to an edge? ''' self._weights = [] for d, dot in enumerate(self._dots): if dot.type is None: self._weights.append(0) elif dot.type: self._weights.append(1000) else: pos = self._dot_to_grid(d) pos2 = (THIRTEEN - pos[0], THIRTEEN - pos[1]) self._weights.append( min(min(pos[0], pos2[0]), min(pos[1], pos2[1]))) def _my_strategy_import(self, f, arg): ''' Run Python code passed as argument ''' userdefined = {} try: exec(f, globals(), userdefined) return userdefined['_turtle_strategy'](self, arg) except ZeroDivisionError as e: self._set_label('Python zero-divide error: {}'.format(e)) except ValueError as e: self._set_label('Python value error: {}'.format(e)) except SyntaxError as e: self._set_label('Python syntax error: {}'.format(e)) except NameError as e: self._set_label('Python name error: {}'.format(e)) except OverflowError as e: self._set_label('Python overflow error: {}'.format(e)) except TypeError as e: self._set_label('Python type error: {}'.format(e)) except: self._set_label('Python error') traceback.print_exc() return None def __draw_cb(self, canvas, cr): self._sprites.redraw_sprites(cr=cr) def do_expose_event(self, event): ''' Handle the expose-event by drawing ''' # Restrict Cairo to the exposed area cr = self._canvas.window.cairo_create() cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): Gtk.main_quit() def _new_dot(self, color, dot_size): ''' generate a dot of a color color ''' self._stroke = color self._fill = color self._svg_width = dot_size self._svg_height = dot_size return svg_str_to_pixbuf( self._header() + \ self._circle(dot_size / 2., dot_size / 2., dot_size / 2.) + \ self._footer()) def _new_turtle(self): ''' generate a turtle ''' self._svg_width = self._dot_size * 2 self._svg_height = self._dot_size * 2 self._stroke = '#101010' self._fill = '#404040' return svg_str_to_pixbuf( self._header() + \ self._turtle() + \ self._footer()) def _rotate_turtle(self, image): w, h = image.get_width(), image.get_height() nw = nh = int(sqrt(w * w + h * h)) for i in range(6): surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, nw, nh) context = cairo.Context(surface) context.translate(w / 2., h / 2.) context.rotate((30 + i * 60) * pi / 180.) context.translate(-w / 2., -h / 2.) Gdk.cairo_set_source_pixbuf(context, image, 0, 0) context.rectangle(0, 0, nw, nh) context.fill() self._turtle_images.append(surface) self._turtle_offset = int(self._dot_size / 2.) def _header(self): return '<svg\n' + 'xmlns:svg="http://www.w3.org/2000/svg"\n' + \ 'xmlns="http://www.w3.org/2000/svg"\n' + \ 'xmlns:xlink="http://www.w3.org/1999/xlink"\n' + \ 'version="1.1"\n' + 'width="' + str(self._svg_width) + '"\n' + \ 'height="' + str(self._svg_height) + '">\n' def _circle(self, r, cx, cy): return '<circle style="fill:' + str(self._fill) + ';stroke:' + \ str(self._stroke) + ';" r="' + str(r - 0.5) + '" cx="' + \ str(cx) + '" cy="' + str(cy) + '" />\n' def _footer(self): return '</svg>\n' def _turtle(self): svg = '<g\ntransform="scale(%.1f, %.1f)">\n' % (self._svg_width / 60., self._svg_height / 60.) svg += '%s%s%s%s%s%s%s%s' % ( ' <path d="M 27.5 48.3 ', 'C 26.9 48.3 26.4 48.2 25.9 48.2 L 27.2 50.5 L 28.6 48.2 ', 'C 28.2 48.2 27.9 48.3 27.5 48.3 Z" stroke_width="3.5" ', 'fill="', self._fill, ';" stroke="', self._stroke, '" />\n') svg += '%s%s%s%s%s%s%s%s%s%s' % ( ' <path d="M 40.2 11.7 ', 'C 38.0 11.7 36.2 13.3 35.8 15.3 ', 'C 37.7 16.7 39.3 18.4 40.5 20.5 ', 'C 42.8 20.4 44.6 18.5 44.6 16.2 ', 'C 44.6 13.7 42.6 11.7 40.2 11.7 Z" stroke_width="3.5" ', 'fill="', self._fill, ';" stroke="', self._stroke, '" />\n') svg += '%s%s%s%s%s%s%s%s%s%s' % ( ' <path d="M 40.7 39.9 ', 'C 39.5 42.1 37.9 44.0 35.9 45.4 ', 'C 36.4 47.3 38.1 48.7 40.2 48.7 ', 'C 42.6 48.7 44.6 46.7 44.6 44.3 ', 'C 44.6 42.0 42.9 40.2 40.7 39.9 Z" stroke_width="3.5" ', 'fill="', self._fill, ';" stroke="', self._stroke, '" />\n') svg += '%s%s%s%s%s%s%s%s%s%s' % ( ' <path d="M 14.3 39.9 ', 'C 12.0 40.1 10.2 42.0 10.2 44.3 ', 'C 10.2 46.7 12.2 48.7 14.7 48.7 ', 'C 16.7 48.7 18.5 47.3 18.9 45.4 ', 'C 17.1 43.9 15.5 42.1 14.3 39.9 Z" stroke_width="3.5" ', 'fill="', self._fill, ';" stroke="', self._stroke, '" />\n') svg += '%s%s%s%s%s%s%s%s%s%s' % ( ' <path d="M 19.0 15.4 ', 'C 18.7 13.3 16.9 11.7 14.7 11.7 ', 'C 12.2 11.7 10.2 13.7 10.2 16.2 ', 'C 10.2 18.5 12.1 20.5 14.5 20.6 ', 'C 15.7 18.5 17.2 16.8 19.0 15.4 Z" stroke_width="3.5" ', 'fill="', self._fill, ';" stroke="', self._stroke, '" />\n') svg += '%s%s%s%s%s%s%s%s%s%s%s%s' % ( ' <path d="M 27.5 12.6 ', 'C 29.4 12.6 31.2 13.0 32.9 13.7 ', 'C 33.7 12.6 34.1 11.3 34.1 9.9 ', 'C 34.1 6.2 31.1 3.2 27.4 3.2 ', 'C 23.7 3.2 20.7 6.2 20.7 9.9 ', 'C 20.7 11.3 21.2 12.7 22.0 13.7 ', 'C 23.7 13.0 25.5 12.6 27.5 12.6 Z" stroke_width="3.5" ', 'fill="', self._fill, ';" stroke="', self._stroke, '" />\n') svg += '%s%s%s%s%s%s%s%s%s%s%s%s' % ( ' <path d="M 43.1 30.4 ', 'C 43.1 35.2 41.5 39.7 38.5 43.0 ', 'C 35.6 46.4 31.6 48.3 27.5 48.3 ', 'C 23.4 48.3 19.4 46.4 16.5 43.0 ', 'C 13.5 39.7 11.9 35.2 11.9 30.4 ', 'C 11.9 20.6 18.9 12.6 27.5 12.6 ', 'C 36.1 12.6 43.1 20.6 43.1 30.4 Z" stroke_width="3.5" ', 'fill="', self._fill, ';" stroke="', self._stroke, '" />\n') svg += '%s%s%s%s%s' % ( ' <path d="M 25.9 33.8 L 24.3 29.1 ', 'L 27.5 26.5 L 31.1 29.2 L 29.6 33.8 Z" stroke_width="3.5" ', 'fill="', self._stroke, ';" stroke="none" />\n') svg += '%s%s%s%s%s%s' % ( ' <path d="M 27.5 41.6 ', 'C 23.5 41.4 22.0 39.5 22.0 39.5 L 25.5 35.4 L 30.0 35.5 ', 'L 33.1 39.7 C 33.1 39.7 30.2 41.7 27.5 41.6 Z" ', 'stroke_width="3.5" fill="', self._stroke, ';" stroke="none" />\n') svg += '%s%s%s%s%s%s' % ( ' <path d="M 18.5 33.8 ', 'C 17.6 30.9 18.6 27.0 18.6 27.0 L 22.6 29.1 L 24.1 33.8 ', 'L 20.5 38.0 C 20.5 38.0 19.1 36.0 18.4 33.8 Z" ', 'stroke_width="3.5" fill="', self._stroke, ';" stroke="none" />\n') svg += '%s%s%s%s%s%s' % ( ' <path d="M 19.5 25.1 ', 'C 19.5 25.1 20.0 23.2 22.5 21.3 ', 'C 24.7 19.7 27.0 19.6 27.0 19.6 L 26.9 24.6 L 23.4 27.3 ', 'L 19.5 25.1 Z" stroke_width="3.5" fill="', self._stroke, ';" stroke="none" />\n') svg += '%s%s%s%s%s%s' % ( ' <path d="M 32.1 27.8 L 28.6 25.0 ', 'L 29 19.8 C 29 19.8 30.8 19.7 33.0 21.4 ', 'C 35.2 23.2 36.3 26.4 36.3 26.4 L 32.1 27.8 Z" ', 'stroke_width="3.5" fill="', self._stroke, ';" stroke="none" />\n') svg += '%s%s%s%s%s%s' % ( ' <path d="M 31.3 34.0 L 32.6 29.6 ', 'L 36.8 28.0 C 36.8 28.0 37.5 30.7 36.8 33.7 ', 'C 36.2 36.0 34.7 38.1 34.7 38.1 L 31.3 34.0 Z" ', 'stroke_width="3.5" fill="', self._stroke, ';" stroke="none" />\n') svg += '</g>\n' return svg def save_best_time(self): file_path = os.path.join(get_activity_root(), 'data', 'best-time') best_time = [180] if os.path.exists(file_path): with open(file_path, "r") as fp: best_time = fp.readlines() int_best_time = int(best_time[0]) if not int_best_time <= self.elapsed_time and not self.game_lost: int_best_time = self.elapsed_time with open(file_path, "w") as fp: fp.write(str(int_best_time)) def load_best_time(self): file_path = os.path.join(get_activity_root(), 'data', 'best-time') if os.path.exists(file_path): with open(file_path, "r") as fp: highscore = fp.readlines() try: return int(highscore[0]) except (ValueError, IndexError) as e: logging.exception(e) return 0 return 0
class Game(): def __init__(self, canvas, parent=None, colors=['#A0FFA0', '#FF8080']): self._activity = parent self._colors = colors self._canvas = canvas parent.show_all() self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.connect("draw", self.__draw_cb) self._canvas.connect("button-press-event", self._button_press_cb) self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - (GRID_CELL_SIZE * 1.5) self._scale = self._height / (14.0 * DOT_SIZE * 1.2) self._dot_size = int(DOT_SIZE * self._scale) self._turtle_offset = 0 self._space = int(self._dot_size / 5.) self._orientation = 0 self.level = 0 self.custom_strategy = None self.strategies = [ BEGINNER_STRATEGY, INTERMEDIATE_STRATEGY, EXPERT_STRATEGY, self.custom_strategy ] self.strategy = self.strategies[self.level] self._timeout_id = None # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) self._dots = [] for y in range(THIRTEEN): for x in range(THIRTEEN): xoffset = int((self._width - THIRTEEN * (self._dot_size + \ self._space) - self._space) / 2.) if y % 2 == 1: xoffset += int((self._dot_size + self._space) / 2.) if x == 0 or y == 0 or x == THIRTEEN - 1 or y == THIRTEEN - 1: self._dots.append( Sprite(self._sprites, xoffset + x * (self._dot_size + self._space), y * (self._dot_size + self._space), self._new_dot('#B0B0B0'))) else: self._dots.append( Sprite(self._sprites, xoffset + x * (self._dot_size + self._space), y * (self._dot_size + self._space), self._new_dot(self._colors[FILL]))) self._dots[-1].type = False # not set # Put a turtle at the center of the screen... self._turtle_images = [] self._rotate_turtle(self._new_turtle()) self._turtle = Sprite(self._sprites, 0, 0, self._turtle_images[0]) self._move_turtle(self._dots[int(THIRTEEN * THIRTEEN / 2)].get_xy()) # ...and initialize. self._all_clear() def _move_turtle(self, pos): ''' Move turtle and add its offset ''' self._turtle.move(pos) self._turtle.move_relative( (-self._turtle_offset, -self._turtle_offset)) def _all_clear(self): ''' Things to reinitialize when starting up a new game. ''' # Clear dots for dot in self._dots: if dot.type: dot.type = False dot.set_shape(self._new_dot(self._colors[FILL])) dot.set_label('') # Recenter the turtle self._move_turtle(self._dots[int(THIRTEEN * THIRTEEN / 2)].get_xy()) self._turtle.set_shape(self._turtle_images[0]) self._set_label('') if self._timeout_id is not None: GObject.source_remove(self._timeout_id) def new_game(self, saved_state=None): ''' Start a new game. ''' self._all_clear() # Fill in a few dots to start for i in range(15): n = int(uniform(0, THIRTEEN * THIRTEEN)) if self._dots[n].type is not None: self._dots[n].type = True self._dots[n].set_shape(self._new_dot(self._colors[STROKE])) # Calculate the distances to the edge self._initialize_weights() self.strategy = self.strategies[self.level] def _set_label(self, string): ''' Set the label in the toolbar or the window frame. ''' self._activity.status.set_label(string) def _button_press_cb(self, win, event): win.grab_focus() x, y = map(int, event.get_coords()) spr = self._sprites.find_sprite((x, y), inverse=True) if spr == None: return if spr.type is not None and not spr.type: spr.type = True spr.set_shape(self._new_dot(self._colors[STROKE])) self._weights[self._dots.index(spr)] = 1000 self._test_game_over(self._move_the_turtle()) return True def _find_the_turtle(self): turtle_pos = self._turtle.get_xy() turtle_dot = None for dot in self._dots: pos = dot.get_xy() # Turtle is offset if pos[0] == turtle_pos[0] + self._turtle_offset and \ pos[1] == turtle_pos[1] + self._turtle_offset: turtle_dot = self._dots.index(dot) break if turtle_dot is None: _logger.debug('Cannot find the turtle...') return None return turtle_dot def _move_the_turtle(self): ''' Move the turtle after each click ''' self._turtle_dot = self._find_the_turtle() if self._turtle_dot is None: return # Given the col and row of the turtle, do something new_dot = self._grid_to_dot( self._my_strategy_import(self.strategy, self._dot_to_grid(self._turtle_dot))) self._move_turtle(self._dots[new_dot].get_xy()) # And set the orientation self._turtle.set_shape(self._turtle_images[self._orientation]) return new_dot def _test_game_over(self, new_dot): ''' Check to see if game is over ''' if new_dot is None: return if self._dots[new_dot].type is None: # Game-over feedback self._once_around = False self._happy_turtle_dance() return True c = int(self._turtle_dot / THIRTEEN) % 2 if self._dots[ new_dot + CIRCLE[c][0][0] + THIRTEEN * CIRCLE[c][0][1]].type and \ self._dots[ new_dot + CIRCLE[c][1][0] + THIRTEEN * CIRCLE[c][1][1]].type and \ self._dots[ new_dot + CIRCLE[c][2][0] + THIRTEEN * CIRCLE[c][2][1]].type and \ self._dots[ new_dot + CIRCLE[c][3][0] + THIRTEEN * CIRCLE[c][3][1]].type and \ self._dots[ new_dot + CIRCLE[c][4][0] + THIRTEEN * CIRCLE[c][4][1]].type and \ self._dots[ new_dot + CIRCLE[c][5][0] + THIRTEEN * CIRCLE[c][5][1]].type: # Game-over feedback for dot in self._dots: dot.set_label(':)') return True return False def _grid_to_dot(self, pos): ''' calculate the dot index from a column and row in the grid ''' return pos[0] + pos[1] * THIRTEEN def _dot_to_grid(self, dot): ''' calculate the grid column and row for a dot ''' return [dot % THIRTEEN, int(dot / THIRTEEN)] def _happy_turtle_dance(self): ''' Turtle dances along the edge ''' i = self._find_the_turtle() if i == 0: if self._once_around: return else: self._once_around = True _logger.debug(i) x, y = self._dot_to_grid(i) if y == 0: x += 1 if x == 0: y -= 1 if x == THIRTEEN - 1: y += 1 if y == THIRTEEN - 1: x -= 1 i = self._grid_to_dot((x, y)) self._dots[i].set_label(':)') self._move_turtle(self._dots[i].get_xy()) self._orientation += 1 self._orientation %= 6 self._turtle.set_shape(self._turtle_images[self._orientation]) self._timeout_id = GObject.timeout_add(250, self._happy_turtle_dance) def _ordered_weights(self, pos): ''' Returns the list of surrounding points sorted by their distance to the edge ''' dots = self._surrounding_dots(pos) dots_and_weights = [] for dot in dots: dots_and_weights.append((dot, self._weights[dot])) sorted_dots = sorted(dots_and_weights, key=lambda foo: foo[1]) for i in range(6): dots[i] = sorted_dots[i][0] return dots def _daylight_ahead(self, pos): ''' Returns true if there is a straight path to the edge from the current position/orientation ''' dots = self._surrounding_dots(pos) while True: dot_type = self._dots[dots[self._orientation]].type if dot_type is None: return True elif dot_type: return False else: # keep looking pos = self._dot_to_grid(dots[self._orientation]) dots = self._surrounding_dots(pos) def _surrounding_dots(self, pos): ''' Returns dots surrounding a position in the grid ''' dots = [] evenodd = pos[1] % 2 for i in range(6): col = pos[0] + CIRCLE[evenodd][i][0] row = pos[1] + CIRCLE[evenodd][i][1] dots.append(self._grid_to_dot((col, row))) return dots def _initialize_weights(self): ''' How many steps to an edge? ''' self._weights = [] for d, dot in enumerate(self._dots): if dot.type is None: self._weights.append(0) elif dot.type: self._weights.append(1000) else: pos = self._dot_to_grid(d) pos2 = (THIRTEEN - pos[0], THIRTEEN - pos[1]) self._weights.append( min(min(pos[0], pos2[0]), min(pos[1], pos2[1]))) def _my_strategy_import(self, f, arg): ''' Run Python code passed as argument ''' userdefined = {} try: exec f in globals(), userdefined return userdefined['_turtle_strategy'](self, arg) except ZeroDivisionError, e: self._set_label('Python zero-divide error: %s' % (str(e))) except ValueError, e: self._set_label('Python value error: %s' % (str(e)))
class Game(): def __init__(self, canvas, parent=None, colors=['#A0FFA0', '#FF8080']): self._activity = parent self.colors = colors # Starting from command line if parent is None: self._running_sugar = False self._canvas = canvas else: self._running_sugar = True self._canvas = canvas parent.show_all() self._canvas.set_flags(gtk.CAN_FOCUS) self._canvas.add_events(gtk.gdk.BUTTON_PRESS_MASK) self._canvas.add_events(gtk.gdk.BUTTON_RELEASE_MASK) self._canvas.add_events(gtk.gdk.POINTER_MOTION_MASK) self._canvas.connect("expose-event", self._expose_cb) self._canvas.connect("button-press-event", self._button_press_cb) self._canvas.connect("button-release-event", self._button_release_cb) self._canvas.connect("motion-notify-event", self._mouse_move_cb) self._canvas.connect("key_press_event", self._keypress_cb) self._width = gtk.gdk.screen_width() self._height = gtk.gdk.screen_height() - (GRID_CELL_SIZE * 1.5) self._scale = self._height / (8.0 * TILE_HEIGHT) self.tile_width = TILE_WIDTH * self._scale self.tile_height = TILE_HEIGHT * self._scale # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) self.grid = Grid(self._sprites, self._width, self._height, self.tile_width, self.tile_height, self._scale, colors[0]) self.deck = Deck(self._sprites, self._scale, colors[1]) self.deck.board.move((self.grid.left, self.grid.top)) self.hands = [] self.hands.append(Hand(self.tile_width, self.tile_height)) self._errormsg = [] for i in range(4): self._errormsg.append(error_graphic(self._sprites)) self._highlight = highlight_graphic(self._sprites, self._scale) self._score_card = blank_tile(self._sprites, scale=self._scale * 2, color=colors[1]) self._score_card.set_label_attributes(64) self._score_card.move(((int(self._width / 2) - self.tile_width), int(self._height / 2) - self.tile_height)) # and initialize a few variables we'll need. self.buddies = [] self._my_hand = MY_HAND self.playing_with_robot = False self._all_clear() def _all_clear(self): ''' Things to reinitialize when starting up a new game. ''' self._hide_highlight() self._hide_errormsgs() self.deck.hide() self.deck.clear() self.grid.clear() for hand in self.hands: hand.clear() self.show_connected_tiles() self._press = None self._release = None self._dragpos = [0, 0] self._total_drag = [0, 0] self.last_spr_moved = None self._last_tile_played = None self._last_tile_orientation = 0 self._last_grid_played = None self.whos_turn = MY_HAND self._waiting_for_my_turn = False self._waiting_for_robot = False self.placed_a_tile = False self._there_are_errors = False self.score = 0 self._score_card.set_layer(HIDE) self._score_card.move(((int(self._width / 2) - self.tile_width), int(self._height / 2) - self.tile_height)) self.saw_game_over = False def _initiating(self): if not self._running_sugar: return True return self._activity.initiating def new_game(self, saved_state=None, deck_index=0): ''' Start a new game. ''' self._all_clear() # If we are not sharing or we are the sharer... if not self.we_are_sharing() or self._initiating(): # Let joiners know we are starting a new game... if self.we_are_sharing(): self._activity.send_event('n| ') # The initiator shuffles the deck... self.deck.shuffle() # ...and shares it. if self.we_are_sharing(): self._activity.send_event('d|%s' % (self.deck.serialize())) # Deal a hand to yourself... self.hands[self._my_hand].deal(self.deck) # ...deal a hand to the robot... if self.playing_with_robot: if len(self.hands) < ROBOT_HAND + 1: self.hands.append(Hand(self.tile_width, self.tile_height, remote=True)) self.hands[ROBOT_HAND].deal(self.deck) # ...or deal hands to the joiners. elif len(self.buddies) > 1: for i, buddy in enumerate(self.buddies): if buddy != self._activity.nick: self.hands.append(Hand( self.tile_width, self.tile_height, remote=True)) self.hands[i].deal(self.deck) self._activity.send_event('h|%s' % \ (self.hands[i].serialize(buddy=buddy))) # As initiator, you take the first turn. self.its_my_turn() # If we are joining, we need to wait for a hand. else: self._my_hand = self.buddies.index(self._activity.nick) self.its_their_turn(self.buddies[1]) # Sharer will be buddy 1 def we_are_sharing(self): ''' If we are sharing, there is more than one buddy. ''' if len(self.buddies) > 1: return True def _set_label(self, string): ''' Set the label in the toolbar or the window frame. ''' if self._running_sugar: self._activity.status.set_label(string) self._activity.score.set_label(_('Score: ') + str(self.score)) elif hasattr(self, 'win'): self.win.set_title('%s: %s [%d]' % (_('Paths'), string, self.score)) def its_my_turn(self): # I need to play a piece... self.placed_a_tile = False # and I am no longer waiting for my turn. self._waiting_for_my_turn = False # If I don't have any tiles left, time to redeal. if self.hands[self._my_hand].tiles_in_hand() == 0: self._redeal() if self._running_sugar: self._activity.set_player_on_toolbar(self._activity.nick) self._activity.dialog_button.set_icon('go-next') self._activity.dialog_button.set_tooltip( _('Click after taking your turn.')) self._set_label(_('It is your turn.')) def _redeal(self): # Only the sharer deals tiles. if not self.we_are_sharing(): self.hands[self._my_hand].deal(self.deck) if self.playing_with_robot: self.hands[ROBOT_HAND].deal(self.deck) if self.hands[self._my_hand].tiles_in_hand() == 0: if self._running_sugar: self._activity.dialog_button.set_icon( 'media-playback-stop-insensitive') self._activity.dialog_button.set_tooltip(_('Game over')) self.game_over() elif self._initiating(): if self.deck.empty(): self.game_over() return if self.deck.tiles_remaining() < COL * len(self.buddies): number_of_tiles_to_deal = \ int(self.deck.tiles_remaining() / len(self.buddies)) if number_of_tiles_to_deal == 0: number_of_tiles_to_deal = 1 # Deal last tile in deck. else: number_of_tiles_to_deal = COL for i, nick in enumerate(self.buddies): self.hands[i].deal(self.deck, number_of_tiles_to_deal) # Send the joiners their new hands. if nick != self._activity.nick: self._activity.send_event('h|%s' % \ (self.hands[i].serialize(buddy=nick))) def took_my_turn(self): # Did I complete my turn without any errors? if self._there_are_errors: self._set_label(_('There are errors—it is still your turn.')) return # After the tile is placed, expand regions of playable grid squares. self.show_connected_tiles() # Are there any completed paths? self._test_for_complete_paths(self._last_grid_played) # If so, let everyone know what piece I moved. if self.we_are_sharing(): self._activity.send_event('p|%s' % \ (json_dump([self._last_tile_played, self._last_tile_orientation, self._last_grid_played]))) self._last_tile_orientation = 0 # Reset orientation. # I took my turn, so I am waiting again. self._waiting_for_my_turn = True if self.last_spr_moved is not None: self.last_spr_moved.set_layer(TILES) self.last_spr_moved = None self._hide_highlight() self._set_label(_('You took your turn.')) if self.playing_with_robot: self.its_their_turn(_('robot')) self._waiting_for_robot = True gobject.timeout_add(1000, self._robot_turn) elif not self.we_are_sharing(): if self.deck.empty() and \ self.hands[self._my_hand].tiles_in_hand() == 0: self.game_over() else: self.its_my_turn() elif self._initiating(): self.whos_turn += 1 if self.whos_turn == len(self.buddies): self.whos_turn = 0 else: self.its_their_turn(self.buddies[self.whos_turn]) self._activity.send_event('t|%s' % ( self.buddies[self.whos_turn])) def _robot_turn(self): self._robot_play() self.show_connected_tiles() if not self._waiting_for_robot: self.its_my_turn() def its_their_turn(self, nick): # It is someone else's turn. if self._running_sugar: if not self.playing_with_robot: self._activity.set_player_on_toolbar(nick) self._activity.dialog_button.set_icon('media-playback-stop') self._activity.dialog_button.set_tooltip(_('Wait your turn.')) self._set_label(_('Waiting for') + ' ' + nick) self._waiting_for_my_turn = True # I am still waiting. def _button_press_cb(self, win, event): win.grab_focus() x, y = map(int, event.get_coords()) self._dragpos = [x, y] self._total_drag = [0, 0] spr = self._sprites.find_sprite((x, y)) # If it is not my turn, do nothing. if self._waiting_for_my_turn: self._press = None return self._release = None # Ignore clicks on background except to indicate you took your turn if spr is None or spr in self.grid.blanks or spr == self.deck.board: if self.placed_a_tile and spr is None: self.took_my_turn() self._press = None return True # Are we clicking on a tile in the hand? if self.hands[self._my_hand].spr_to_hand(spr) is not None and \ not self._there_are_errors: self.last_spr_moved = spr clicked_in_hand = True if self.placed_a_tile: self._press = None self.took_my_turn() else: clicked_in_hand = False # We cannot switch to an old tile. if spr == self.last_spr_moved: self._press = spr spr.set_layer(TOP) self._show_highlight() return True def _mouse_move_cb(self, win, event): """ Drag a tile with the mouse. """ spr = self._press if spr is None: self._dragpos = [0, 0] return True win.grab_focus() x, y = map(int, event.get_coords()) dx = x - self._dragpos[0] dy = y - self._dragpos[1] spr.move_relative([dx, dy]) self._move_relative_highlight([dx, dy]) self._dragpos = [x, y] self._total_drag[0] += dx self._total_drag[1] += dy def _button_release_cb(self, win, event): win.grab_focus() self._dragpos = [0, 0] if self._waiting_for_my_turn: return if self._press is None: return x, y = map(int, event.get_coords()) spr = self._sprites.find_sprite((x, y)) self._release = spr grid_pos = self.grid.xy_to_grid(x, y) hand_pos = self.hands[self._my_hand].xy_to_hand(x, y) # Placing tile in grid if grid_pos is not None and self._it_is_a_drag() and \ self.grid.blanks[grid_pos].get_layer() > HIDE: # Moving to an empty grid position if self.grid.grid[grid_pos] is None: tile = self.deck.spr_to_tile(self._press) tile.spr.move(self.grid.grid_to_xy(grid_pos)) # If the tile was previously in the grid, empty its old pos. i = self.grid.spr_to_grid(self._press) if i is not None: self.grid.grid[i] = None # Assign the tile to the new grid position. self.grid.grid[grid_pos] = tile self.placed_a_tile = True self._last_tile_played = tile.number self._last_grid_played = grid_pos # If the tile came from the hand, empty its old position. i = self.hands[self._my_hand].spr_to_hand(self._press) if i is not None: self.hands[self._my_hand].hand[i] = None # Remember which tile moved. if self.last_spr_moved != tile.spr: self.last_spr_moved = tile.spr self._show_highlight() # Returning tile to hand elif hand_pos is not None: # Make sure there is somewhere to place the tile. empty = self.hands[self._my_hand].find_empty_slot() if empty is not None: tile = self.deck.spr_to_tile(self._press) tile.spr.move(self.hands[self._my_hand].hand_to_xy(empty)) # Did the tile come from elsewhere in the hand? if self.hands[self._my_hand].spr_to_hand( self._press) is not None: self.hands[self._my_hand].hand[self.hands[ self._my_hand].spr_to_hand(self._press)] = None # or from the grid? elif self.grid.spr_to_grid(self._press) is not None: self.grid.grid[self.grid.spr_to_grid(self._press)] = None self.hands[self._my_hand].hand[empty] = tile # Remember which tile moved. if spr == self.last_spr_moved: self.last_spr_moved = None self._hide_errormsgs() self._there_are_errors = False else: # Or return tile to the grid grid_pos = self.grid.spr_to_grid(self._press) if grid_pos is not None: tile = self.deck.spr_to_tile(self._press) tile.spr.move(self.grid.grid_to_xy(grid_pos)) self._hide_highlight() self._press = None self._release = None self.placed_a_tile = False return True # Rotate elif self._press == self._release and not self._it_is_a_drag(): tile = self.deck.spr_to_tile(spr) tile.rotate_clockwise() self._last_tile_orientation = tile.orientation # Remember which tile moved. if self.last_spr_moved != tile.spr: self.last_spr_moved = tile.spr self._show_highlight() # In limbo: return to grid if hand_pos is None and x < self.grid.left: grid_pos = self.grid.spr_to_grid(self._press) if grid_pos is not None: tile = self.deck.spr_to_tile(self._press) tile.spr.move(self.grid.grid_to_xy(grid_pos)) self._hide_highlight() self._snap_to_grid(self._release) self._test_for_bad_paths(self.grid.spr_to_grid(self._press)) self._press = None self._release = None return True def _snap_to_grid(self, spr): ''' make sure a tile is aligned in its grid position ''' for i in range(COL * ROW): if self.grid.grid[i] is not None: self.grid.grid[i].spr.move(self.grid.grid_to_xy(i)) if self.grid.grid[i].spr == spr: self._move_highlight(self.grid.grid_to_xy(i)) def _it_is_a_drag(self): ''' The movement was large enough to be consider a drag as opposed to a tile rotate. ''' if self._total_drag[0] * self._total_drag[0] + \ self._total_drag[1] * self._total_drag[1] > \ self.tile_width * self.tile_height: return True return False def _shuffle_up(self, hand): ''' Shuffle all the tiles in a hand to the top. ''' for i, tile in enumerate(self.hands[hand].hand): empty = self.hands[hand].find_empty_slot() if i > 0 and tile is not None and empty is not None: tile.spr.move(self.hands[hand].hand_to_xy(empty)) self.hands[hand].hand[empty] = tile self.hands[hand].hand[i] = None def game_over(self, msg=_('Game over')): ''' Nothing left to do except show the results. ''' self._set_label(msg) self.saw_game_over = True if self.hands[self._my_hand].tiles_in_hand() == 0: self.score += 50 # Bonus points else: for tile in self.hands[self._my_hand].hand: if tile is not None: self.score -= 2 * tile.get_value() # Penalty self._shuffle_up(self._my_hand) if self._running_sugar: self._activity.score.set_label(_('Score: ') + str(self.score)) self._score_card.set_label(str(self.score)) self._score_card.set_layer(OVER_THE_TOP) self._score_card.move((int(self.tile_width / 2), int(self._height / 2) + 2 * self.tile_height)) if self.playing_with_robot: self._shuffle_up(ROBOT_HAND) for tile in range(COL): if self.hands[ROBOT_HAND].hand[tile] is not None: x, y = self.hands[ROBOT_HAND].hand_to_xy(tile) self.hands[ROBOT_HAND].hand[tile].spr.move( (self.grid.left_hand + self.grid.xinc, y)) if self._running_sugar: self._activity.set_robot_status(False, 'robot-off') elif self.we_are_sharing(): self._activity.send_event('g| ') def show_connected_tiles(self): ''' Highlight the squares that surround the tiles already on the grid. ''' for i in range(ROW * COL): if self._connected(i): self.grid.blanks[i].set_layer(GRID) else: self.grid.blanks[i].set_layer(HIDE) def _connected(self, tile): ''' Does tile abut the path? ''' if self.grid.grid.count(None) == ROW * COL: return True if self.grid.grid[tile] is not None: # already has a tile return False # Looking north if tile >= COL and self.grid.grid[tile + OFFSETS[0]] is not None: return True # Looking east if tile % ROW < ROW - 1 and \ self.grid.grid[tile + OFFSETS[1]] is not None: return True # Looking south if tile < (ROW - 1) * COL and \ self.grid.grid[tile + OFFSETS[2]] is not None: return True # Looking west if tile % ROW > 0 and self.grid.grid[tile + OFFSETS[3]] is not None: return True return False def give_a_hint(self): ''' Try to find an open place on the grid for any tile in my_hand. ''' order = self.deck.random_order(ROW * COL) for i in range(ROW * COL): if self._connected(order[i]): for tile in self.hands[self._my_hand].hand: if self._try_placement(tile, order[i]): # Success, so give hint. self.grid.grid[order[i]] = None self._show_highlight( pos=self.grid.grid_to_xy(order[i])) return # Nowhere to play. self.game_over(_('Nowhere to play.')) def _robot_play(self): ''' The robot tries random tiles in random locations. ''' # TODO: strategy try to complete paths order = self.deck.random_order(ROW * COL) for i in range(ROW * COL): if self._connected(order[i]): for tile in self.hands[ROBOT_HAND].hand: if self._try_placement(tile, order[i]): # Success, so remove tile from hand. self.hands[ROBOT_HAND].hand[ self.hands[ROBOT_HAND].hand.index(tile)] = None tile.spr.move(self.grid.grid_to_xy(order[i])) tile.spr.set_layer(TILES) self._waiting_for_robot = False return # If we didn't return above, we were unable to play a tile. self.game_over(_('Robot unable to play')) def _try_placement(self, tile, i): ''' Try to place a tile at grid posiion i. Rotate it, if necessary. ''' if tile is None: return False self.grid.grid[i] = tile for j in range(4): self._test_for_bad_paths(i) if not self._there_are_errors: return True tile.rotate_clockwise() self.grid.grid[i] = None return False def _test_for_complete_paths(self, tile): ''' Did this tile complete a path? (or two paths?) ''' # A tile can complete up to two paths. self._paths = [[], []] break_in_path = [False, False] # Seed the paths and lists with the current tile. if tile is not None: self._add_to_path_list(tile, 0, 0) if len(self.grid.grid[tile].paths) == 2: self._add_to_path_list(tile, 1, 1) # Walk the path. for p in range(2): tile, path = self._tile_to_test(p) while(tile is not None): self._test(tile, path, p, self._test_a_neighbor) self._tile_has_been_tested(tile, path, p) tile, path = self._tile_to_test(p) # Is the path complete? for i in self._paths[p]: if not self._test(i[0], i[1], None, self._test_a_connection): break_in_path[p] = True if not break_in_path[p] and len(self._paths[p]) > 0: for i in self._paths[p]: self.grid.grid[i[0]].set_shape(i[1]) self.score += self.grid.grid[i[0]].get_value() def _tile_to_test(self, test_path): ''' Find a tile that needs testing. ''' for i in self._paths[test_path]: if i[2] is False: return i[0], i[1] return None, None def _add_to_path_list(self, tile, tile_path, test_path): ''' Only add a tile to the path if it is not already there. ''' for i in self._paths[test_path]: if i[0] == tile and i[1] == tile_path: return self._paths[test_path].append([tile, tile_path, False]) def _tile_has_been_tested(self, tile, tile_path, test_path): ''' Mark a tile as tested. ''' for i in self._paths[test_path]: if i[0] == tile and i[1] == tile_path: i[2] = True return def _test(self, tile, tile_path, test_path, test): ''' Test each neighbor of a block for a connecting path. ''' if tile is None: return False for i in range(4): if not test(tile, tile_path, test_path, i, tile + OFFSETS[i]): return False return True def _test_a_connection(self, tile, tile_path, test_path, direction, neighbor): ''' Is there a break in the connection? If so return False. ''' if self.grid.grid[tile].paths[tile_path][direction] == 1: if self.grid.grid[neighbor] is None: return False # Which of the neighbor's paths are we connecting to? if len(self.grid.grid[neighbor].paths) == 1: if self.grid.grid[neighbor].paths[0][(direction + 2) % 4] == 0: return False else: return True if self.grid.grid[neighbor].paths[0][(direction + 2) % 4] == 0 and\ self.grid.grid[neighbor].paths[1][(direction + 2) % 4] == 0: return False return True def _test_a_neighbor(self, tile, tile_path, test_path, direction, neighbor): ''' Are we connected to a neighbor's path? If so, add the neighbor to our paths list and to the list of tiles that need to be tested. ''' if self.grid.grid[tile].paths[tile_path][direction] == 1: if self.grid.grid[neighbor] is not None: if not neighbor in self._paths[test_path]: # Which of the neighbor's paths are we connecting to? if self.grid.grid[neighbor].paths[0][ (direction + 2) % 4] == 1: self._add_to_path_list(neighbor, 0, test_path) elif len(self.grid.grid[neighbor].paths) == 2 and \ self.grid.grid[neighbor].paths[1][ (direction + 2) % 4] == 1: self._add_to_path_list(neighbor, 1, test_path) return True def _test_for_bad_paths(self, tile): ''' Is there a path to nowhere? ''' self._hide_errormsgs() self._there_are_errors = False if tile is not None: self._check_tile(tile, [int(tile / COL), 0], NORTH, tile + OFFSETS[0]) self._check_tile(tile, [tile % ROW, ROW - 1], EAST, tile + OFFSETS[1]) self._check_tile(tile, [int(tile / COL), COL - 1], SOUTH, tile + OFFSETS[2]) self._check_tile(tile, [tile % ROW, 0], WEST, tile + OFFSETS[3]) def _check_tile(self, i, edge_check, direction, neighbor): ''' Can a tile be placed at position i? ''' if edge_check[0] == edge_check[1]: for path in self.grid.grid[i].paths: if path[direction] == 1: self._display_errormsg(i, direction) else: if self.grid.grid[neighbor] is not None: my_path = 0 your_path = 0 for c in self.grid.grid[i].paths: if c[direction] == 1: my_path = 1 for c in self.grid.grid[neighbor].paths: if c[(direction + 2) % 4] == 1: your_path = 1 if my_path != your_path: self._display_errormsg(i, direction) def _display_errormsg(self, i, direction): ''' Display an error message where and when appropriate. ''' if self._press is not None: dxdy = [[0.375, -0.125], [0.875, 0.375], [0.375, 0.875], [-0.125, 0.375]] x, y = self._press.get_xy() self._errormsg[direction].move( (x + dxdy[direction][0] * self.tile_width, y + dxdy[direction][1] * self.tile_height)) self._errormsg[direction].set_layer(OVER_THE_TOP) self._there_are_errors = True def _hide_errormsgs(self): ''' Hide all the error messages. ''' for i in range(4): self._errormsg[i].move((self.grid.left, self.grid.top)) self._errormsg[i].set_layer(HIDE) def _hide_highlight(self): ''' No tile is selected. ''' for i in range(4): self._highlight[i].move((self.grid.left, self.grid.top)) self._highlight[i].set_layer(HIDE) def _move_relative_highlight(self, pos): for i in range(4): self._highlight[i].move_relative(pos) def _move_highlight(self, pos): x, y = pos self._highlight[0].move((x, y)) self._highlight[1].move((x + 7 * self.tile_width / 8, y)) self._highlight[2].move((x + 7 * self.tile_width / 8, y + 7 * self.tile_height / 8)) self._highlight[3].move((x, y + 7 * self.tile_height / 8)) def _show_highlight(self, pos=None): ''' Highlight the tile that is selected. ''' if self.last_spr_moved is None and pos is None: self._hide_highlight() else: if pos is None: x, y = self.last_spr_moved.get_xy() else: # Giving a hint. x, y = pos self._move_highlight((x, y)) for i in range(4): self._highlight[i].set_layer(OVER_THE_TOP) def _keypress_cb(self, area, event): return True def _expose_cb(self, win, event): ''' Callback to handle window expose events ''' self.do_expose_event(event) return True def do_expose_event(self, event): ''' Handle the expose-event by drawing ''' # Restrict Cairo to the exposed area cr = self._canvas.window.cairo_create() cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): gtk.main_quit()
class Game(): def __init__(self, canvas, parent=None, path=None): self._canvas = canvas self._parent = parent self._parent.show_all() self._path = path self._canvas.connect("draw", self.__draw_cb) self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.connect("button-press-event", self._button_press_cb) self._canvas.add_events(Gdk.EventMask.POINTER_MOTION_MASK) self._canvas.connect("motion-notify-event", self._mouse_move_cb) self._canvas.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK) self._canvas.connect('button-release-event', self._button_release_cb) self._canvas.add_events(Gdk.EventMask.KEY_PRESS_MASK) self._canvas.connect('key-press-event', self._keypress_cb) self._canvas.set_can_focus(True) self._canvas.grab_focus() self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() self._scale = self._width / 1200. self._first_time = True self._loco_pos = (0, 0) self._loco_dim = (0, 0) self._loco_quadrant = 3 self._drag_pos = [0, 0] self._counter = 0 self._correct = 0 self._timeout_id = None self._pause = 200 self._press = None self._clicked = False self._dead_key = None self._waiting_for_delete = False self._waiting_for_enter = False self._seconds = 0 self._timer_id = None self.level = 0 self.score = 0 # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) self._BG = ['background0.jpg', 'background0.jpg', 'background0.jpg', 'background1.jpg', 'background2.jpg', 'background2.jpg', 'background2.jpg'] self._backgrounds = [] for bg in self._BG: self._backgrounds.append(Sprite( self._sprites, 0, 0, GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._path, 'images', bg), self._width, self._height))) self._backgrounds[-1].type = 'background' self._backgrounds[-1].hide() self._panel = Sprite( self._sprites, int(400 * self._scale), int(400 * self._scale), GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._path, 'images', 'ventana.png'), int(720 * self._scale), int(370 * self._scale))) self._panel.type = 'panel' self._panel.set_label(LABELS[0]) self._panel.set_label_attributes(20) self._panel.hide() self._LOCOS = glob.glob( os.path.join(self._path, 'images', 'loco*.png')) self._loco_cards = [] for loco in self._LOCOS: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( loco, int(150 * self._scale), int(208 * self._scale)) self._loco_cards.append(Sprite(self._sprites, 0, 0, pixbuf)) self._loco_cards[-1].type = 'loco' self._loco_dim = (int(150 * self._scale), int(208 * self._scale)) self._MEN = glob.glob( os.path.join(self._path, 'images', 'man*.png')) self._man_cards = [] for loco in self._MEN: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( loco, int(150 * self._scale), int(208 * self._scale)) self._man_cards.append(Sprite(self._sprites, 0, 0, pixbuf)) self._man_cards[-1].type = 'loco' self._TAUNTS = glob.glob( os.path.join(self._path, 'images', 'taunt*.png')) self._taunt_cards = [] for loco in self._TAUNTS: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( loco, int(150 * self._scale), int(208 * self._scale)) self._taunt_cards.append(Sprite(self._sprites, 0, 0, pixbuf)) self._taunt_cards[-1].type = 'loco' self._GHOSTS = glob.glob( os.path.join(self._path, 'images', 'ghost*.png')) self._ghost_cards = [] for loco in self._GHOSTS: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( loco, int(150 * self._scale), int(208 * self._scale)) self._ghost_cards.append(Sprite(self._sprites, 0, 0, pixbuf)) self._ghost_cards[-1].type = 'loco' self._sticky_cards = [] self._loco_pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( self._LOCOS[0], int(150 * self._scale), int(208 * self._scale)) self._man_pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( self._MEN[0], int(150 * self._scale), int(208 * self._scale)) self._ghost_pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( self._GHOSTS[0], int(150 * self._scale), int(208 * self._scale)) for i in range(len(MSGS[1])): # Check re i18n self._sticky_cards.append(Sprite(self._sprites, 0, 0, self._loco_pixbuf)) self._sticky_cards[-1].type = 'loco' self._sticky_cards[-1].set_label_attributes(24, vert_align='bottom') self._all_clear() def _time_increment(self): ''' Track seconds since start_time. ''' self._seconds = int(GObject.get_current_time() - self._start_time) self.timer_id = GObject.timeout_add(1000, self._time_increment) def _timer_reset(self): ''' Reset the timer for each level ''' self._start_time = GObject.get_current_time() if self._timer_id is not None: GObject.source_remove(self._timer_id) self._timer_id = None self.score += self._seconds self._time_increment() def _all_clear(self): ''' Things to reinitialize when starting up a new game. ''' for p in self._loco_cards: p.hide() for p in self._man_cards: p.hide() for p in self._taunt_cards: p.hide() for p in self._ghost_cards: p.hide() for p in self._sticky_cards: p.set_shape(self._loco_pixbuf) p.set_label('') p.set_label_color('white') p.hide() self._backgrounds[self.level].set_layer(BG_LAYER) def _show_time(self): self.level = 0 self._all_clear() x = int(self._width / 4.) y = int(self._height / 8.) for i in range(len(str(self.score))): self._sticky_cards[i].move((x, y)) self._sticky_cards[i].set_layer(LOCO_LAYER) self._sticky_cards[i].set_label(str(self.score)[i]) x += int(self._loco_dim[0] / 2.) self.score = 0 self._parent.unfullscreen() GObject.idle_add(play_audio_from_file, self, os.path.join( self._path, 'sounds', 'sonar.ogg')) GObject.timeout_add(5000, self.new_game, True) def new_game(self, first_time): ''' Start a new game at the current level. ''' self._first_time = first_time self._clicked = False # It may be time to advance to the next level. if (self.level == 6 and self._counter == len(MSGS)) or \ self._counter > 4: self._first_time = True self.level += 1 self._counter = 0 self._correct = 0 self._pause = 200 if self.level == len(self._backgrounds): self._show_time() return self._all_clear() if self._first_time: # Every game starts by putting up a panel with instructions # The panel disappears on mouse movement self._panel.set_label(LABELS[self.level]) self._panel.set_layer(PANEL_LAYER) play_audio_from_file(self, os.path.join( self._path, 'sounds', 'drip.ogg')) self._timer_reset() if self.level == 0: # Choose a random location for the Loco self._loco_quadrant += int(uniform(1, 4)) self._loco_quadrant %= 4 x, y = self._quad_to_xy(self._loco_quadrant) play_audio_from_file(self, os.path.join( self._path, 'sounds', 'bark.ogg')) self._loco_cards[0].move((x, y)) self._loco_pos = (x, y) elif self.level == 1: play_audio_from_file(self, os.path.join( self._path, 'sounds', 'glass.ogg')) elif self.level == 2: play_audio_from_file(self, os.path.join( self._path, 'sounds', 'glass.ogg')) # Place some Locos on the canvas for i in range(self._counter + 1): self._loco_quadrant += int(uniform(1, 4)) self._loco_quadrant %= 4 x, y = self._quad_to_xy(self._loco_quadrant) self._sticky_cards[i].move((x, y)) self._sticky_cards[i].type = 'loco' self._sticky_cards[i].set_layer(LOCO_LAYER) elif self.level == 3: play_audio_from_file(self, os.path.join( self._path, 'sounds', 'bark.ogg')) # Place some Locos on the left-side of the canvas for i in range(self._counter + 1): self._loco_quadrant = int(uniform(2, 4)) x, y = self._quad_to_xy(self._loco_quadrant) self._sticky_cards[i].move((x, y)) self._sticky_cards[i].type = 'loco' self._sticky_cards[i].set_layer(LOCO_LAYER) elif self.level == 4: # Place some Locos on the canvas with letters as labels # Just lowercase for i in range(self._counter + 1): self._loco_quadrant = int(uniform(0, 4)) x, y = self._quad_to_xy(self._loco_quadrant) self._sticky_cards[i].move((x, y)) self._sticky_cards[i].type = 'loco' self._sticky_cards[i].set_layer(LOCO_LAYER) self._sticky_cards[i].set_label( ALPHABETLC[int(uniform(0, len(ALPHABETLC)))]) elif self.level == 5: # Place some Locos on the canvas with letters as labels # Uppercase for i in range(self._counter + 1): self._loco_quadrant = int(uniform(0, 4)) x, y = self._quad_to_xy(self._loco_quadrant) self._sticky_cards[i].move((x, y)) self._sticky_cards[i].type = 'loco' self._sticky_cards[i].set_layer(LOCO_LAYER) self._sticky_cards[i].set_label( ALPHABETUC[int(uniform(0, len(ALPHABETUC)))]) elif self.level == 6: x = 0 y = 0 c = 0 for i in range(len(MSGS[self._counter])): if MSGS[self._counter][i] == ' ': y += self._loco_dim[1] x = 0 else: self._sticky_cards[c].move((x, y)) self._sticky_cards[c].type = i self._sticky_cards[c].set_layer(LOCO_LAYER) self._sticky_cards[c].set_label(MSGS[self._counter][i]) c += 1 x += int(self._loco_dim[0] / 2.) if self.level in [0, 1]: self._loco_quadrant += int(uniform(1, 4)) self._loco_quadrant %= 4 x, y = self._quad_to_xy(self._loco_quadrant) if self.level == 0: self._move_loco(x, y, 0) else: self._taunt(x, y, 0) def _quad_to_xy(self, q): x = int(max(0, (self._width / 2.) * uniform(0, 1) - self._loco_dim[0])) if q in [0, 1]: x += int(self._width / 2.) y = int(max(0, (self._height / 2.) * uniform(0, 1) - self._loco_dim[1])) if q in [1, 2]: y += int(self._height / 2.) return x, y def _taunt(self, x, y, i): n = len(self._taunt_cards) self._taunt_cards[(i + 1) % n].hide() if self._clicked: self._timeout_id = None return True else: self._taunt_cards[i % n].move((x, y)) self._taunt_cards[i % n].set_layer(LOCO_LAYER) self._timeout_id = GObject.timeout_add( 200, self._taunt, x, y, i + 1) def _move_loco(self, x, y, i): j = (i + 1) % len(self._loco_cards) cx, cy = self._loco_cards[i].get_xy() dx = cx - x dy = cy - y if dx * dx + dy * dy < 100: self._loco_cards[j].move((x, y)) self._loco_pos = (x, y) self._loco_cards[j].hide() self._loco_cards[i].hide() self._man_cards[0].move((x, y)) self._man_cards[0].set_layer(LOCO_LAYER) self._timeout_id = None if self._pause > 50: self._pause -= 10 return True else: if dx > 0: cx -= 5 elif dx < 0: cx += 5 if dy > 0: cy -= 5 elif dy < 0: cy += 5 self._loco_cards[j].move((cx, cy)) self._loco_pos = (cx, cy) self._loco_cards[j].set_layer(LOCO_LAYER) self._loco_cards[i].hide() self._timeout_id = GObject.timeout_add( self._pause, self._move_loco, x, y, j) def _keypress_cb(self, area, event): ''' Keypress ''' # Games 4, 5, and 6 use the keyboard print 'keypress event' if self.level not in [4, 5, 6]: return True k = Gdk.keyval_name(event.keyval) u = Gdk.keyval_to_unicode(event.keyval) if self._waiting_for_enter: if k == 'Return': self._waiting_for_enter = False self._panel.hide() self._counter += 1 self._correct = 0 GObject.timeout_add(1000, self.new_game, False) return if k in NOISE_KEYS or k in WHITE_SPACE: return True if self.level == 6 and self._waiting_for_delete: if k in ['BackSpace', 'Delete']: self._waiting_for_delete = False self._sticky_cards[self._correct].set_label_color('white') self._sticky_cards[self._correct].set_label( MSGS[self._counter][ self._sticky_cards[self._correct].type]) self._panel.hide() self._panel.set_label_color('black') return if k[0:5] == 'dead_': self._dead_key = k[5:] return if self.level == 6: n = len(MSGS[self._counter]) else: n = self._counter + 1 if self.level == 6: i = self._correct if self._dead_key is not None: k = DEAD_DICTS[DEAD_KEYS.index(self._dead_key)][k] self._dead_key = None elif k in PUNCTUATION: k = PUNCTUATION[k] elif k in SPECIAL: k = SPECIAL[k] elif len(k) > 1: return True if self._sticky_cards[i].labels[0] == k: self._sticky_cards[i].set_label_color('blue') self._sticky_cards[i].set_label(k) self._correct += 1 else: self._sticky_cards[i].set_label_color('red') self._sticky_cards[i].set_label(k) self._panel.set_label_color('red') self._panel.set_label(ALERTS[1]) self._panel.set_layer(PANEL_LAYER) self._waiting_for_delete = True play_audio_from_file(self, os.path.join( self._path, 'sounds', 'glass.ogg')) else: for i in range(n): if self._sticky_cards[i].labels[0] == k: self._sticky_cards[i].set_label('') self._sticky_cards[i].hide() break # Test for end condition if self.level == 6 and \ self._correct == len(MSGS[self._counter]) - \ MSGS[self._counter].count(' '): c = 0 for i in range(len(MSGS[self._counter])): if MSGS[self._counter][i] == ' ': continue elif MSGS[self._counter][i] != self._sticky_cards[c].labels[0]: return True c += 1 self._panel.set_label(ALERTS[0]) self._panel.set_layer(PANEL_LAYER) self._waiting_for_enter = True GObject.idle_add(play_audio_from_file, self, os.path.join( self._path, 'sounds', 'drip.ogg')) return else: for i in range(n): if len(self._sticky_cards[i].labels[0]) > 0: return True self._counter += 1 self._correct = 0 GObject.timeout_add(1000, self.new_game, False) def _mouse_move_cb(self, win, event): ''' Move the mouse. ''' # Games 0, 3, 4, and 5 use move events x, y = map(int, event.get_coords()) if self._seconds > 1: self._panel.hide() if not self._clicked and self.level == 0: # For Game 0, see if the mouse is on the Loco dx = x - self._loco_pos[0] - self._loco_dim[0] / 2. dy = y - self._loco_pos[1] - self._loco_dim[1] / 2. if dx * dx + dy * dy < 200: self._clicked = True if self._timeout_id is not None: GObject.source_remove(self._timeout_id) # Play again self._all_clear() self._man_cards[0].move((x - int(self._loco_dim[0] / 2.), y - int(self._loco_dim[1] / 2.))) self._man_cards[0].set_layer(LOCO_LAYER) self._correct += 1 self._counter += 1 GObject.timeout_add(1000, self.new_game, False) elif self.level in [4, 5]: # For Game 4 and 5, we allow dragging if self._press is None: self._drag_pos = [0, 0] return True dx = x - self._drag_pos[0] dy = y - self._drag_pos[1] self._press.move_relative((dx, dy)) self._drag_pos = [x, y] elif self.level == 3: # For Game 3, we are dragging if self._press is None: self._drag_pos = [0, 0] return True dx = x - self._drag_pos[0] dy = y - self._drag_pos[1] self._press.move_relative((dx, dy)) self._drag_pos = [x, y] if x > self._width / 2.: self._press.set_shape(self._man_pixbuf) if self._press.type == 'loco': self._correct += 1 self._press.type = 'man' return True def _button_release_cb(self, win, event): # Game 3 uses release if self.level == 3: # Move to release if self._correct == self._counter + 1: self._counter += 1 self._correct = 0 GObject.timeout_add(2000, self.new_game, False) self._press = None self._drag_pos = [0, 0] return True def _button_press_cb(self, win, event): self._press = None x, y = map(int, event.get_coords()) if self.level == 0: return spr = self._sprites.find_sprite((x, y)) if spr is None: return if spr.type != 'loco': return if self.level < 2 and self._timeout_id is None: return if self._clicked: return # Games 1, 2, and 3 involve clicks; Games 4 and 5 allow click to drag if self.level == 1: self._all_clear() self._man_cards[0].move((x - int(self._loco_dim[0] / 2.), y - int(self._loco_dim[1] / 2.))) self._man_cards[0].set_layer(LOCO_LAYER) self._clicked = True self._counter += 1 self._correct += 1 if self._timeout_id is not None: GObject.source_remove(self._timeout_id) GObject.timeout_add(2000, self.new_game, False) elif self.level == 2: spr.set_shape(self._ghost_pixbuf) spr.type = 'ghost' if self._correct == self._counter: self._counter += 1 self._correct = 0 GObject.timeout_add(2000, self.new_game, False) else: self._correct += 1 elif self.level in [3, 4, 5]: # In Games 4 and 5, dragging is used to remove overlaps self._press = spr self._drag_pos = [x, y] return True def __draw_cb(self, canvas, cr): self._sprites.redraw_sprites(cr=cr) def do_expose_event(self, event): ''' Handle the expose-event by drawing ''' # Restrict Cairo to the exposed area cr = self._canvas.window.cairo_create() cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): Gtk.main_quit()
class Game(): def __init__(self, canvas, parent=None, path=None): self._canvas = canvas self._parent = parent self._parent.show_all() self._path = path self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.connect("draw", self.__draw_cb) self._canvas.connect("button-press-event", self._button_press_cb) self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() self._scale = self._width / 1200. self._target = 0 self._tries = 0 self.level = 0 self._picture_cards = [] self._small_picture_cards = [] self.food_cards = [] self._group_cards = [] self._quantity_cards = [] self._balance_cards = [] self._last_twenty = [] self._background = None # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) self._background = Sprite( self._sprites, 0, 0, GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._path, 'images', 'background.png'), self._width, self._height)) self._background.set_layer(0) self._background.type = None self._background.hide() self.pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._path, 'images', 'word-box.png'), int(350 * self._scale), int(100 * self._scale)) for i in range(len(FOOD_DATA) / 4): FOOD.append([ FOOD_DATA[i * 4 + NAME], FOOD_DATA[i * 4 + CALS], FOOD_DATA[i * 4 + GROUP], FOOD_DATA[i * 4 + IMAGE] ]) self.food_cards.append(None) self._picture_cards.append(None) for j in range(6): self._small_picture_cards.append(None) self.allocate_food(0) x = 10 dx, dy = self.food_cards[0].get_dimensions() y = 10 for i in range(len(MYPLATE)): self.word_card_append(self._group_cards, self.pixbuf) self._group_cards[-1].type = i self._group_cards[-1].set_label(MYPLATE[i][0]) self._group_cards[-1].move((x, y)) y += int(dy * 1.25) y = 10 for i in range(len(QUANTITIES)): self.word_card_append(self._quantity_cards, self.pixbuf) self._quantity_cards[-1].type = i self._quantity_cards[-1].set_label(QUANTITIES[i]) self._quantity_cards[-1].move((x, y)) y += int(dy * 1.25) y = 10 for i in range(len(BALANCE)): self.word_card_append(self._balance_cards, self.pixbuf) self._balance_cards[-1].type = i self._balance_cards[-1].set_label(BALANCE[i]) self._balance_cards[-1].move((x, y)) y += int(dy * 1.25) self._smile = Sprite( self._sprites, int(self._width / 4), int(self._height / 4), GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._path, 'images', 'correct.png'), int(self._width / 2), int(self._height / 2))) self._smile.set_label_attributes(36) self._smile.set_margins(10, 0, 10, 0) self._frown = Sprite( self._sprites, int(self._width / 4), int(self._height / 4), GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._path, 'images', 'wrong.png'), int(self._width / 2), int(self._height / 2))) self._frown.set_label_attributes(36) self._frown.set_margins(10, 0, 10, 0) self.build_food_groups() self._all_clear() def allocate_food(self, i): self.picture_append( os.path.join(self._path, 'images', FOOD_DATA[i * 4 + IMAGE]), i) self.small_picture_append( os.path.join(self._path, 'images', FOOD_DATA[i * 4 + IMAGE]), i) self.word_card_append(self.food_cards, self.pixbuf, i) self.food_cards[i].type = i self.food_cards[i].set_label(FOOD_DATA[i * 4 + NAME]) def word_card_append(self, card_list, pixbuf, i=-1): if i == -1: card_list.append(Sprite(self._sprites, 10, 10, pixbuf)) else: card_list[i] = Sprite(self._sprites, 10, 10, pixbuf) card_list[i].set_label_attributes(36) card_list[i].set_margins(10, 0, 10, 0) card_list[i].hide() def picture_append(self, path, i=-1): spr = Sprite( self._sprites, int(self._width / 2.), int(self._height / 4.), GdkPixbuf.Pixbuf.new_from_file_at_size(path, int(self._width / 3.), int(9 * self._width / 12.))) if i == -1: self._picture_cards.append(spr) else: self._picture_cards[i] = spr self._picture_cards[i].type = 'picture' self._picture_cards[i].hide() def small_picture_append(self, path, i=-1): x = int(self._width / 3.) y = int(self._height / 6.) pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( path, int(self._width / 6.), int(3 * self._width / 8.)) for j in range(6): # up to 6 of each card if i == -1: self._small_picture_cards.append( Sprite(self._sprites, x, y, pixbuf)) self._small_picture_cards[-1].type = 'picture' self._small_picture_cards[-1].hide() else: self._small_picture_cards[i * 6 + j] = Sprite( self._sprites, x, y, pixbuf) self._small_picture_cards[i * 6 + j].type = 'picture' self._small_picture_cards[i * 6 + j].hide() x += int(self._width / 6.) if j == 2: x = int(self._width / 3.) y += int(3 * self._width / 16.) def _all_clear(self): ''' Things to reinitialize when starting up a new game. ''' for p in self._picture_cards: if p is not None: p.hide() for p in self._small_picture_cards: if p is not None: p.hide() for i, w in enumerate(self.food_cards): if w is not None: w.set_label_color('black') w.set_label(FOOD[i][NAME]) w.hide() for i, w in enumerate(self._group_cards): w.set_label_color('black') w.set_label(MYPLATE[i][0]) w.hide() for i, w in enumerate(self._quantity_cards): w.set_label_color('black') w.set_label(QUANTITIES[i]) w.hide() for i, w in enumerate(self._balance_cards): w.set_label_color('black') w.set_label(BALANCE[i]) w.hide() self._smile.hide() self._frown.hide() self._background.set_layer(1) def build_food_groups(self): self._my_plate = [[], [], [], []] for i, food in enumerate(FOOD): self._my_plate[MYPLATE[food[GROUP]][QUANT]].append(i) def new_game(self): ''' Start a new game. ''' games = { 0: self._name_that_food, 1: self._name_that_food_group, 2: self._compare_calories, 3: self._how_much_to_eat, 4: self._balanced_meal } self._all_clear() games[self.level]() self._frown.set_label('') self._smile.set_label('') self._tries = 0 def _name_that_food(self): ''' Choose food cards and one matching food picture ''' x = 10 y = 10 dx, dy = self.food_cards[0].get_dimensions() # Select some cards word_list = [] for i in range(NCARDS): j = int(uniform(0, len(FOOD))) while j in word_list: j = int(uniform(0, len(FOOD))) word_list.append(j) # Show the word cards from the list for i in word_list: if self.food_cards[i] is None: self.allocate_food(i) self.food_cards[i].set_layer(100) self.food_cards[i].move((x, y)) y += int(dy * 1.25) # Choose a random food image from the list and show it. self._target = self.food_cards[word_list[int(uniform(0, NCARDS))]].type while self._target in self._last_twenty: self._target = self.food_cards[word_list[int(uniform( 0, NCARDS))]].type self._last_twenty.append(self._target) if len(self._last_twenty) > 20: self._last_twenty.remove(self._last_twenty[0]) self._picture_cards[self._target].set_layer(100) def _name_that_food_group(self): ''' Show group cards and one food picture ''' for i in range(len(MYPLATE)): self._group_cards[i].set_layer(100) # Choose a random food image and show it. self._target = int(uniform(0, len(FOOD))) if self.food_cards[self._target] is None: self.allocate_food(self._target) self._picture_cards[self._target].set_layer(100) def _compare_calories(self): ''' Choose food cards and compare the calories ''' x = 10 y = 10 dx, dy = self.food_cards[0].get_dimensions() # Select some cards word_list = [] for i in range(6): j = int(uniform(0, len(FOOD))) while j in word_list: j = int(uniform(0, len(FOOD))) word_list.append(j) if self.food_cards[j] is None: self.allocate_food(j) # Show the word cards from the list for i in word_list: self.food_cards[i].set_layer(100) self.food_cards[i].move((x, y)) y += int(dy * 1.25) # Show food images self._target = word_list[0] for i in range(5): if FOOD[word_list[i + 1]][CALS] > FOOD[self._target][CALS]: self._target = word_list[i + 1] self._small_picture_cards[word_list[0] * 6].set_layer(100) self._small_picture_cards[word_list[1] * 6 + 1].set_layer(100) self._small_picture_cards[word_list[2] * 6 + 2].set_layer(100) self._small_picture_cards[word_list[3] * 6 + 3].set_layer(100) self._small_picture_cards[word_list[4] * 6 + 4].set_layer(100) self._small_picture_cards[word_list[5] * 6 + 5].set_layer(100) def _how_much_to_eat(self): ''' Show quantity cards and one food picture ''' for i in range(len(QUANTITIES)): self._quantity_cards[i].set_layer(100) # Choose a random image from the list and show it. self._target = int(uniform(0, len(FOOD))) if self.food_cards[self._target] is None: self.allocate_food(self._target) self._picture_cards[self._target].set_layer(100) def _balanced_meal(self): ''' A well-balanced meal ''' for i in range(2): self._balance_cards[i].set_layer(100) # Determine how many foods from each group n = [0, 0, 0, 0] n[0] = int(uniform(0, 2.5)) n[1] = int(uniform(0, 3 - n[0])) n[2] = 3 - n[0] - n[1] n[3] = 6 - n[0] - n[1] - n[2] # Fill a plate with foods from different groups meal = [] for i in range(n[0]): # Sweets j = int(uniform(0, len(self._my_plate[0]))) meal.append(self._my_plate[0][j]) for i in range(n[1]): # Dairy j = int(uniform(0, len(self._my_plate[1]))) meal.append(self._my_plate[1][j]) for i in range(n[2]): # Protein and Fruits j = int(uniform(0, len(self._my_plate[2]))) meal.append(self._my_plate[2][j]) for i in range(n[3]): # Veggies and Grains j = int(uniform(0, len(self._my_plate[3]))) meal.append(self._my_plate[3][j]) if n[0] < 2 and n[1] < 2 and n[2] < n[3]: self._target = 0 # Balanced meal else: self._target = 1 for i in range(6): if self.food_cards[meal[i]] is None: self.allocate_food(meal[i]) # Randomly position small cards self._small_picture_cards[meal[3] * 6].set_layer(100) self._small_picture_cards[meal[4] * 6 + 1].set_layer(100) self._small_picture_cards[meal[1] * 6 + 2].set_layer(100) self._small_picture_cards[meal[2] * 6 + 3].set_layer(100) self._small_picture_cards[meal[5] * 6 + 4].set_layer(100) self._small_picture_cards[meal[0] * 6 + 5].set_layer(100) def _button_press_cb(self, win, event): win.grab_focus() x, y = list(map(int, event.get_coords())) spr = self._sprites.find_sprite((x, y)) if spr == None: return # We only care about clicks on word cards if type(spr.type) != int: return # Which card was clicked? Set its label to red. spr.set_label_color('red') label = spr.labels[0] spr.set_label(label) if self.level == 0: if spr.type == self._target: self._smile.set_layer(200) self._tries = 3 else: self._frown.set_layer(200) self._tries += 1 if self._tries == 3: self.food_cards[self._target].set_label_color('blue') label = self.food_cards[self._target].labels[0] self.food_cards[self._target].set_label(label) elif self.level == 1: i = FOOD[self._target][GROUP] if spr.type == i: self._smile.set_layer(200) self._tries = 3 else: self._frown.set_layer(200) self._tries += 1 if self._tries == 3: self._group_cards[i].set_label_color('blue') label = self._group_cards[i].labels[0] self._group_cards[i].set_label(label) elif self.level == 2: if spr.type == self._target: self._smile.set_layer(200) self._tries = 3 else: self._frown.set_layer(200) self._tries += 1 if self._tries == 3: self.food_cards[self._target].set_label_color('blue') label = self.food_cards[self._target].labels[0] self.food_cards[self._target].set_label(label) elif self.level == 3: i = MYPLATE[FOOD[self._target][GROUP]][QUANT] if spr.type == i: self._smile.set_layer(200) self._tries = 3 else: self._frown.set_layer(200) self._tries += 1 if self._tries == 3: self._quantity_cards[i].set_label_color('blue') label = self._quantity_cards[i].labels[0] self._quantity_cards[i].set_label(label) elif self.level == 4: if self._target == spr.type: self._smile.set_layer(200) self._tries = 3 else: self._frown.set_layer(200) self._tries += 1 if self._tries == 3: self._balance_cards[self._target].set_label_color('blue') label = self._balance_cards[self._target].labels[0] self._balance_cards[self._target].set_label(label) else: _logger.debug('unknown play level %d' % (self.level)) # Play again if self._tries == 3: GObject.timeout_add(2000, self.new_game) else: GObject.timeout_add(1000, self._reset_game) return True def _reset_game(self): self._frown.hide() if self.level in [0, 2]: for i, w in enumerate(self.food_cards): w.set_label_color('black') w.set_label(FOOD[i][NAME]) elif self.level == 1: for i, w in enumerate(self._group_cards): w.set_label_color('black') w.set_label(MYPLATE[i][0]) elif self.level == 3: for i, w in enumerate(self._quantity_cards): w.set_label_color('black') w.set_label(QUANTITIES[i]) elif self.level == 4: for i, w in enumerate(self._balance_cards): w.set_label_color('black') w.set_label(BALANCE[i]) def __draw_cb(self, canvas, cr): self._sprites.redraw_sprites(cr=cr) def do_expose_event(self, event): ''' Handle the expose-event by drawing ''' # Restrict Cairo to the exposed area cr = self._canvas.window.cairo_create() cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): Gtk.main_quit()
class Game(): def __init__(self, canvas, parent=None, path=None, root=None, mode='array', colors=['#A0FFA0', '#FF8080']): self._canvas = canvas self._parent = parent self._path = path self._root = root self._mode = mode self.current_image = 0 self.playing = False self._timeout_id = None self._prev_mouse_pos = (0, 0) self._start_time = 0 self._colors = ['#FFFFFF'] self._colors.append(colors[0]) self._colors.append(colors[1]) self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK | Gdk.EventMask.BUTTON_MOTION_MASK | Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.POINTER_MOTION_HINT_MASK | Gdk.EventMask.TOUCH_MASK) self._canvas.connect('draw', self.__draw_cb) self._canvas.connect('event', self.__event_cb) self.configure(move=False) self.we_are_sharing = False self._start_time = 0 self._timeout_id = None # Find the image files self._PATHS = glob.glob(os.path.join(self._path, 'images', '*.svg')) # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) a = max(Gdk.Screen.width(), Gdk.Screen.height()) b = min(Gdk.Screen.width(), Gdk.Screen.height()) self._bg_pixbufs = [] if self._parent.tablet_mode: # text on top # landscape self._bg_pixbufs.append( svg_str_to_pixbuf( genhole(a, a, 3 * style.GRID_CELL_SIZE, style.DEFAULT_SPACING, a - 3 * style.GRID_CELL_SIZE, style.GRID_CELL_SIZE * 3 + style.DEFAULT_SPACING))) # portrait self._bg_pixbufs.append( svg_str_to_pixbuf( genhole(a, a, 3 * style.GRID_CELL_SIZE, style.DEFAULT_SPACING, b - 3 * style.GRID_CELL_SIZE, style.GRID_CELL_SIZE * 3 + style.DEFAULT_SPACING))) else: # text on bottom # landscape self._bg_pixbufs.append( svg_str_to_pixbuf( genhole( a, a, 3 * style.GRID_CELL_SIZE, b - style.GRID_CELL_SIZE * 4 - style.DEFAULT_SPACING, a - 3 * style.GRID_CELL_SIZE, b - style.GRID_CELL_SIZE - style.DEFAULT_SPACING))) # portrait self._bg_pixbufs.append( svg_str_to_pixbuf( genhole( a, a, 3 * style.GRID_CELL_SIZE, a - style.GRID_CELL_SIZE * 4 - style.DEFAULT_SPACING, b - 3 * style.GRID_CELL_SIZE, a - style.GRID_CELL_SIZE - style.DEFAULT_SPACING))) if Gdk.Screen.width() > Gdk.Screen.height(): self._bg = Sprite(self._sprites, 0, 0, self._bg_pixbufs[0]) else: self._bg = Sprite(self._sprites, 0, 0, self._bg_pixbufs[1]) self._bg.set_layer(-2) self._bg.type = 'background' size = 3 * self._dot_size + 4 * self._space x = int((Gdk.Screen.width() - size) / 2.) self._dots = [] self._Dots = [] # larger dots for linear mode X = int((Gdk.Screen.width() - self._dot_size * 3) / 2.) Y = style.GRID_CELL_SIZE + self._yoff if self._parent.tablet_mode: yoffset = self._space * 2 + self._yoff else: yoffset = self._yoff for y in range(3): for x in range(3): xoffset = int( (self._width - 3 * self._dot_size - 2 * self._space) / 2.) self._dots.append( Sprite(self._sprites, xoffset + x * (self._dot_size + self._space), y * (self._dot_size + self._space) + yoffset, self._new_dot_surface(color=self._colors[0]))) self._dots[-1].type = -1 # No image self._dots[-1].set_label_attributes(72) self._dots[-1].set_label('?') self._Dots.append( Sprite( self._sprites, X, Y, self._new_dot_surface(color=self._colors[0], large=True))) self._Dots[-1].type = -1 # No image self._Dots[-1].set_label_attributes(72 * 3) self._Dots[-1].set_label('?') self.number_of_images = len(self._PATHS) if USE_ART4APPS: self._art4apps = Art4Apps() self.number_of_images = len(self._art4apps.get_words()) self._record_pixbufs = [] for icon in ['media-audio', 'media-audio-recording']: self._record_pixbufs.append( GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._root, 'icons', icon + '.svg'), style.GRID_CELL_SIZE, style.GRID_CELL_SIZE)) self._play_pixbufs = [] for icon in ['play-inactive', 'play']: self._play_pixbufs.append( GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._root, 'icons', icon + '.svg'), style.GRID_CELL_SIZE, style.GRID_CELL_SIZE)) self._speak_pixbufs = [] for icon in ['speak-inactive', 'speak']: self._speak_pixbufs.append( GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._root, 'icons', icon + '.svg'), style.GRID_CELL_SIZE, style.GRID_CELL_SIZE)) left = style.GRID_CELL_SIZE right = Gdk.Screen.width() - 2 * style.GRID_CELL_SIZE y0 = style.DEFAULT_SPACING + style.DEFAULT_PADDING y1 = y0 + style.GRID_CELL_SIZE y2 = y1 + style.GRID_CELL_SIZE if not self._parent.tablet_mode: dy = Gdk.Screen.height() - 4 * style.GRID_CELL_SIZE - \ 2 * style.DEFAULT_SPACING y0 += dy y1 += dy y2 += dy y3 = int((Gdk.Screen.height() - 2 * style.GRID_CELL_SIZE) / 2) self._record = Sprite(self._sprites, right, y0, self._record_pixbufs[RECORD_OFF]) self._record.set_layer(1) self._record.type = 'record' self._play = Sprite(self._sprites, right, y1, self._play_pixbufs[PLAY_OFF]) self._play.set_layer(1) self._play.type = 'play-inactive' self._speak = Sprite(self._sprites, right, y2, self._speak_pixbufs[SPEAK_OFF]) self._speak.set_layer(1) self._speak.type = 'speak-inactive' self._next_prev_pixbufs = [] for icon in [ 'go-previous', 'go-next', 'go-previous-inactive', 'go-next-inactive' ]: self._next_prev_pixbufs.append( GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._root, 'icons', icon + '.svg'), style.GRID_CELL_SIZE, style.GRID_CELL_SIZE)) self._prev = Sprite(self._sprites, left, y3, self._next_prev_pixbufs[PREV_INACTIVE]) self._prev.set_layer(1) self._prev.type = 'prev' if self._mode == 'array': self._prev.hide() self._next = Sprite(self._sprites, right, y3, self._next_prev_pixbufs[NEXT]) self._next.set_layer(1) self._next.type = 'next' if self._mode == 'array': self._next.hide() def configure(self, move=True): self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - style.GRID_CELL_SIZE if not move: if self._height < self._width: self._scale = self._height / (3 * DOT_SIZE * 1.2) else: self._scale = self._width / (3 * DOT_SIZE * 1.2) self._scale /= 1.5 self._dot_size = int(DOT_SIZE * self._scale) if self._parent.tablet_mode: # text on top self._yoff = style.GRID_CELL_SIZE * 3 + style.DEFAULT_SPACING else: self._yoff = style.DEFAULT_SPACING self._space = int(self._dot_size / 5.) return left = style.GRID_CELL_SIZE right = Gdk.Screen.width() - 2 * style.GRID_CELL_SIZE y0 = style.DEFAULT_SPACING + style.DEFAULT_PADDING y1 = y0 + style.GRID_CELL_SIZE y2 = y1 + style.GRID_CELL_SIZE if not self._parent.tablet_mode: dy = Gdk.Screen.height() - 4 * style.GRID_CELL_SIZE - \ 2 * style.DEFAULT_SPACING y0 += dy y1 += dy y2 += dy y3 = int((Gdk.Screen.height() - 2 * style.GRID_CELL_SIZE) / 2) self._record.move((right, y0)) self._play.move((right, y1)) self._speak.move((right, y2)) self._prev.move((left, y3)) self._next.move((right, y3)) # Move the dots X = int((Gdk.Screen.width() - self._dot_size * 3) / 2.) Y = style.GRID_CELL_SIZE + self._yoff if self._parent.tablet_mode: yoffset = self._space * 2 + self._yoff else: yoffset = self._yoff for y in range(3): for x in range(3): xoffset = int( (self._width - 3 * self._dot_size - 2 * self._space) / 2.) self._dots[x + y * 3].move( (xoffset + x * (self._dot_size + self._space), y * (self._dot_size + self._space) + yoffset)) self._Dots[x + y * 3].move((X, Y)) # switch orientation the bg sprite if Gdk.Screen.width() > Gdk.Screen.height(): self._bg.set_image(self._bg_pixbufs[0]) else: self._bg.set_image(self._bg_pixbufs[1]) self._bg.set_layer(-2) def set_speak_icon_state(self, state): if state: self._speak.set_image(self._speak_pixbufs[SPEAK_ON]) self._speak.type = 'speak' else: self._speak.set_image(self._speak_pixbufs[SPEAK_OFF]) self._speak.type = 'speak-inactive' self._speak.set_layer(1) def set_record_icon_state(self, state): if state: self._record.set_image(self._record_pixbufs[RECORD_ON]) else: self._record.set_image(self._record_pixbufs[RECORD_OFF]) self._record.set_layer(1) def set_play_icon_state(self, state): if state: self._play.set_image(self._play_pixbufs[PLAY_ON]) self._play.type = 'play' else: self._play.set_image(self._play_pixbufs[PLAY_OFF]) self._play.type = 'play-inactive' self._play.set_layer(1) def autoplay(self): self.set_mode('linear') # forces current image to 0 self.playing = True self._autonext(next=False) def stop(self): self.playing = False if self._parent.audio_process is not None: self._parent.audio_process.terminate() self._parent.audio_process = None if self._timeout_id is not None: GObject.source_remove(self._timeout_id) self._timeout_id = None self._parent.autoplay_button.set_icon_name('media-playback-start') self._parent.autoplay_button.set_tooltip(_('Play')) self._parent.array_button.set_sensitive(True) def _autonext(self, next=True): self._timeout_id = None if not self.playing: return if next: self._Dots[self.current_image].hide() self.current_image += 1 self._Dots[self.current_image].set_layer(100) if self.current_image == 8: self._next.set_image(self._next_prev_pixbufs[NEXT_INACTIVE]) self._next.set_layer(1) self._prev.set_image(self._next_prev_pixbufs[PREV]) self._prev.set_layer(1) self._parent.check_audio_status() self._parent.check_text_status() GObject.idle_add(self._play_sound) def _poll_audio(self): if self._parent.audio_process is None: # Already stopped? return if self._parent.audio_process.poll() is None: GObject.timeout_add(200, self._poll_audio) else: self._parent.audio_process = None self._next_image() def _play_sound(self): self._start_time = time.time() # Either play back a recording or speak the text if self._play.type == 'play': self._parent.playback_recording_cb() self._poll_audio() elif self._speak.type == 'speak': bounds = self._parent.text_buffer.get_bounds() text = self._parent.text_buffer.get_text(bounds[0], bounds[1], True) speak(text) self._next_image() def _next_image(self): accumulated_time = int(time.time() - self._start_time) if accumulated_time < 5: pause = 5 - accumulated_time else: pause = 1 if self.playing and self.current_image < 8: self._timeout_id = GObject.timeout_add(pause * 1000, self._autonext) else: self.stop() def __event_cb(self, win, event): ''' The mouse button was pressed. Is it on a sprite? or there was a gesture. ''' left = right = False if event.type in (Gdk.EventType.TOUCH_BEGIN, Gdk.EventType.TOUCH_CANCEL, Gdk.EventType.TOUCH_END, Gdk.EventType.BUTTON_PRESS, Gdk.EventType.BUTTON_RELEASE): x = int(event.get_coords()[1]) y = int(event.get_coords()[2]) if event.type in (Gdk.EventType.TOUCH_BEGIN, Gdk.EventType.BUTTON_PRESS): self._prev_mouse_pos = (x, y) elif event.type in (Gdk.EventType.TOUCH_END, Gdk.EventType.BUTTON_RELEASE): if self._parent.audio_process is not None: self._parent.audio_process.terminate() self._parent.audio_process = None terminated_audio = True else: terminated_audio = False if self.playing: self.stop() new_mouse_pos = (x, y) mouse_movement = (new_mouse_pos[0] - self._prev_mouse_pos[0], new_mouse_pos[1] - self._prev_mouse_pos[1]) # horizontal gestures only if (abs(mouse_movement[0]) / 5) > abs(mouse_movement[1]): if abs(mouse_movement[0]) > abs(mouse_movement[1]): if mouse_movement[0] < 0: right = True else: left = True if event.type in (Gdk.EventType.TOUCH_END, Gdk.EventType.BUTTON_RELEASE): spr = self._sprites.find_sprite((x, y)) if left or right or spr is not None: if spr.type in [ 'record', 'play', 'play-inactive', 'speak', 'speak-inactive' ]: if spr.type == 'record': self._parent.record_cb() elif spr.type == 'play' and not terminated_audio: self._parent.playback_recording_cb() elif spr.type == 'speak': bounds = self._parent.text_buffer.get_bounds() text = self._parent.text_buffer.get_text( bounds[0], bounds[1], True) speak(text) return elif self._mode == 'array': return self._parent.speak_text_cb() if self._parent.recording: self._parent.record_cb() if (left or spr.type == 'prev') and self.current_image > 0: self._Dots[self.current_image].hide() self.current_image -= 1 self._Dots[self.current_image].set_layer(100) if self.current_image == 0: self._prev.set_image( self._next_prev_pixbufs[PREV_INACTIVE]) self._next.set_image(self._next_prev_pixbufs[NEXT]) elif (right or spr.type == 'next') and self.current_image < 8: self._Dots[self.current_image].hide() self.current_image += 1 self._Dots[self.current_image].set_layer(100) if self.current_image == 8: self._next.set_image( self._next_prev_pixbufs[NEXT_INACTIVE]) self._prev.set_image(self._next_prev_pixbufs[PREV]) elif spr.type not in ['prev', 'background'] and \ self.current_image < 8: self._Dots[self.current_image].hide() self.current_image += 1 self._Dots[self.current_image].set_layer(100) if self.current_image == 8: self._next.set_image( self._next_prev_pixbufs[NEXT_INACTIVE]) self._prev.set_image(self._next_prev_pixbufs[PREV]) self._parent.check_audio_status() self._parent.check_text_status() self._prev.set_layer(1) self._next.set_layer(1) return False def get_mode(self): return self._mode def set_mode(self, mode): self.current_image = 0 self._prev.set_image(self._next_prev_pixbufs[PREV_INACTIVE]) self._next.set_image(self._next_prev_pixbufs[NEXT]) if mode == 'array': self._mode = 'array' self._prev.hide() self._next.hide() else: self._mode = 'linear' self._prev.set_layer(1) self._next.set_layer(1) for i in range(9): if self._mode == 'array': self._dots[i].set_layer(100) self._Dots[i].hide() else: self._dots[i].hide() if self.current_image == i: self._Dots[i].set_layer(100) else: self._Dots[i].hide() def _all_clear(self): ''' Things to reinitialize when starting up a new game. ''' if self._timeout_id is not None: GObject.source_remove(self._timeout_id) self.set_mode(self._mode) if self._mode == 'array': for dot in self._dots: if dot.type != -1: dot.type = -1 dot.set_shape( self._new_dot_surface(self._colors[abs(dot.type)])) dot.set_label('?') else: for dot in self._Dots: if dot.type != -1: dot.type = -1 dot.set_shape( self._new_dot_surface(self._colors[abs(dot.type)], large=True)) dot.set_label('?') self._dance_counter = 0 self._dance_step() def _dance_step(self): ''' Short animation before loading new game ''' if self._mode == 'array': for dot in self._dots: dot.set_shape( self._new_dot_surface(self._colors[int(uniform(0, 3))])) else: self._Dots[0].set_shape( self._new_dot_surface(self._colors[int(uniform(0, 3))], large=True)) self._dance_counter += 1 if self._dance_counter < 10: self._timeout_id = GObject.timeout_add(500, self._dance_step) else: self._new_images() def new_game(self): ''' Start a new game. ''' self._all_clear() def _new_images(self): ''' Select pictures at random ''' used_images = [0] * self.number_of_images for i in range(9): random_selection = int(uniform(0, self.number_of_images)) while used_images[random_selection] != 0: random_selection = int(uniform(0, self.number_of_images)) used_images[random_selection] = 1 self._dots[i].set_label('') self._dots[i].type = random_selection self._dots[i].set_shape( self._new_dot_surface(image=self._dots[i].type)) self._Dots[i].set_label('') self._Dots[i].type = self._dots[i].type self._Dots[i].set_shape( self._new_dot_surface(image=self._Dots[i].type, large=True)) if self._mode == 'array': self._dots[i].set_layer(100) self._Dots[i].hide() else: if self.current_image == i: self._Dots[i].set_layer(100) else: self._Dots[i].hide() self._dots[i].hide() if self.we_are_sharing: self._parent.send_new_images() def restore_game(self, dot_list): ''' Restore a game from the Journal or share ''' self.set_mode(self._mode) for i, dot in enumerate(dot_list): self._dots[i].type = dot self._dots[i].set_shape( self._new_dot_surface(image=self._dots[i].type)) self._dots[i].set_label('') self._Dots[i].type = dot self._Dots[i].set_shape( self._new_dot_surface(image=self._Dots[i].type, large=True)) self._Dots[i].set_label('') if self._mode == 'array': self._dots[i].set_layer(100) self._Dots[i].hide() else: if self.current_image == i: self._Dots[i].set_layer(100) else: self._Dots[i].hide() self._dots[i].hide() def save_game(self): ''' Return dot list for saving to Journal or sharing ''' dot_list = [] for dot in self._dots: dot_list.append(dot.type) return dot_list def set_sharing(self, share=True): self.we_are_sharing = share def _grid_to_dot(self, pos): ''' calculate the dot index from a column and row in the grid ''' return pos[0] + pos[1] * 3 def _dot_to_grid(self, dot): ''' calculate the grid column and row for a dot ''' return [dot % 3, int(dot / 3)] def __draw_cb(self, canvas, cr): self._sprites.redraw_sprites(cr=cr) def __expose_cb(self, win, event): ''' Callback to handle window expose events ''' self.do_expose_event(event) return True # Handle the expose-event by drawing def do_expose_event(self, event): # Create the cairo context cr = self._canvas.window.cairo_create() # Restrict Cairo to the exposed area; avoid extra work cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list if cr is not None: self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): Gtk.main_quit() def export(self): ''' Write dot to cairo surface. ''' if self._mode == 'array': w = h = int(4 * self._space + 3 * self._dot_size) png_surface = cairo.ImageSurface(cairo.FORMAT_RGB24, w, h) cr = cairo.Context(png_surface) cr.set_source_rgb(192, 192, 192) cr.rectangle(0, 0, w, h) cr.fill() for i in range(9): y = self._space + int(i / 3.) * (self._dot_size + self._space) x = self._space + (i % 3) * (self._dot_size + self._space) cr.save() cr.set_source_surface(self._dots[i].images[0], x, y) cr.rectangle(x, y, self._dot_size, self._dot_size) cr.fill() cr.restore() else: w = h = int(2 * self._space + 3 * self._dot_size) png_surface = cairo.ImageSurface(cairo.FORMAT_RGB24, w, h) cr = cairo.Context(png_surface) cr.set_source_rgb(192, 192, 192) cr.rectangle(0, 0, w, h) cr.fill() y = self._space x = self._space cr.save() cr.set_source_surface(self._Dots[self.current_image].images[0], x, y) cr.rectangle(x, y, 3 * self._dot_size, 3 * self._dot_size) cr.fill() cr.restore() return png_surface def _new_dot_surface(self, color='#000000', image=None, large=False): ''' generate a dot of a color color ''' if large: size = self._dot_size * 3 else: size = self._dot_size self._svg_width = size self._svg_height = size if image is None: # color dot self._stroke = color self._fill = color pixbuf = svg_str_to_pixbuf(self._header() + self._circle(size / 2., size / 2., size / 2.) + self._footer()) else: if USE_ART4APPS: word = self._art4apps.get_words()[image] try: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( self._art4apps.get_image_filename(word), size, size) except Exception, e: _logger.error('new dot surface %s %s: %s' % (image, word, e)) word = 'zebra' # default in case image is not found pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( self._art4apps.get_image_filename(word), size, size) else:
class Yupana(): def __init__(self, canvas, parent=None, colors=['#A0FFA0', '#FF8080']): self._activity = parent self._colors = ['#FFFFFF'] self._colors.append(colors[0]) self._colors.append(colors[1]) self._colors.append('#000000') self._canvas = canvas if parent is not None: parent.show_all() self._parent = parent self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.connect("draw", self.__draw_cb) self._canvas.connect("button-press-event", self._button_press_cb) self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - (GRID_CELL_SIZE * 1.5) self._scale = self._width / (20 * DOT_SIZE * 1.1) self._dot_size = int(DOT_SIZE * self._scale) self._space = int(self._dot_size / 5.) self.we_are_sharing = False self._sum = 0 self._mode = 'ten' self.custom = [1, 1, 1, 1, 10] # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) Sprite(self._sprites, 0, 0, self._box(self._width, self._height, color=colors[1])) self._number_box = Sprite(self._sprites, 0, 0, self._box( self._width, 2 * self._dot_size, color=colors[1])) self._number_box.set_label_attributes(48) self._dots = [] for p in range(SIX): y = self._height - self._space Sprite(self._sprites, 0, y, self._line(vertical=False)) x = int(p * self._width / 6) + self._space y -= self._dot_size for d in range(3): # bottom of fives row self._dots.append( Sprite(self._sprites, x, y, self._new_dot(self._colors[0]))) self._dots[-1].type = 0 # not set # self._dots[-1].set_label_color('white') x += self._dot_size + self._space x = int((p * self._width / 6.) + self._dot_size / 2.) + self._space y -= self._dot_size + self._space for d in range(2): # top of fives row self._dots.append( Sprite(self._sprites, x, y, self._new_dot(self._colors[0]))) self._dots[-1].type = 0 # not set # self._dots[-1].set_label_color('white') x += self._dot_size + self._space y -= self._dot_size Sprite(self._sprites, 0, y, self._line(vertical=False)) x = int((p * self._width / 6.) + self._dot_size / 2.) + self._space y -= self._dot_size for d in range(2): # bottom of threes row self._dots.append( Sprite(self._sprites, x, y, self._new_dot(self._colors[0]))) self._dots[-1].type = 0 # not set # self._dots[-1].set_label_color('white') x += self._dot_size + self._space x = int((p * self._width / 6.) + self._dot_size) + self._space y -= self._dot_size + self._space for d in range(1): # top of threes row self._dots.append( Sprite(self._sprites, x, y, self._new_dot(self._colors[0]))) self._dots[-1].type = 0 # not set # self._dots[-1].set_label_color('white') x += self._dot_size + self._space y -= self._dot_size Sprite(self._sprites, 0, y, self._line(vertical=False)) x = int((p * self._width / 6.) + self._dot_size / 2.) + self._space y -= self._dot_size for d in range(2): # twos row self._dots.append( Sprite(self._sprites, x, y, self._new_dot(self._colors[0]))) self._dots[-1].type = 0 # not set # self._dots[-1].set_label_color('white') x += self._dot_size + self._space y -= self._dot_size Sprite(self._sprites, 0, y, self._line(vertical=False)) x = int((p * self._width / 6.) + self._dot_size) + self._space y -= self._dot_size for d in range(1): # ones row self._dots.append( Sprite(self._sprites, x, y, self._new_dot(self._colors[0]))) self._dots[-1].type = 0 # not set # self._dots[-1].set_label_color('white') x += self._dot_size + self._space y -= self._dot_size Sprite(self._sprites, 0, y, self._line(vertical=False)) for p in range(SIX - 1): x = int((p + 1) * self._width / 6) Sprite(self._sprites, x - 1, y, self._line(vertical=True)) # and initialize a few variables we'll need. self._all_clear() def _all_clear(self): ''' Things to reinitialize when starting up a new yupana. ''' self._sum = 0 for dot in self._dots: if dot.type > 0: dot.type = 0 dot.set_shape(self._new_dot(self._colors[0])) dot.set_label('') self._set_label(str(self._sum)) def _initiating(self): return self._activity.initiating def new_yupana(self, mode=None): ''' Create a new yupana. ''' self._all_clear() if mode is not None: self._mode = mode o = (SIX - 1) * (TEN + 1) # only label units if mode == 'ten': for i in range(TEN + 1): self._dots[o + i].set_label('1') self._dots[o - 1].set_label('10') elif mode == 'twenty': for i in range(TEN + 1): if i in [7, 10]: self._dots[o + i].set_label('1') else: self._dots[o + i].set_label('2') self._dots[o - 1].set_label('20') elif mode == 'factor': for i in range(TEN + 1): if i in [10]: self._dots[o + i].set_label('1') elif i in [8, 9]: self._dots[o + i].set_label('2') elif i in [5, 6, 7]: self._dots[o + i].set_label('3') else: self._dots[o + i].set_label('5') self._dots[o - 1].set_label('10') elif mode == 'fibonacci': for i in range(TEN + 1): if i in [10]: self._dots[o + i].set_label('1') elif i in [8, 9]: self._dots[o + i].set_label('2') elif i in [5, 6, 7]: self._dots[o + i].set_label('5') else: self._dots[o + i].set_label('20') self._dots[o - 1].set_label('60') else: # custom for i in range(TEN + 1): if i in [10]: self._dots[o + i].set_label(str(self.custom[0])) elif i in [8, 9]: self._dots[o + i].set_label(str(self.custom[1])) elif i in [5, 6, 7]: self._dots[o + i].set_label(str(self.custom[2])) else: self._dots[o + i].set_label(str(self.custom[3])) self._dots[o - 1].set_label(str(self.custom[4])) if self.we_are_sharing: _logger.debug('sending a new yupana') self._parent.send_new_yupana() def restore_yupana(self, dot_list): ''' Restore a yumpana from the Journal or share ''' for i, dot in enumerate(dot_list): self._dots[i].type = dot self._dots[i].set_shape(self._new_dot( self._colors[self._dots[i].type])) if self._dots[i].type == 1: self._sum += self._calc_bead_value(i) self._set_label(str(self._sum)) def save_yupana(self): ''' Return dot list and orientation for saving to Journal or sharing ''' dot_list = [] for dot in self._dots: dot_list.append(dot.type) return [self._mode, dot_list] def _set_label(self, string): ''' Set the label in the toolbar or the window frame. ''' self._number_box.set_label(string) # self._activity.status.set_label(string) def _button_press_cb(self, win, event): win.grab_focus() x, y = map(int, event.get_coords()) spr = self._sprites.find_sprite((x, y)) if spr == None: return if spr.type is not None: spr.type += 1 spr.type %= 2 spr.set_shape(self._new_dot(self._colors[spr.type])) if self.we_are_sharing: _logger.debug('sending a click to the share') self._parent.send_dot_click(self._dots.index(spr), spr.type) if spr.type == 1: self._sum += self._calc_bead_value(self._dots.index(spr)) else: self._sum -= self._calc_bead_value(self._dots.index(spr)) self._set_label(str(self._sum)) return True def _calc_bead_value(self, i): ''' Calculate a bead value based on the index and the mode ''' e = 5 - i / (TEN + 1) m = i % 11 if self._mode == 'ten': return 10 ** e elif self._mode == 'twenty': if m in [7, 10]: return 20 ** e else: return (20 ** e) * 2 elif self._mode == 'factor': if m in [10]: return 10 ** e elif m in [8, 9]: return (10 ** e) * 2 elif m in [5, 6, 7]: return (10 ** e) * 3 else: return (10 ** e) * 5 elif self._mode == 'fibonacci': if m in [10]: return 60 ** e elif m in [8, 9]: return (60 ** e) * 2 elif m in [5, 6, 7]: return (60 ** e) * 5 else: return (60 ** e) * 20 else: # custom if m in [10]: return (self.custom[4] ** e) * self.custom[0] elif m in [8, 9]: return (self.custom[4] ** e) * self.custom[1] elif m in [5, 6, 7]: return (self.custom[4] ** e) * self.custom[2] else: return (self.custom[4] ** e) * self.custom[3] def remote_button_press(self, dot, color): ''' Receive a button press from a sharer ''' self._dots[dot].type = color self._dots[dot].set_shape(self._new_dot(self._colors[color])) def set_sharing(self, share=True): _logger.debug('enabling sharing') self.we_are_sharing = share def _grid_to_dot(self, pos): ''' calculate the dot index from a column and row in the grid ''' return pos[0] + pos[1] * TEN def _dot_to_grid(self, dot): ''' calculate the grid column and row for a dot ''' return [dot % TEN, int(dot / TEN)] def __draw_cb(self, canvas, cr): self._sprites.redraw_sprites(cr=cr) def do_expose_event(self, event): ''' Handle the expose-event by drawing ''' # Restrict Cairo to the exposed area cr = self._canvas.window.cairo_create() cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): Gtk.main_quit() def _new_dot(self, color): ''' generate a dot of a color color ''' def darken(color): ''' return a darker color than color ''' gdk_fill_color = Gdk.color_parse(self._fill) gdk_fill_dark_color = Gdk.Color( int(gdk_fill_color.red * 0.5), int(gdk_fill_color.green * 0.5), int(gdk_fill_color.blue * 0.5)).to_string() return str(gdk_fill_dark_color) self._dot_cache = {} if not color in self._dot_cache: self._stroke = color self._fill = color self._fill_dark = darken(color) self._svg_width = self._dot_size self._svg_height = self._dot_size if color in ['#FFFFFF', '#000000']: pixbuf = svg_str_to_pixbuf( self._header() + \ self._circle(self._dot_size / 2., self._dot_size / 2., self._dot_size / 2.) + \ self._footer()) else: pixbuf = svg_str_to_pixbuf( self._header() + \ self._def(self._dot_size) + \ self._gradient(self._dot_size / 2., self._dot_size / 2., self._dot_size / 2.) + \ self._footer()) surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self._svg_width, self._svg_height) context = cairo.Context(surface) Gdk.cairo_set_source_pixbuf(context, pixbuf, 0, 0) context.rectangle(0, 0, self._svg_width, self._svg_height) context.fill() self._dot_cache[color] = surface return self._dot_cache[color] def _line(self, vertical=True): ''' Generate a center line ''' if vertical: self._svg_width = 3 self._svg_height = self._dot_size * 10 + self._space * 2 return svg_str_to_pixbuf( self._header() + \ self._rect(3, self._dot_size * 10 + self._space * 2, 0, 0) + \ self._footer()) else: self._svg_width = self._width self._svg_height = 3 return svg_str_to_pixbuf( self._header() + \ self._rect(self._width, 3, 0, 0) + \ self._footer()) def _box(self, w, h, color='white'): ''' Generate a box ''' self._svg_width = w self._svg_height = h return svg_str_to_pixbuf( self._header() + \ self._rect(self._svg_width, self._svg_height, 0, 0, color=color) + \ self._footer()) def _header(self): return '<svg\n' + 'xmlns:svg="http://www.w3.org/2000/svg"\n' + \ 'xmlns="http://www.w3.org/2000/svg"\n' + \ 'xmlns:xlink="http://www.w3.org/1999/xlink"\n' + \ 'version="1.1"\n' + 'width="' + str(self._svg_width) + '"\n' + \ 'height="' + str(self._svg_height) + '">\n' def _rect(self, w, h, x, y, color='black'): svg_string = ' <rect\n' svg_string += ' width="%f"\n' % (w) svg_string += ' height="%f"\n' % (h) svg_string += ' rx="%f"\n' % (0) svg_string += ' ry="%f"\n' % (0) svg_string += ' x="%f"\n' % (x) svg_string += ' y="%f"\n' % (y) if color == 'black': svg_string += 'style="fill:#000000;stroke:#000000;"/>\n' elif color == 'white': svg_string += 'style="fill:#ffffff;stroke:#ffffff;"/>\n' else: svg_string += 'style="fill:%s;stroke:%s;"/>\n' % (color, color) return svg_string def _circle(self, r, cx, cy): scale = (DOT_SIZE * self._scale) / 55. return '\ <g transform="matrix(%f,0,0,%f,0,0)">\ <path\ d="m 35.798426,4.2187227 c -2.210658,0.9528967 -4.993612,-0.9110169 -7.221856,0 C 23.805784,6.1692574 20.658687,10.945585 17.543179,15.051507 13.020442,21.012013 7.910957,27.325787 6.7103942,34.711004 6.0558895,38.737163 6.434461,43.510925 8.917073,46.747431 c 3.604523,4.699107 15.24614,7.62307 16.048569,7.62307 0.802429,0 8.366957,0.46766 12.036427,-1.203642 2.841316,-1.294111 5.173945,-3.766846 6.820641,-6.419428 2.543728,-4.097563 3.563068,-9.062928 4.21275,-13.841891 C 49.107723,25.018147 48.401726,15.967648 47.433639,9.0332932 47.09109,6.5796321 43.508442,7.2266282 42.329009,5.7211058 41.256823,4.3524824 42.197481,1.860825 40.813604,0.80840168 40.384481,0.48205899 39.716131,0.42556727 39.208747,0.60779459 37.650593,1.1674066 37.318797,3.5633724 35.798426,4.2187227 z"\ style="fill:none;fill-opacity:1;stroke:%s;stroke-width:3.0" />\ </g>' % ( scale, scale, self._colors[1]) def _gradient(self, r, cx, cy): scale = (DOT_SIZE * self._scale) / 55. return '\ <defs>\ <linearGradient\ id="linearGradient3769">\ <stop\ id="stop3771"\ style="stop-color:#ffff00;stop-opacity:1"\ offset="0" />\ <stop\ id="stop3773"\ style="stop-color:#ffff00;stop-opacity:0"\ offset="1" />\ </linearGradient>\ <linearGradient\ x1="10.761448"\ y1="41.003559"\ x2="56.70686"\ y2="41.003559"\ id="linearGradient2999"\ xlink:href="#linearGradient3769"\ gradientUnits="userSpaceOnUse"\ gradientTransform="matrix(0.93094239,0,0,0.93094239,-3.9217825,-2.4013121)" />\ </defs>\ <g transform="matrix(%f,0,0,%f,0,0)">\ <path\ d="m 35.798426,4.2187227 c -2.210658,0.9528967 -4.993612,-0.9110169 -7.221856,0 C 23.805784,6.1692574 20.658687,10.945585 17.543179,15.051507 13.020442,21.012013 7.910957,27.325787 6.7103942,34.711004 6.0558895,38.737163 6.434461,43.510925 8.917073,46.747431 c 3.604523,4.699107 15.24614,7.62307 16.048569,7.62307 0.802429,0 8.366957,0.46766 12.036427,-1.203642 2.841316,-1.294111 5.173945,-3.766846 6.820641,-6.419428 2.543728,-4.097563 3.563068,-9.062928 4.21275,-13.841891 C 49.107723,25.018147 48.401726,15.967648 47.433639,9.0332932 47.09109,6.5796321 43.508442,7.2266282 42.329009,5.7211058 41.256823,4.3524824 42.197481,1.860825 40.813604,0.80840168 40.384481,0.48205899 39.716131,0.42556727 39.208747,0.60779459 37.650593,1.1674066 37.318797,3.5633724 35.798426,4.2187227 z"\ style="fill:#fffec2;fill-opacity:1;stroke:#878600;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />\ <path\ d="m 15.11608,18.808876 c 1.271657,-1.444003 4.153991,-3.145785 5.495465,-1.7664 2.950062,3.033434 -6.07961,8.17155 -4.219732,11.972265 0.545606,1.114961 2.322391,1.452799 3.532799,1.177599 5.458966,-1.241154 6.490591,-12.132334 12.070397,-11.677864 1.584527,0.129058 2.526156,2.269906 2.845867,3.827199 0.453143,2.207236 -1.962667,6.182399 -1.570133,6.574932 0.392533,0.392533 2.371401,0.909584 3.140266,0.196266 1.91857,-1.779962 -0.490667,-7.752531 0.09813,-7.850664 0.5888,-0.09813 4.421663,2.851694 5.789865,5.004799 0.583188,0.917747 -0.188581,2.956817 0.8832,3.140266 2.128963,0.364398 1.601562,-5.672021 3.729066,-5.299199 1.836829,0.321884 1.450925,3.532631 1.471999,5.397332 0.06743,5.965698 -0.565586,12.731224 -4.317865,17.369596 -3.846028,4.75426 -10.320976,8.31978 -16.388263,7.556266 C 22.030921,53.720741 16.615679,52.58734 11.485147,49.131043 7.9833717,46.771994 6.8028191,42.063042 6.5784815,37.846738 6.3607378,33.754359 8.3381535,29.765466 10.111281,26.070741 c 1.271951,-2.650408 2.940517,-4.917813 5.004799,-7.261865 z"\ style="fill:url(#linearGradient2999);fill-opacity:1;stroke:none" />\ <path\ d="m 32.382709,4.7758124 c -0.123616,1.0811396 1.753928,2.8458658 2.728329,2.9439992 0.974405,0.098134 6.718874,0.7298319 9.159392,-0.1962668 0.820281,-0.3112699 0.968884,-0.9547989 0.974407,-1.4719993 0.02053,-1.9240971 0.03247,-4.7715376 -3.507853,-5.49546551 C 39.556079,0.11012647 37.217081,1.4131653 35.500801,2.2243463 34.054814,2.9077752 32.496703,3.7788369 32.382709,4.7758124 z"\ style="fill:#b69556;fill-opacity:1;stroke:#b69556;stroke-width:1.31189477px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /></g>' % ( scale, scale) def _def(self, r): return ' <defs>\ <linearGradient\ id="linearGradient3755">\ <stop\ id="stop3757"\ style="stop-color:%s;stop-opacity:1"\ offset="0" />\ <stop\ id="stop3759"\ style="stop-color:%s;stop-opacity:1"\ offset="1" />\ </linearGradient>\ <radialGradient\ cx="0"\ cy="0"\ r="%f"\ fx="%f"\ fy="%f"\ id="radialGradient3761"\ xlink:href="#linearGradient3755"\ gradientUnits="userSpaceOnUse" />\ </defs>\ ' % (self._fill, self._fill_dark, r, r / 3, r / 3) def _footer(self): return '</svg>\n'
class Bounce(): ''' The Bounce class is used to define the ball and the user interaction. ''' def __init__(self, canvas, path, parent=None): ''' Initialize the canvas and set up the callbacks. ''' self._activity = parent self._fraction = None self._path = path if parent is None: # Starting from command line self._sugar = False else: # Starting from Sugar self._sugar = True self._canvas = canvas self._canvas.grab_focus() self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK) self._canvas.add_events(Gdk.EventMask.POINTER_MOTION_MASK) self._canvas.add_events(Gdk.EventMask.KEY_PRESS_MASK) self._canvas.add_events(Gdk.EventMask.KEY_RELEASE_MASK) self._canvas.connect('draw', self.__draw_cb) self._canvas.connect('button-press-event', self._button_press_cb) self._canvas.connect('button-release-event', self._button_release_cb) self._canvas.connect('key-press-event', self._keypress_cb) self._canvas.connect('key-release-event', self._keyrelease_cb) self._canvas.set_can_focus(True) self._canvas.grab_focus() self._sprites = Sprites(self._canvas) self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - GRID_CELL_SIZE self._scale = Gdk.Screen.height() / 900.0 self._timeout = None self.buddies = [] # used for sharing self._my_turn = False self.select_a_fraction = False self._easter_egg = int(uniform(1, 100)) # Find paths to sound files self._path_to_success = os.path.join(path, LAUGH) self._path_to_failure = os.path.join(path, CRASH) self._path_to_bubbles = os.path.join(path, BUBBLES) self._create_sprites(path) self.mode = 'fractions' self._challenge = 0 self._expert = False self._challenges = [] for challenge in CHALLENGES[self._challenge]: self._challenges.append(challenge) self._fraction = 0.5 # the target of the current challenge self._label = '1/2' # the label self.count = 0 # number of bounces played self._correct = 0 # number of correct answers self._press = None # sprite under mouse click self._new_bounce = False self._n = 0 self._accel_index = 0 self._accel_flip = False self._accel_xy = [0, 0] self._guess_orientation() self._dx = 0. # ball horizontal trajectory # acceleration (with dampening) self._ddy = (6.67 * self._height) / (STEPS * STEPS) self._dy = self._ddy * (1 - STEPS) / 2. # initial step size if self._sugar: if _is_tablet_mode(): self._activity.reset_label( _('Click the ball to start. Rock the computer left ' 'and right to move the ball.')) else: self._activity.reset_label( _('Click the ball to start. Then use the arrow keys to ' 'move the ball.')) def _accelerometer(self): return os.path.exists(ACCELEROMETER_DEVICE) and _is_tablet_mode() def configure_cb(self, event): self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - GRID_CELL_SIZE self._scale = Gdk.Screen.height() / 900.0 # We need to resize the backgrounds width, height = self._calc_background_size() for bg in self._backgrounds.keys(): if bg == 'custom': path = self._custom_dsobject.file_path pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( path, width, height) else: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._path, 'images', bg), width, height) if Gdk.Screen.height() > Gdk.Screen.width(): pixbuf = self._crop_to_portrait(pixbuf) self._backgrounds[bg] = pixbuf self._background = Sprite(self._sprites, 0, 0, self._backgrounds[self._current_bg]) self._background.set_layer(-100) self._background.type = 'background' # and resize and reposition the bars self.bar.resize_all() self.bar.show_bar(2) self._current_bar = self.bar.get_bar(2) # Calculate a new accerlation based on screen height. self._ddy = (6.67 * self._height) / (STEPS * STEPS) self._guess_orientation() def _create_sprites(self, path): ''' Create all of the sprites we'll need ''' self.smiley_graphic = svg_str_to_pixbuf(svg_from_file( os.path.join(path, 'images', 'smiley.svg'))) self.frown_graphic = svg_str_to_pixbuf(svg_from_file( os.path.join(path, 'images', 'frown.svg'))) self.blank_graphic = svg_str_to_pixbuf( svg_header(REWARD_HEIGHT, REWARD_HEIGHT, 1.0) + svg_rect(REWARD_HEIGHT, REWARD_HEIGHT, 5, 5, 0, 0, 'none', 'none') + svg_footer()) self.ball = Ball(self._sprites, os.path.join(path, 'images', 'soccerball.svg')) self._current_frame = 0 self.bar = Bar(self._sprites, self.ball.width(), COLORS) self._current_bar = self.bar.get_bar(2) self.ball_y_max = self.bar.bar_y() - self.ball.height() + \ int(BAR_HEIGHT / 2.) self.ball.move_ball((int((self._width - self.ball.width()) / 2), self.ball_y_max)) self._backgrounds = {} width, height = self._calc_background_size() pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(path, 'images', 'grass_background.png'), width, height) if Gdk.Screen.height() > Gdk.Screen.width(): pixbuf = self._crop_to_portrait(pixbuf) self._backgrounds['grass_background.png'] = pixbuf self._background = Sprite(self._sprites, 0, 0, pixbuf) self._background.set_layer(-100) self._background.type = 'background' self._current_bg = 'grass_background.png' def _crop_to_portrait(self, pixbuf): tmp = GdkPixbuf.Pixbuf.new(0, True, 8, Gdk.Screen.width(), Gdk.Screen.height()) x = int(Gdk.Screen.height() / 3) pixbuf.copy_area(x, 0, Gdk.Screen.width(), Gdk.Screen.height(), tmp, 0, 0) return tmp def _calc_background_size(self): if Gdk.Screen.height() > Gdk.Screen.width(): height = Gdk.Screen.height() return int(4 * height / 3), height else: width = Gdk.Screen.width() return width, int(3 * width / 4) def new_background_from_image(self, path, dsobject=None): if path is None: path = dsobject.file_path width, height = self._calc_background_size() pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( path, width, height) if Gdk.Screen.height() > Gdk.Screen.width(): pixbuf = self._crop_to_portrait(pixbuf) self._backgrounds['custom'] = pixbuf self.set_background('custom') self._custom_dsobject = dsobject self._current_bg = 'custom' def set_background(self, name): if not name in self._backgrounds: width, height = self._calc_background_size() pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._path, 'images', name), width, height) if Gdk.Screen.height() > Gdk.Screen.width(): pixbuf = self._crop_to_portrait(pixbuf) self._backgrounds[name] = pixbuf self._background.set_image(self._backgrounds[name]) self.bar.mark.hide() self._current_bar.hide() self.ball.ball.hide() self.do_expose_event() self.ball.ball.set_layer(3) self._current_bar.set_layer(2) self.bar.mark.set_layer(1) self._current_bg = name def pause(self): ''' Pause play when visibility changes ''' if self._timeout is not None: GObject.source_remove(self._timeout) self._timeout = None def we_are_sharing(self): ''' If there is more than one buddy, we are sharing. ''' if len(self.buddies) > 1: return True def its_my_turn(self): ''' When sharing, it is your turn... ''' GObject.timeout_add(1000, self._take_a_turn) def _take_a_turn(self): ''' On your turn, choose a fraction. ''' self._my_turn = True self.select_a_fraction = True self._activity.set_player_on_toolbar(self._activity.nick) self._activity.reset_label( _("Click on the bar to choose a fraction.")) def its_their_turn(self, nick): ''' When sharing, it is nick's turn... ''' GObject.timeout_add(1000, self._wait_your_turn, nick) def _wait_your_turn(self, nick): ''' Wait for nick to choose a fraction. ''' self._my_turn = False self._activity.set_player_on_toolbar(nick) self._activity.reset_label( _('Waiting for %(buddy)s') % {'buddy': nick}) def play_a_fraction(self, fraction): ''' Play this fraction ''' fraction_is_new = True for i, c in enumerate(self._challenges): if c[0] == fraction: fraction_is_new = False self._n = i break if fraction_is_new: self.add_fraction(fraction) self._n = len(self._challenges) self._choose_a_fraction() self._move_ball() def _button_press_cb(self, win, event): ''' Callback to handle the button presses ''' win.grab_focus() x, y = map(int, event.get_coords()) self._press = self._sprites.find_sprite((x, y)) return True def _button_release_cb(self, win, event): ''' Callback to handle the button releases ''' win.grab_focus() x, y = map(int, event.get_coords()) if self._press is not None: if self.we_are_sharing(): if self.select_a_fraction and self._press == self._current_bar: # Find the fraction closest to the click fraction = self._search_challenges( (x - self.bar.bar_x()) / float(self.bar.width())) self.select_a_fraction = False self._activity.send_a_fraction(fraction) self.play_a_fraction(fraction) else: if self._timeout is None and self._press == self.ball.ball: self._choose_a_fraction() self._move_ball() return True def _search_challenges(self, f): ''' Find the fraction which is closest to f in the list. ''' dist = 1. closest = '1/2' for c in self._challenges: numden = c[0].split('/') delta = abs((float(numden[0]) / float(numden[1])) - f) if delta <= dist: dist = delta closest = c[0] return closest def _guess_orientation(self): if self._accelerometer(): fh = open(ACCELEROMETER_DEVICE) string = fh.read() fh.close() xyz = string[1:-2].split(',') x = int(xyz[0]) y = int(xyz[1]) self._accel_xy = [x, y] if abs(x) > abs(y): self._accel_index = 1 # Portrait mode self._accel_flip = x > 0 else: self._accel_index = 0 # Landscape mode self._accel_flip = y < 0 def _move_ball(self): ''' Move the ball and test boundary conditions ''' if self._new_bounce: self.bar.mark.move((0, self._height)) # hide the mark if not self.we_are_sharing(): self._choose_a_fraction() self._new_bounce = False self._dy = self._ddy * (1 - STEPS) / 2 # initial step size if self._accelerometer(): self._guess_orientation() self._dx = float(self._accel_xy[self._accel_index]) / 18. if self._accel_flip: self._dx *= -1 if self.ball.ball_x() + self._dx > 0 and \ self.ball.ball_x() + self._dx < self._width - self.ball.width(): self.ball.move_ball_relative((int(self._dx), int(self._dy))) else: self.ball.move_ball_relative((0, int(self._dy))) # speed up ball in x while key is pressed self._dx *= DDX # accelerate in y self._dy += self._ddy # Calculate a new ball_y_max depending on the x position self.ball_y_max = self.bar.bar_y() - self.ball.height() + \ self._wedge_offset() if self.ball.ball_y() >= self.ball_y_max: # hit the bottom self.ball.move_ball((self.ball.ball_x(), self.ball_y_max)) self._test() self._new_bounce = True if self.we_are_sharing(): if self._my_turn: # Let the next player know it is their turn. i = (self.buddies.index(self._activity.nick) + 1) % \ len(self.buddies) self.its_their_turn(self.buddies[i]) self._activity.send_event('', {"data": (self.buddies[i])}) else: if not self.we_are_sharing() and self._easter_egg_test(): self._animate() else: self._timeout = GObject.timeout_add( max(STEP_PAUSE, BOUNCE_PAUSE - self.count * STEP_PAUSE), self._move_ball) else: self._timeout = GObject.timeout_add(STEP_PAUSE, self._move_ball) def _wedge_offset(self): return int(BAR_HEIGHT * (1 - (self.ball.ball_x() / float(self.bar.width())))) def _mark_offset(self, x): return int(BAR_HEIGHT * (1 - (x / float(self.bar.width())))) - 12 def _animate(self): ''' A little Easter Egg just for fun. ''' if self._new_bounce: self._dy = self._ddy * (1 - STEPS) / 2 # initial step size self._new_bounce = False self._current_frame = 0 self._frame_counter = 0 self.ball.move_frame(self._current_frame, (self.ball.ball_x(), self.ball.ball_y())) self.ball.move_ball((self.ball.ball_x(), self._height)) GObject.idle_add(play_audio_from_file, self, self._path_to_bubbles) if self._accelerometer(): fh = open(ACCELEROMETER_DEVICE) string = fh.read() xyz = string[1:-2].split(',') self._dx = float(xyz[0]) / 18. fh.close() else: self._dx = uniform(-int(DX * self._scale), int(DX * self._scale)) self.ball.move_frame_relative( self._current_frame, (int(self._dx), int(self._dy))) self._dy += self._ddy self._frame_counter += 1 self._current_frame = self.ball.next_frame(self._frame_counter) if self.ball.frame_y(self._current_frame) >= self.ball_y_max: # hit the bottom self.ball.move_ball((self.ball.ball_x(), self.ball_y_max)) self.ball.hide_frames() self._test(easter_egg=True) self._new_bounce = True self._timeout = GObject.timeout_add(BOUNCE_PAUSE, self._move_ball) else: GObject.timeout_add(STEP_PAUSE, self._animate) def add_fraction(self, string): ''' Add a new challenge; set bar to 2x demominator ''' numden = string.split('/', 2) self._challenges.append([string, int(numden[1]), 0]) def _get_new_fraction(self): ''' Select a new fraction challenge from the table ''' if not self.we_are_sharing(): n = int(uniform(0, len(self._challenges))) else: n = self._n fstr = self._challenges[n][0] if '/' in fstr: # fraction numden = fstr.split('/', 2) fraction = float(numden[0].strip()) / float(numden[1].strip()) elif '%' in fstr: # percentage fraction = float(fstr.strip().strip('%').strip()) / 100. else: # To do: add support for decimals (using locale) _logger.debug('Could not parse challenge (%s)', fstr) fstr = '1/2' fraction = 0.5 return fraction, fstr, n def _choose_a_fraction(self): ''' choose a new fraction and set the corresponding bar ''' # Don't repeat the same fraction twice in a row fraction, fstr, n = self._get_new_fraction() if not self.we_are_sharing(): while fraction == self._fraction: fraction, fstr, n = self._get_new_fraction() self._fraction = fraction self._n = n if self.mode == 'percents': self._label = str(int(self._fraction * 100 + 0.5)) + '%' else: # percentage self._label = fstr if self.mode == 'sectors': self.ball.new_ball_from_fraction(self._fraction) if not Gdk.Screen.width() < 1024: self._activity.reset_label( _('Bounce the ball to a position ' '%(fraction)s of the way from the left side of the bar.') % {'fraction': self._label}) else: self._activity.reset_label(_('Bounce the ball to %(fraction)s') % {'fraction': self._label}) self.ball.ball.set_label(self._label) self.bar.hide_bars() if self._expert: # Show two-segment bar in expert mode nseg = 2 else: if self.mode == 'percents': nseg = 10 else: nseg = self._challenges[self._n][1] # generate new bar on demand self._current_bar = self.bar.get_bar(nseg) self.bar.show_bar(nseg) def _easter_egg_test(self): ''' Test to see if we show the Easter Egg ''' delta = self.ball.width() / 8 x = self.ball.ball_x() + self.ball.width() / 2 f = self.bar.width() * self._easter_egg / 100. if x > f - delta and x < f + delta: return True else: return False def _test(self, easter_egg=False): ''' Test to see if we estimated correctly ''' self._timeout = None if self._expert: delta = self.ball.width() / 6 else: delta = self.ball.width() / 3 x = self.ball.ball_x() + self.ball.width() / 2 f = int(self._fraction * self.bar.width()) self.bar.mark.move((int(f - self.bar.mark_width() / 2), int(self.bar.bar_y() + self._mark_offset(f)))) if self._challenges[self._n][2] == 0: # label the column spr = Sprite(self._sprites, 0, 0, self.blank_graphic) spr.set_label(self._label) spr.move((int(self._n * 27), 0)) spr.set_layer(-1) self._challenges[self._n][2] += 1 if x > f - delta and x < f + delta: spr = Sprite(self._sprites, 0, 0, self.smiley_graphic) self._correct += 1 GObject.idle_add(play_audio_from_file, self, self._path_to_success) else: spr = Sprite(self._sprites, 0, 0, self.frown_graphic) GObject.idle_add(play_audio_from_file, self, self._path_to_failure) spr.move((int(self._n * 27), int(self._challenges[self._n][2] * 27))) spr.set_layer(-1) # after enough correct answers, up the difficulty if self._correct == len(self._challenges) * 2: self._challenge += 1 if self._challenge < len(CHALLENGES): for challenge in CHALLENGES[self._challenge]: self._challenges.append(challenge) else: self._expert = True self.count += 1 self._dx = 0. # stop horizontal movement between bounces def _keypress_cb(self, area, event): ''' Keypress: moving the slides with the arrow keys ''' k = Gdk.keyval_name(event.keyval) if k in ['KP_Page_Down', 'KP_Home', 'h', 'Left', 'KP_Left']: self._dx = -DX * self._scale elif k in ['KP_Page_Up', 'KP_End', 'l', 'Right', 'KP_Right']: self._dx = DX * self._scale elif k in ['Return']: self._choose_a_fraction() self._move_ball() else: self._dx = 0. if self._accel_flip: self._dx = -self._dx return True def _keyrelease_cb(self, area, event): ''' Keyrelease: stop horizontal movement ''' self._dx = 0. return True def __draw_cb(self, canvas, cr): self._sprites.redraw_sprites(cr=cr) def do_expose_event(self, event=None): ''' Handle the expose-event by drawing ''' # Restrict Cairo to the exposed area cr = self._activity.get_window().cairo_create() if event is None: cr.rectangle(0, 0, Gdk.Screen.width(), Gdk.Screen.height()) else: cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): ''' Callback to handle quit ''' Gtk.main_quit()