def _get_player(self, player_id):
        """Build and execute DB API transaction to retrieve the player row.

        If player doesn't exist, build and execute DB API transaction to
        initialize the player row and then retrieve it.

        Args:
            player_id: Hashed player name.

        Returns:
            If successful: boolean True or a positive integer indicating the number
                of rows affected.
            If unsuccessful: boolean False or a zero-value integer.
            Note: Doesn't explicitly return latest player stats; if successful the
                updated results are available in the object's attributes.

        Raises:
            RuntimeError: There was an issue retrieving the player's row.
        """
        logger.info("Getting Player!")

        # Var init
        self.player = None
        trans_id = str(uuid.uuid4())

        transaction = player.get(player_id)
        # Since a query that returns no rows will return a 0, explicitly check for
        # the False keyword value
        if self._execute_db_transaction(trans_id, transaction) is not False:
            logger.debug("Printing player! %s", self.player)
            if self.player:
                return True
            else:
                # There was nothing to fetch from the db, need to make this player
                trans_id = str(uuid.uuid4())  # Generate a new transaction ID
                transaction = player.create(player_id, cfg)

                # Get queries to make the specified number of each kind of card,
                # defined in the config file.
                initial_cards = self.cfg['player']['initial_cards']
                for loot_type in initial_cards:
                    for i in range(initial_cards[loot_type]):  # pylint: disable=unused-variable
                        card_type = random.randint(
                            self.cfg['loot_tables'][loot_type]['min'],
                            self.cfg['loot_tables'][loot_type]['max'])
                        transaction.extend(card.create(player_id, card_type))

                logger.info("Creating initial cards for player '%d'",
                            player_id)

                # Create player, create n cards.  _get_cards is called immediately
                # after, so no need to get cards yet.
                transaction.extend(player.get(player_id))
                return self._execute_db_transaction(trans_id, transaction)
        else:
            raise RuntimeError(
                "Unable to retrieve player %s from the database!" % player_id)
    def add_slots(self, num_slots):
        """Build and execute DB API transaction to add slots to a player.

        If player doesn't exist, build and execute DB API transaction to
        initialize the player row and then retrieve it.

        Args:
            player_id: Hashed player name.

        Returns:
            If successful: boolean True or a positive integer indicating the number
                of rows affected.
            If unsuccessful: boolean False or a zero-value integer.
            Note: Doesn't explicitly return latest player stats; if successful the
                updated results are available in the object's attributes.

        Raises:
            RuntimeError: There was an issue updating or retrieving the player's
                row.
        """

        transaction = []
        # Test that query generation is successful. Necessary as query generation
        # will fail if, for example, the player already has max slots
        updated_player = self.player
        updated_player['slots'] = self.player['slots'] + num_slots
        update_player_query = player.update(updated_player)
        if update_player_query:
            transaction.extend(update_player_query)
        else:
            logger.error(
                "Unable to update player! (continuing without update!)")

        # Get player after updating slots
        transaction.extend(player.get(self.player['id']))

        # Run transaction
        trans_id = str(uuid.uuid4())
        results = self._execute_db_transaction(trans_id, transaction)
        # Since a query that returns no rows will return a 0, explicitly check for
        # the False keyword value
        if results is False:
            raise RuntimeError("Unable to add slots to player %s!" % player['id'])
        return results
    def play_stage(self):
        """Build and execute DB API transaction to simulate player playing a stage.

        Note: Stamina is not currently validated.

        Returns:
            If successful: boolean True or a positive integer indicating the number
                of rows affected.
            If unsuccessful: boolean False or a zero-value integer.
            Note: Doesn't explicitly return latest cardlist/player stats;
                if successful the updated results are available in the
                object's attributes.

        Raises:
            RuntimeError: There was an issue with the database transaction
                required to evolve the card.
        """

        # Obviously this could be a call out to a key/value store to get a constantly
        # updating chance of drops
        loot_table = self.cfg['loot_tables']['std']  # Standard loot table
        num_rounds = 5  # rounds in this level

        transaction = []
        # Test to see if the player failed the stage
        if random.random() <= self.cfg['stage']['failure_chance']:

            # Roll for card drops
            for i in range(num_rounds):
                if (len(self.cards) + len(transaction)) < self.player['slots']:
                    #logger.debug(" Playing round %d" % i)
                    card_type = None
                    # Roll d100
                    roll = random.random()
                    if roll <= loot_table['drop_chance']:
                        # This can be replaced with a more advanced probabilistic function, just
                        # random for now
                        card_type = random.randint(loot_table['min'],
                                                   loot_table['max'])
                        transaction.extend(card.create(self.player['id'],
                                                       card_type))
                    loot_msg = " Round %2d: Rolled %.2f/%.2f for player %d, dropped card %s"
                    logger.info(loot_msg, i, roll, loot_table['drop_chance'],
                                            self.player['id'], str(card_type))
                else:
                    full_msg = "****Player (%d) doesn't have any more slots! Discarding remaining drops..."
                    logger.warning(full_msg, self.player['id'])
                    break
            logger.info(" Player completed stage - %2d loot cards acquired.",
                        len(transaction))

            # Assume player took a friend along, give them friend points
            updated_player = self.player.copy()
            updated_player['points'] = self.player['points'] + self.cfg[
                'stage']['points_per_run']
            # Test that query generation is successful. Necessary as query generation
            # will fail if, for example, the player already has max friend points
            update_player_query = player.update(updated_player)
            if update_player_query:
                transaction.extend(update_player_query)
            else:
                logger.error(
                    "Unable to update player! (continuing without update!)")

            # After updates, get the latest player/cardlist
            transaction.extend(player.get(self.player['id']))
            transaction.extend(card.get_all(self.player['id']))

            # Run transaction
            trans_id = str(uuid.uuid4())
            results = self._execute_db_transaction(trans_id, transaction)

            # Since a query that returns no rows will return a 0, explicitly check for
            # the False keyword value
            if results is False:
                raise RuntimeError("Unable to Play Stage for player %s!" %
                                   self.player['id'])
            return results
        else:
            logger.info("  Player failed stage!")
            return False