Exemple #1
0
def create():

    new_card = Card()

    if len(request.forms.get("name")) != 0:
        new_card.name = request.forms.get("name")

    if len(request.forms.get("description")) != 0:
        new_card.description = request.forms.get("description")

    if request.forms.get("parentNo") != "--":
        new_card.parent_no = request.forms.get("parentNo")

    # TODO 画像選択フォームを使うかどうか・・・
    if "image" in request.files:
        image = request.files["image"]

        image_name, file_ext = os.path.splitext(image.filename)
        image.save(os.path.join('./selectImages', image_name + file_ext))

        new_card.image = image_name + file_ext

    if len(request.forms.get("selectImage")) != 0:
        new_card.image = request.forms.get("selectImage")

    new_card.create()

    redirect('/')
Exemple #2
0
def scrape(card_no):

    serch_card = Card(card_no)

    # -- スクレイピング --
    browser = RoboBrowser(parser='html.parser')

    browser.open('https://www.google.co.jp/')

    form = browser.get_form(action='/search')
    form['q'] = serch_card.name

    browser.submit_form(form, list(form.submit_fields.values())[0])

    new_card = Card()
    new_card.name = serch_card.name
    # 画像はとりあえずランダムに設定
    new_card.image = random.choice([os.path.basename(file) for file in glob.glob('./selectImages/*.*')])
    new_card.description = "検索結果:" + serch_card.name

    new_card.create()

    for a in browser.select('h3 > a'):

        new_child_card = Card()
        new_child_card.name = a.text[0:32].encode("utf-8")
        new_child_card.image = random.choice([os.path.basename(file) for file in glob.glob('./selectImages/*.*')])
        new_child_card.description = a.get('href')
        new_child_card.parent_no = new_card.no

        new_child_card.create()


    redirect('/')
Exemple #3
0
 def from_user(fight, user, team_id):
     ''' Создание файтера из пользователя.
         User user - пользователь.
         int team_id - номер команды.
     '''
     f = Fighter(fight)
     f._type = "user"
     f.have_deck = True
     f.alive = True
     f.user_id = user.id
     f.team_id = team_id
     f.deck = list(chain.from_iterable((Card.create(id) for i in range(count)) for id, count in user["deck"].dict.iteritems()))
     f.name = user.name
     f.image = user.image
     f.level = user["level"]
     f.base_stats = {}
     for key in ["atk", "def", "matk", "mdef", "hp_max"]:
         f.base_stats[key] = f.stats[key] = user[key]
     for sk in user['skills'].keys():
         key = 'skill_' + sk
         f.base_stats[key] = f.stats[key] = user['skills'][sk]
     if fight._type == "training":
         f.hp = user["hp_max"]
     else:
         user.update_hp()
         f.hp = int(user.hp)
     f.effects = []
     for eff, limit in user["effects"]:
         f.add_effect(effect.get(eff.id, *eff.args), 99, f)
     f.stats["max_hand"] = 6
     shuffle(f.deck)
     f.hand = []
     f.draw(5)
     f.mana = 0
     return f
Exemple #4
0
 def get_card(self, sn):
     return Card.create(self.query('select * from card where cardid = "%s"' % (sn,)))
Exemple #5
0
 def get_card(self, sn):
     apb = self.local.option('apb') == '2'
     return Card.create(self.query('select * from card where CardID = "%s"' % (sn,)), apb=apb)
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)