Ejemplo n.º 1
0
class Player():
    """
        This class handles everything relating to the player
        """
    LOW_HEALTH = 150
    TASKS = Enum('Task', 'MINE, SMELT, FORGE')

    def __init__(self, game_map, task=TASKS.MINE):
        self.user_interface = UserInterface()
        self.backpack = Backpack()
        self.mine = Mine(self, game_map)
        self.smelt = Smelt(self, game_map)
        self.forge = Forge(self, game_map)

        # Set the default initial task
        self.task = task
        self.action_count = 0

    def perform_task(self):
        """
            Checks health, organizes backpack and then performs
            one of the following tasks: mine, smelt, or forge
            """
        # Do pre-task checks
        self.action_count += 1

        # Only check health every 25 turns (it is a slow process)
        if self.action_count % 25 == 0:
            self.action_count = 0
            self.check_health()

        # Perform task
        if self.task == self.TASKS.MINE:
            self.task = self.mine.mine()
            delay = 0
        elif self.task == self.TASKS.SMELT:
            self.task = self.smelt.smelt()
            delay = 1.4
        elif self.task == self.TASKS.FORGE:
            self.task = self.forge.forge()
            delay = 2.1

        # Give the task time to complete
        sleep(delay)

        # Organize backpack now that items have been potentially added
        self.organize_backpack()

    def check_health(self):
        """
            Checks player's HP and uses a potion if it is low.
            If no potions are found, game will quit
            """
        # Check if HP is low
        if self.user_interface.get_health() < self.LOW_HEALTH:
            # Attempt to use a potion
            utils.log("INFO", F"Health dropped below {self.LOW_HEALTH}")
            used_potion = self.backpack.use_item('potion', offset=(4, 9))

            # No potions were found
            if not used_potion:
                # Potions may be obscurred by items, try selling
                self.forge.sell_items()
                # Try using a potion again
                used_potion = self.backpack.use_item('potion', offset=(4, 9))

                # Still can't use potions, likely do not have any
                if not used_potion:
                    utils.log("SEVERE", "No potions found")
                    utils.quit_game()

            # Sleep so that there is no issue using the next item
            utils.log("INFO", F"Used a potion")
            sleep(6)

    def is_weight_below_threshold(self, threshold):
        # Calculate how much more weight the player can carry
        current_weight, max_weight = self.user_interface.get_weight()
        difference = max_weight - current_weight

        # Check if the weight the player can carry is below the threshold
        if difference < threshold:
            utils.log("INFO", F"Weight is {difference} from max, threshold was set to {threshold}")
            return True

        return False

    def organize_backpack(self):
        """
            Move all items to the correct areas of the backpack
            """
        self.backpack.move_item('ore', self.backpack.ORE_LOC, (8, 6))
        self.backpack.move_item('gem', self.backpack.GEM_LOC, (4, 2))
        self.backpack.move_item('jewel', self.backpack.GEM_LOC, (4, 2))
        self.backpack.move_item('galantine', self.backpack.GEM_LOC, (5, 4))
        self.backpack.move_item('pickaxe', self.backpack.PICKAXE_LOC, (9, 4))
        self.backpack.move_item('dagger', self.backpack.DAGGER_LOC, (4, 6))
        self.backpack.move_item('hammer', self.backpack.HAMMER_LOC, (7, 3))
        self.backpack.move_item('gold', self.backpack.GEM_LOC, (6, 5))
        self.backpack.move_item('ingot', self.backpack.INGOT_LOC)
Ejemplo n.º 2
0
class GameMap:
    """
        This class is used to detect inaccessible tiles in the gameplay
        region and perform actions with them.
        """
    TILE_DIM = 16
    TILES = Enum(
        'Tile',
        'UNKNOWN, ACCESSIBLE, DOOR, GRAVEL, INACCESSIBLE, MOUNTAIN, WEAPON_SHOPKEEPER, ITEM_SHOPKEEPER, POTION_SHOPKEEPER, BANKER, BLACKSMITH, PLAYER'
    )

    def __init__(self):
        """
            Create a tuple for each tile containing the following properties:
            0: tile data, 1: name
            """
        self.backpack = Backpack()

        try:
            # Load the saved map from disk
            self.game_map = np.loadtxt('map.txt', dtype=int)
        except OSError:
            # Map does not exist, create a new one
            self.game_map = np.ones(shape=(82, 240), dtype="int")
            self.game_map[:, 0:166] = self.TILES.INACCESSIBLE.value
            self.game_map[:, -10:-1] = self.TILES.INACCESSIBLE.value
            self.game_map[0, :] = self.TILES.INACCESSIBLE.value
            self.game_map[-10:-1, :] = self.TILES.INACCESSIBLE.value

        # Initialize the player position
        self.player_position = (0, 0)

        self.templates = []
        # Load all accessible tile templates
        for tile in [
                f for f in os.listdir('./accessible_tiles')
                if f.endswith('.png')
        ]:
            tile_template = cv2.imread('./accessible_tiles/' + tile)
            tile_template = cv2.cvtColor(tile_template, cv2.COLOR_BGR2GRAY)
            self.templates.append(
                [tile_template, 'accessible/' + tile.split('.')[0]])

        # Load all inaccessible tile templates
        for tile in [
                f for f in os.listdir('./inaccessible_tiles')
                if f.endswith('.png')
        ]:
            tile_template = cv2.imread('./inaccessible_tiles/' + tile)
            tile_template = cv2.cvtColor(tile_template, cv2.COLOR_BGR2GRAY)
            self.templates.append(
                [tile_template, 'inaccessible/' + tile.split('.')[0]])

        # Load all NPC tile templates
        for tile in [f for f in os.listdir('./npcs') if f.endswith('.png')]:
            tile_template = cv2.imread('./npcs/' + tile)
            tile_template = cv2.cvtColor(tile_template, cv2.COLOR_BGR2GRAY)
            self.templates.append(
                [tile_template, 'npcs/' + tile.split('.')[0]])

    def update_player_position(self, screenshot):
        """To update the player position the following steps are taken.
            1. The sextant is location and used
            2. The coordinates are parsed and normalized
            3. Old player position is removed from map
            4. New player position is added to the map

            If any of these operations fails, the game will quit.

            Returns tuple containing player position as well as the tile map
            """

        sextant_x = None
        sextant_y = None
        errors = 0

        while sextant_x is None:
            # Move mouse to a neutral position that won't obstruct template matching
            pyautogui.moveTo(400, 400)

            # Find and use the sextant
            self.backpack.use_item('sextant', None, (5, 2), True)

            # Take a new screenshot that includes the location
            screenshot = utils.take_screenshot()

            # Find the current position
            position = screenshot[450:465, 120:180]

            # Resize and parse the image to a string
            position = cv2.resize(position, (0, 0), fx=5, fy=5)
            position = cv2.bitwise_not(position)
            position = cv2.blur(position, (8, 8))

            text = ''

            try:
                text = pytesseract.image_to_string(position, config='--psm 8')
                # Split the text into coordinates
                sextant_x, sextant_y = text.split(", ")[0::1]
            except UnicodeDecodeError:
                utils.log("SEVERE",
                          F"Unable to parse sextant coordinates string")
                utils.quit_game()
            except ValueError as value_error:
                utils.log("WARN", F"{value_error}")
                # Move mouse to a neutral position that won't obstruct template matching
                pyautogui.moveTo(400, 400)
                errors += 1

            if errors == 10:
                utils.log("SEVERE", F"Sextant failed 10 times")
                utils.quit_game()

        # Normalize the coordinates to fit the map indicies
        normalized_x = int(sextant_x) - utils.NORMALIZATION_CONSTANT
        normalized_y = int(sextant_y) - utils.NORMALIZATION_CONSTANT

        # Remove old player position from the map
        self.game_map[self.game_map ==
                      self.TILES.PLAYER.value] = self.TILES.ACCESSIBLE.value
        self.player_position = (normalized_x, normalized_y)
        self.game_map[self.player_position] = self.TILES.PLAYER.value

    def match_template_type(self, tile, templates):
        """
            Compare the tile with a set of templates
            """
        potential_tiles = []

        # Go through all accessible tiles
        for template in templates:
            result = cv2.matchTemplate(tile, template[0], cv2.TM_CCORR_NORMED)
            max_val = cv2.minMaxLoc(result)[1]

            # Confidence too low for a match
            if max_val < 0.90:
                continue

            # This is a potential tile
            potential_tiles.append((template, max_val))

            # Very high confidence that this is the correct tile
            if max_val > 0.99:
                break

        return potential_tiles

    def update_map(self, screenshot=None):
        """
            Takes a screenshot of the game, this method will iterate
            over the gameplay region in 16x16 chunks.  Each chunk is compared
            with all potential inaccessible tiles until a match is found.

            When a match is found, the frequency of that tile is incremented
            so future chunks are compared with high frequency tiles first.
            """
        # Get the visible tiles
        nearby = self.game_map[(self.player_position[0] -
                                10):(self.player_position[0] + 11),
                               (self.player_position[1] -
                                10):(self.player_position[1] + 11)]

        # Clear NPCs in the nearby as they may have moved
        nearby[nearby ==
               self.TILES.WEAPON_SHOPKEEPER.value] = self.TILES.UNKNOWN.value
        nearby[nearby ==
               self.TILES.BLACKSMITH.value] = self.TILES.UNKNOWN.value

        # Take screenshot and isolate the gamplay region
        if screenshot is None:
            screenshot = utils.take_screenshot()
        play = screenshot[8:344, 8:344]

        # Loop through all unknown tiles in the nearby
        for i, j in zip(*np.where(nearby == self.TILES.UNKNOWN.value)):
            # Scale up the dimensions
            tile_x = i * self.TILE_DIM
            tile_y = j * self.TILE_DIM

            # The center cell is always the player
            if i == 10 and j == 10:
                tile_x = self.player_position[0] + int(tile_x / 16) - 10
                tile_y = self.player_position[1] + int(tile_y / 16) - 10
                self.game_map[(tile_x, tile_y)] = self.TILES.PLAYER.value
                continue

            # Slice the tile from the play region
            tile = play[tile_y:tile_y + self.TILE_DIM,
                        tile_x:tile_x + self.TILE_DIM]

            tile_x = self.player_position[0] + int(tile_x / 16) - 10
            tile_y = self.player_position[1] + int(tile_y / 16) - 10

            # Go through all tile types looking for a high confidence match
            template = None
            for potential_template in self.templates:
                if np.allclose(potential_template[0], tile, 1, 1):
                    template = potential_template
                    break

            # No match, assume it is inaccessible
            if template is None:
                self.game_map[(tile_x, tile_y)] = self.TILES.INACCESSIBLE.value
                continue

            # By default, mark tile as inaccessible
            label = None

            # Mark as mineable
            if re.search(r'rock', template[1], re.M | re.I):
                label = self.TILES.MOUNTAIN.value
            elif re.search(r'door', template[1], re.M | re.I):
                label = self.TILES.DOOR.value
            elif re.search(r'gravel', template[1], re.M | re.I):
                label = self.TILES.GRAVEL.value
            elif re.search(r'shopkeeper', template[1], re.M | re.I):
                label = self.TILES.WEAPON_SHOPKEEPER.value
            elif re.search(r'blacksmith', template[1], re.M | re.I):
                label = self.TILES.BLACKSMITH.value
            elif re.search(r'guard', template[1], re.M | re.I):
                label = self.TILES.INACCESSIBLE.value
            elif re.search(r'inaccessible', template[1], re.M | re.I):
                label = self.TILES.INACCESSIBLE.value
            elif re.search(r'accessible', template[1], re.M | re.I):
                label = self.TILES.ACCESSIBLE.value

            # Calculate coordinates of tile in the map relative to the player
            self.game_map[(tile_x, tile_y)] = label

        # Go through all tiles in the gameplay region to find the mountains
        for i, j in zip(*np.where(nearby == self.TILES.MOUNTAIN.value)):
            # Get the tile to the left of the mountain
            tile_left = nearby[(i - 1, j)]

            # Only allow mountains to be minable if they are beside gravel
            if not tile_left == self.TILES.GRAVEL.value:
                nearby[(i, j)] = self.TILES.INACCESSIBLE.value

        # Save the game map to disk
        np.savetxt('map.txt', self.game_map, fmt='%d')