示例#1
0
class Tile:
    def __init__(self, sprites, svg, svgs, tile_type='tile', number=0):
        self.highlight = [svg_str_to_pixbuf(svg)]
        self.spr = Sprite(sprites, 0, 0, self.highlight[0])
        for s in svgs:
            self.highlight.append(svg_str_to_pixbuf(s))
        self.paths = []  # [[N, E, S, W], [N, E, S, W]]
        self.shape = None
        self.orientation = 0
        self.type = tile_type
        self.number = number
        self.value = 1
        self.spr.set_label_color('#FF0000')

    def set_value(self, value):
        self.value = value

    def get_value(self):
        return self.value

    def set_paths(self, paths):
        for c in paths:
            self.paths.append(c)

    def get_paths(self):
        return self.paths

    def reset(self):
        self.spr.set_layer(HIDE)
        self.shape = None
        self.spr.set_shape(self.highlight[0])
        while self.orientation != 0:
            self.rotate_clockwise()

    def set_shape(self, path):
        if self.shape is None:
            self.spr.set_shape(self.highlight[path + 1])
            self.shape = path
        elif self.shape != path:
            self.spr.set_shape(self.highlight[-1])

    def rotate_clockwise(self):
        """ rotate the tile and its paths """
        for i in range(len(self.paths)):
            west = self.paths[i][WEST]
            self.paths[i][WEST] = self.paths[i][SOUTH]
            self.paths[i][SOUTH] = self.paths[i][EAST]
            self.paths[i][EAST] = self.paths[i][NORTH]
            self.paths[i][NORTH] = west
        for h in range(len(self.highlight)):
            self.highlight[h] = self.highlight[h].rotate_simple(270)
        self.spr.set_shape(self.highlight[0])
        self.orientation += 90
        self.orientation %= 360

    def show_tile(self):
        self.spr.set_layer(CARDS)

    def hide(self):
        self.spr.move((-self.spr.get_dimensions()[0], 0))
示例#2
0
文件: tile.py 项目: erilyth/paths
class Tile:

    def __init__(self, sprites, svg, svgs, tile_type='tile', number=0):
        self.highlight = [svg_str_to_pixbuf(svg)]
        self.spr = Sprite(sprites, 0, 0, self.highlight[0])
        for s in svgs:
            self.highlight.append(svg_str_to_pixbuf(s))
        self.paths = []  # [[N, E, S, W], [N, E, S, W]]
        self.shape = None
        self.orientation = 0
        self.type = tile_type
        self.number = number
        self.value = 1
        self.spr.set_label_color('#FF0000')

    def set_value(self, value):
        self.value = value

    def get_value(self):
        return self.value

    def set_paths(self, paths):
        for c in paths:
            self.paths.append(c)

    def get_paths(self):
        return self.paths

    def reset(self):
        self.spr.set_layer(HIDE)
        self.shape = None
        self.spr.set_shape(self.highlight[0])
        while self.orientation != 0:
            self.rotate_clockwise()

    def set_shape(self, path):
        if self.shape is None:
            self.spr.set_shape(self.highlight[path + 1])
            self.shape = path
        elif self.shape != path:
            self.spr.set_shape(self.highlight[-1])

    def rotate_clockwise(self):
        """ rotate the tile and its paths """
        for i in range(len(self.paths)):
            west = self.paths[i][WEST]
            self.paths[i][WEST] = self.paths[i][SOUTH]
            self.paths[i][SOUTH] = self.paths[i][EAST]
            self.paths[i][EAST] = self.paths[i][NORTH]
            self.paths[i][NORTH] = west
        for h in range(len(self.highlight)):
            self.highlight[h] = self.highlight[h].rotate_simple(270)
        self.spr.set_shape(self.highlight[0])
        self.orientation += 90
        self.orientation %= 360

    def show_tile(self):
        self.spr.set_layer(CARDS)

    def hide(self):
        self.spr.move((-self.spr.get_dimensions()[0], 0))
示例#3
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()
示例#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.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()
示例#5
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'
class AbacusGeneric():
    """ A generic abacus: a frame, rods, and beads. """

    def __init__(self, abacus):
        """ Specify parameters that define the abacus """
        self.abacus = abacus
        self.set_parameters()
        self.create()

    def set_parameters(self):
        """ Define the physical paramters. """
        self.name = "suanpan"
        self.num_rods = 15
        self.bot_beads = 5
        self.top_beads = 2
        self.base = 10
        self.top_factor = 5

    def create(self):
        """ Create and position the sprites that compose the abacus """

        # Width is a function of the number of rods
        self.frame_width = self.num_rods*(BWIDTH+BOFFSET)+FSTROKE*2

        # Height is a function of the number of beads
        if self.top_beads > 0:
            self.frame_height = (self.bot_beads+self.top_beads+5)*BHEIGHT +\
                                FSTROKE*2
        else:
            self.frame_height = (self.bot_beads+2)*BHEIGHT + FSTROKE*2

        # Draw the frame...
        x = (self.abacus.width-(self.frame_width*self.abacus.scale))/2
        y = (self.abacus.height-(self.frame_height*self.abacus.scale))/2
        _frame = _svg_header(self.frame_width, self.frame_height,
                             self.abacus.scale) +\
                 _svg_rect(self.frame_width, self.frame_height, 
                           FSTROKE/2, FSTROKE/2, 0, 0, "#000000", "#000000") +\
                 _svg_rect(self.frame_width-(FSTROKE*2), 
                           self.frame_height-(FSTROKE*2), 0, 0,
                           FSTROKE, FSTROKE, "#808080", "#000000") +\
                 _svg_footer()
        self.frame = Sprite(self.abacus.sprites, x, y, 
                            _svg_str_to_pixbuf(_frame))
        self.frame.type = 'frame'

        # and then the rods and beads.
        self.rods = []
        self.beads = []
        x += FSTROKE*self.abacus.scale
        y += FSTROKE*self.abacus.scale

        self.draw_rods_and_beads(x, y)

        # Draw the dividing bar...
        _bar = _svg_header(self.frame_width-(FSTROKE*2), BHEIGHT,
                           self.abacus.scale) +\
               _svg_rect(self.frame_width-(FSTROKE*2), BHEIGHT, 0, 0, 0, 0,
                         "#000000", "#000000") +\
               _svg_footer()
        if self.top_beads > 0:
            self.bar = Sprite(self.abacus.sprites, x,
                              y+(self.top_beads+2)*BHEIGHT*self.abacus.scale,
                              _svg_str_to_pixbuf(_bar))
        else:
            self.bar = Sprite(self.abacus.sprites, x,
                              y-FSTROKE*self.abacus.scale,
                              _svg_str_to_pixbuf(_bar))

        self.bar.type = 'frame'
        self.bar.set_label_color('white')

        # and finally, the mark.
        _mark = _svg_header(20, 15, self.abacus.scale) +\
                _svg_indicator() +\
                _svg_footer()
        dx = (BWIDTH+BOFFSET)*self.abacus.scale
        self.mark = Sprite(self.abacus.sprites, x+(self.num_rods-1)*dx,
                           y-((FSTROKE/2)*self.abacus.scale),
                           _svg_str_to_pixbuf(_mark))
        self.mark.type = 'mark'

    def draw_rods_and_beads(self, x, y):
        """ Draw the rods and beads """
        _white = _svg_header(BWIDTH, BHEIGHT, self.abacus.scale) +\
                 _svg_bead("#ffffff", "#000000") +\
                 _svg_footer()
        _yellow1 = _svg_header(BWIDTH, BHEIGHT, self.abacus.scale) +\
                 _svg_bead("#ffffcc", "#000000") +\
                 _svg_footer()
        _yellow2 = _svg_header(BWIDTH, BHEIGHT, self.abacus.scale) +\
                 _svg_bead("#ffff88", "#000000") +\
                 _svg_footer()
        _yellow3 = _svg_header(BWIDTH, BHEIGHT, self.abacus.scale) +\
                 _svg_bead("#ffff00", "#000000") +\
                 _svg_footer()
        self.colors = [_svg_str_to_pixbuf(_white),
                       _svg_str_to_pixbuf(_yellow1),
                       _svg_str_to_pixbuf(_yellow2),
                       _svg_str_to_pixbuf(_yellow3)]

        dx = (BWIDTH+BOFFSET)*self.abacus.scale
        bo =  (BWIDTH-BOFFSET)*self.abacus.scale/4
        ro =  (BWIDTH+5)*self.abacus.scale/2
        for i in range(self.num_rods):
            _rod = _svg_header(10, self.frame_height-(FSTROKE*2),
                               self.abacus.scale) +\
                   _svg_rect(10, self.frame_height-(FSTROKE*2), 0, 0, 0, 0,
                             ROD_COLORS[i%len(ROD_COLORS)], "#404040") +\
                   _svg_footer()
            self.rods.append(Sprite(self.abacus.sprites, x+i*dx+ro, y,
                                    _svg_str_to_pixbuf(_rod)))

            for b in range(self.top_beads):
                self.beads.append(Bead(Sprite(self.abacus.sprites, x+i*dx+bo,
                                              y+b*BHEIGHT*self.abacus.scale,
                                              self.colors[0]),
                                       2*BHEIGHT*self.abacus.scale,
                                       self.top_factor*(pow(self.base,
                                                        self.num_rods-i-1))))
            for b in range(self.bot_beads):
                if self.top_beads > 0:
                    self.beads.append(Bead(Sprite(self.abacus.sprites,
                                                  x+i*dx+bo,
                                                  y+(self.top_beads+5+b)*\
                                                  BHEIGHT*self.abacus.scale,
                                                  self.colors[0]),
                                           2*BHEIGHT*self.abacus.scale,
                                           pow(self.base,self.num_rods-i-1)))
                else:
                    self.beads.append(Bead(Sprite(self.abacus.sprites,
                                                  x+i*dx+bo,
                                                  y+(2+b)*BHEIGHT\
                                                  *self.abacus.scale,
                                                  self.colors[0]),
                                           2*BHEIGHT*self.abacus.scale,
                                           pow(self.base,self.num_rods-i-1)))

        for rod in self.rods:
            rod.type = "frame"

    def hide(self):
        """ Hide the rod, beads, mark, and frame. """
        for rod in self.rods:
            rod.hide()
        for bead in self.beads:
            bead.hide()
        self.bar.hide()
        self.frame.hide()
        self.mark.hide()

    def show(self):
        """ Show the rod, beads, mark, and frame. """
        self.frame.set_layer(FRAME_LAYER)
        for rod in self.rods:
            rod.set_layer(ROD_LAYER)
        for bead in self.beads:
            bead.show()
        self.bar.set_layer(BAR_LAYER)
        self.mark.set_layer(MARK_LAYER)

    def set_value(self, string):
        """ Set abacus to value in string """
        _logger.debug("restoring %s: %s" % (self.name, string))
        # String has two bytes per column.
        v = []
        for r in range(self.num_rods):
            v.append(0)

        # Convert string to column values.
        if len(string) == 2*self.num_rods:
            for i in range(self.num_rods):
                v[self.num_rods-i-1] = int(
                              string[2*self.num_rods-i*2-1:2*self.num_rods-i*2])
        else:
            _logger.debug("bad saved string %s (%d != 2*%d)" % (string,
                          len(string), self.num_rods))

        # Move the beads to correspond to column values.
        for r in range(self.num_rods):
            self.set_rod_value(r, v[r])

    def set_rod_value(self, r, v):
        """ Move beads on rod r to represent value v """
        bot = v % self.top_factor
        top = (v-bot)/self.top_factor
        top_bead_index = r*(self.top_beads+self.bot_beads)
        bot_bead_index = r*(self.top_beads+self.bot_beads)+self.top_beads

        # Clear the top.
        for i in range(self.top_beads):
            if self.beads[top_bead_index+i].get_state() == 1:
                self.beads[top_bead_index+i].move_up()
        # Clear the bottom.
        for i in range(self.bot_beads):
            if self.beads[bot_bead_index+i].get_state() == 1:
                self.beads[bot_bead_index+i].move_down()
        # Set the top.
        for i in range(top):
            self.beads[top_bead_index+self.top_beads-i-1].move_down()
        # Set the bottom
        for i in range(bot):
            self.beads[bot_bead_index+i].move_up()

    def value(self, count_beads=False):
        """ Return a string representing the value of each rod. """
        string = ''
        v = []
        for r in range(self.num_rods+1): # +1 for overflow
            v.append(0)

        # Tally the values on each rod.
        for i, bead in enumerate(self.beads):
            r = i/(self.top_beads+self.bot_beads)
            j = i % (self.top_beads+self.bot_beads)
            if bead.get_state() == 1:
                if j < self.top_beads:
                    v[r+1] += self.top_factor
                else:
                    v[r+1] += 1

        if count_beads:
            # Save the value associated with each rod as a 2-byte integer.
            for j in v[1:]:
                string += "%2d" % (j)
        else:
            sum = 0
            for bead in self.beads:
                sum += bead.get_value()
            string = str(sum)

        return(string)

    def label(self, string):
        """ Label the crossbar with the string. (Used with self.value) """
        self.bar.set_label(string)

    def move_mark(self, dx):
        """ Move indicator horizontally across the top of the frame. """
        self.mark.move_relative((dx, 0))

    def fade_colors(self):
        """ Reduce the saturation level of every bead. """
        for bead in self.beads:
            if bead.get_level() > 0:
                bead.set_color(self.colors[bead.get_level()-1])
                bead.set_level(bead.get_level()-1)

    def move_bead(self, sprite, dy):
        """ Move a bead (or beads) up or down a rod. """

        # find the bead associated with the sprite
        i = -1
        for bead in self.beads:
            if sprite == bead.spr:
                i = self.beads.index(bead)
                break
        if i == -1:
            print "bead not found"
            return

        b = i % (self.top_beads+self.bot_beads)
        if b < self.top_beads:
            if dy > 0 and bead.get_state() == 0:
                self.fade_colors()
                bead.set_color(self.colors[3])
                bead.move_down()
                # Make sure beads below this bead are also moved.
                for ii in range(self.top_beads-b):
                    if self.beads[i+ii].state == 0:
                        self.beads[i+ii].set_color(self.colors[3])
                        self.beads[i+ii].move_down()
            elif dy < 0 and bead.state == 1:
                self.fade_colors()
                bead.set_color(self.colors[3])
                bead.move_up()
                # Make sure beads above this bead are also moved.
                for ii in range(b+1):
                    if self.beads[i-ii].state == 1:
                        self.beads[i-ii].set_color(self.colors[3])
                        self.beads[i-ii].move_up()
        else:
            if dy < 0 and bead.state == 0:
                self.fade_colors()
                bead.set_color(self.colors[3])
                bead.move_up()
                # Make sure beads above this bead are also moved.
                for ii in range(b-self.top_beads+1):
                    if self.beads[i-ii].state == 0:
                        self.beads[i-ii].set_color(self.colors[3])
                        self.beads[i-ii].move_up()
            elif dy > 0 and bead.state == 1:
                self.fade_colors()
                bead.set_color(self.colors[3])
                bead.move_down()
                # Make sure beads below this bead are also moved.
                for ii in range(self.top_beads+self.bot_beads-b):
                    if self.beads[i+ii].state == 1:
                        self.beads[i+ii].set_color(self.colors[3])
                        self.beads[i+ii].move_down()