Ejemplo n.º 1
0
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'
Ejemplo n.º 2
0
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'
Ejemplo n.º 3
0
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'
Ejemplo n.º 4
0
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()
Ejemplo n.º 5
0
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()
Ejemplo n.º 6
0
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'
Ejemplo n.º 7
0
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'
Ejemplo n.º 8
0
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()
Ejemplo n.º 9
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)))
Ejemplo n.º 10
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
        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()
Ejemplo n.º 11
0
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
Ejemplo n.º 12
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()
Ejemplo n.º 13
0
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'
Ejemplo n.º 14
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.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
Ejemplo n.º 15
0
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)
Ejemplo n.º 16
0
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'
Ejemplo n.º 17
0
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()
Ejemplo n.º 18
0
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()
Ejemplo n.º 19
0
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)
Ejemplo n.º 20
0
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
Ejemplo n.º 21
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
Ejemplo n.º 22
0
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:
Ejemplo n.º 23
0
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
Ejemplo n.º 24
0
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'
Ejemplo n.º 25
0
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 '??'
Ejemplo n.º 26
0
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'
Ejemplo n.º 27
0
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'
Ejemplo n.º 28
0
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]
Ejemplo n.º 29
0
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"
Ejemplo n.º 30
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._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
Ejemplo n.º 31
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)))
Ejemplo n.º 32
0
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()
Ejemplo n.º 33
0
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()
Ejemplo n.º 34
0
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()
Ejemplo n.º 35
0
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:
Ejemplo n.º 36
0
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'
Ejemplo n.º 37
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
        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()