class Adventure:
    # changes to a 'current' location to go a specific direction
    NORTH = [0, -1]
    EAST = [1, 0]
    SOUTH = [0, 1]
    WEST = [-1, 0]

    def __init__(self, map_file_name, class_name):
        self.player = Adventurer('Tango', class_name)  # default character
        self.bot = Playerbot()  # the robot the player will interact with
        self.player_x = 0  # player's current x-coord
        self.player_y = 0  # player's current y-coord
        self.world = Map(
            map_file_name)  # load the given map as the world for the adventure
        self.won = False  # has the player beaten the adventure?

        self.determine_start()

    def determine_start(self):
        ''' determine_start figures out where the game will start on the map '''
        starts = self.world.get_starting_cells()
        start = starts[
            die.roll(len(starts)) -
            1]  # randomly choose a start location from those possible
        self.player_x, self.player_y = start

    def move_player(self, loc_mod):
        ''' move_player alters the player's location based on the given modifier '''
        self.player_x += loc_mod[0]
        self.player_y += loc_mod[1]

        self.bot.move(loc_mod)

        # if the player moves onto a road, take the road in the same direction until a visitable cell is reached
        if self.world.cells[self.player_y][self.player_x].is_road():
            self.move_player(loc_mod)

    def start(self):
        ''' start kicks off an adventure '''
        # keep visiting locations until the move count runs out
        moves = 20
        while not self.won and self.player.hp > 0 and moves > 0:
            self.visit_location()
            moves -= 1

        # based on how game ended, inform Android
        if self.won:
            Client.sendMessage('win')
        elif moves == 0:
            Client.sendMessage('no moves')
        else:
            Client.sendMessage('died')

    def travel_options(self):
        ''' travel_options returns a list of valid options for leaving the current cell '''
        opt = []

        # north
        if not (self.player_y - 1 < 0) and self.world.cells[self.player_y - 1][
                self.player_x].id != '#':
            opt.append('north')
        # east
        if not (self.player_x + 1 > len(self.world.cells[self.player_y]) -
                1) and self.world.cells[self.player_y][self.player_x +
                                                       1].id != '#':
            opt.append('east')
        # south
        if not (self.player_y + 1 > len(self.world.cells) -
                1) and self.world.cells[self.player_y +
                                        1][self.player_x].id != '#':
            opt.append('south')
        # west
        if not (self.player_x - 1 < 0) and self.world.cells[self.player_y][
                self.player_x - 1].id != '#':
            opt.append('west')
        return opt

    def visit_action(self, opt):
        ''' visit_action handles the user's choice of action '''
        # ask Android for user's choice. no validation necessary because Android will only present user with valid options
        choice = Client.sendMessage('action:' + str(opt))
        print("\taction choice:", choice)

        # set up items for interaction
        items = self.world.cells[self.player_y][self.player_x].items
        chest, pool = None, None  # possible items to interact with on the cell
        for it in items:
            if isinstance(it, item.Chest):
                chest = it
            elif isinstance(it, item.Key):
                self.player.backpack.append(
                    it)  # keys are automatically picked up
                self.world.cells[self.player_y][self.player_x].items.remove(
                    it)  # remove the key from the cell once player picks it up
                print('\t***********PICKED UP KEY******************')
            elif isinstance(it, item.RadiantPool):
                pool = it

        # handle movement choice
        if choice == 'north':
            self.move_player(Adventure.NORTH)
        elif choice == 'east':
            self.move_player(Adventure.EAST)
        elif choice == 'south':
            self.move_player(Adventure.SOUTH)
        elif choice == 'west':
            self.move_player(Adventure.WEST)
        # handle interaction choice
        else:
            # if interacting with a chest, see if the player has any keys that unlock it
            if choice == 'chest':
                for it in self.player.backpack:
                    if isinstance(it, item.Key):
                        if chest.unlocked_by(it):
                            self.won = True
                if not self.won:
                    Client.sendMessage(
                        'need key'
                    )  # inform Android that the player does not have the right key
            # a pool heals the player for a specified number of points
            elif choice == 'pool':
                pool.cleanse(self.player)

    def visit_combat(self):
        ''' visit_combat performs combat for the current cell '''
        if len(self.world.cells[self.player_y][self.player_x].npcs) == 0:
            return True  # skip combat if there's nobody to fight

        targets = []  # list of the names of possible targets
        for enemy in self.world.cells[self.player_y][self.player_x].npcs:
            targets.append(str(enemy).lower())
        Client.sendMessage('combat:' +
                           str(targets))  # inform Android combat has started
        self.bot.encounter()

        # as long as there are combatants, fight
        while len(self.world.cells[self.player_y][self.player_x].npcs) > 0:
            # player acts first
            choice = Client.sendMessage(
                'hp:' + str(self.player.hp))  # expect 'attack' or 'run'
            print('\tcombat choice:\'' + choice + '\'')
            if choice == 'run':
                # player has a 75% chance to successfully run
                if die.roll(4) > 1:
                    Client.sendMessage(
                        'run:true')  # tell Android run was successful
                    self.determine_start(
                    )  # teleport the player off to a starting location
                    break
                else:
                    Client.sendMessage(
                        'run:false')  # tell Android run was not successful

            # player attack phase
            # choice = Client.sendMessage('target') # ask Android for a valid target
            target = self.world.cells[self.player_y][
                self.player_x].get_npc_by_name(
                    targets[0])  # step 2 only allows 1 opponent per cell
            player_dmg = self.player.roll_damage()
            self.bot.hit()
            Client.sendMessage('dealt:' + str(player_dmg))

            # if the target dies as a result of the damage, remove it from the cell and from list of possible targets
            if not target.take_damage(player_dmg):
                self.world.cells[self.player_y][self.player_x].npcs.remove(
                    target)
                targets.remove(target.name)
                Client.sendMessage('gained:' + str(target.xp_worth()))

            # NPC attack phase
            for npc in self.world.cells[self.player_y][self.player_x].npcs:
                npc_dmg = npc.roll_damage()
                Client.sendMessage(
                    'recv:' + str(npc_dmg)
                )  # tell Android how much damage player was dealt to the player
                if not self.player.take_damage(npc_dmg):
                    return False
        return True

    def visit_location(self):
        ''' visit_location enables the player to visit a location '''
        # combat happens immediately. return if player dies
        if not self.visit_combat():
            return
        self.visit_options()  # prompt user with post-combat options

    def visit_options(self):
        ''' visit_options presents the player with all post-combat options '''
        opt = self.travel_options(
        )  # directions player can travel from current location

        # output items
        for item in self.world.cells[self.player_y][self.player_x].items:
            opt.append(str(item))

        # prompt user and take action
        self.visit_action(opt)
class Adventure:
    # changes to a 'current' location to go a specific direction
    NORTH = [0, -1]
    EAST = [1, 0]
    SOUTH = [0, 1]
    WEST = [-1, 0]

    def __init__(self, map_file_name, class_name):
        self.player = Adventurer('Tango', class_name)  # default character
        self.player_x = 0  # player's current x-coord
        self.player_y = 0  # player's current y-coord
        self.world = Map(
            map_file_name)  # load the given map as the world for the adventure
        self.won = False  # has the player beaten the adventure?

        self.determine_start()

    def determine_start(self):
        ''' determine_start figures out where the game will start on the map '''
        starts = self.world.get_starting_cells()
        start = starts[
            die.roll(len(starts)) -
            1]  # randomly choose a start location from those possible
        self.player_x, self.player_y = start

    def move_player(self, loc_mod):
        ''' move_player alters the player's location based on the given modifier '''
        self.player_x += loc_mod[0]
        self.player_y += loc_mod[1]

        # if the player moves onto a road, take the road in the same direction until a visitable cell is reached
        if self.world.cells[self.player_y][self.player_x].is_road():
            self.move_player(loc_mod)

    def start(self):
        ''' start kicks off an adventure '''
        # print(self.world)
        # print('Starting', self.player, 'at (' + str(self.player_x) + ',' + str(self.player_y) + ')')

        # keep visiting locations until the move count runs out
        moves = 20
        while not self.won and self.player.hp > 0 and moves > 0:
            self.visit_location()
            moves -= 1

        print()
        if self.won:
            print('The key unlocked the chest! You win!')
        elif moves == 0:
            print('You ran out of moves!')
        else:
            print('YOU DIED')

    def travel_options(self):
        ''' travel_options returns a list of valid options for leaving the current cell '''
        opt = []

        # north
        if not (self.player_y - 1 < 0) and self.world.cells[self.player_y - 1][
                self.player_x].id != '#':
            opt.append('north')
        # east
        if not (self.player_x + 1 > len(self.world.cells[self.player_y]) -
                1) and self.world.cells[self.player_y][self.player_x +
                                                       1].id != '#':
            opt.append('east')
        # south
        if not (self.player_y + 1 > len(self.world.cells) -
                1) and self.world.cells[self.player_y +
                                        1][self.player_x].id != '#':
            opt.append('south')
        # west
        if not (self.player_x - 1 < 0) and self.world.cells[self.player_y][
                self.player_x - 1].id != '#':
            opt.append('west')
        return opt

    def visit_action(self, opt):
        ''' visit_action handles the user's choice of action '''
        while True:
            # take in user choice and check if valid
            choice = input("What would you like to do? ").lower()
            if opt.count(choice) == 0:
                print("\tInvalid choice. Try again.")
                continue

            # handle movement choice
            if choice == 'north':
                self.move_player(Adventure.NORTH)
            elif choice == 'east':
                self.move_player(Adventure.EAST)
            elif choice == 'south':
                self.move_player(Adventure.SOUTH)
            elif choice == 'west':
                self.move_player(Adventure.WEST)
            # handle interaction choice
            else:
                items = self.world.cells[self.player_y][self.player_x].items
                chest, key, pool = None, None, None
                for it in items:
                    if isinstance(it, item.Chest):
                        chest = it
                    elif isinstance(it, item.Key):
                        key = it
                    elif isinstance(it, item.RadiantPool):
                        pool = it
                # if interacting with a chest, see if there are any keys that unlock it
                if choice == 'chest':
                    for it in self.player.backpack:
                        if isinstance(it, item.Key):
                            if chest.unlocked_by(it):
                                self.won = True
                    if not self.won:
                        print('\tYou need a key to unlock this chest.')
                # interacting with a key picks it up
                elif choice == 'key':
                    print('\tYou picked up the key.')
                    self.player.backpack.append(key)
                    self.world.cells[self.player_y][
                        self.player_x].items.remove(key)
                # a pool heals the player for a specified number of points
                elif choice == 'radiant pool':
                    hp = self.player.hp
                    pool.cleanse(self.player)
                    print('\tYou healed for', self.player.hp - hp,
                          'hit points.')
                else:
                    print('Interacting with \'' + choice +
                          '\'s is not supported.')
                    continue

            break  # exit when a valid choice has been made

    def visit_combat(self):
        ''' visit_combat performs combat for the current cell '''
        if len(self.world.cells[self.player_y][self.player_x].npcs) == 0:
            return True

        targets = []  # list of the names of possible targets
        print('You are attacked!', end=' ')
        for enemy in self.world.cells[self.player_y][self.player_x].npcs:
            print(enemy, end=' ')
            targets.append(str(enemy).lower())
        print('enters combat with you.')

        # as long as there are combatants, fight
        while len(self.world.cells[self.player_y][self.player_x].npcs) > 0:
            # player acts first
            print('\tYou have', self.player.hp, 'hp.', end=' ')
            choice = input('Attack or run? ').lower()
            if choice == 'run':
                # player has a 75% chance to successfully run
                if die.roll(4) > 1:
                    print('\t\tSuccessfully ran away.')
                    self.determine_start()
                    break
                else:
                    print('\t\tCannot escape!')

            # player attack phase
            choice = input('\tChoose a target: ').lower()
            while targets.count(choice) == 0:
                print('\tInvalid target.')
                choice = input('\tChoose a target: ').lower()

            target = self.world.cells[self.player_y][
                self.player_x].get_npc_by_name(choice)
            player_dmg = self.player.roll_damage()
            print('\t\tDealt', player_dmg, 'damage to', target)

            # if the target dies as a result of the damage, remove it from the cell
            if not target.take_damage(player_dmg):
                # if player levels up due to the XP
                if self.player.gain_xp(target.xp_worth()):
                    print('\t\tLevel up! You are now level', self.player.level)
                self.world.cells[self.player_y][self.player_x].npcs.remove(
                    target)

                print('\t\t' + str(target), 'evaporated!')
                print('\t\tGained', target.xp_worth(), 'xp.')

            # all NPCs hit
            for npc in self.world.cells[self.player_y][self.player_x].npcs:
                npc_dmg = npc.roll_damage()
                print('\t\tYou took', npc_dmg, 'points of damage from', npc)
                if not self.player.take_damage(npc_dmg):
                    return False
        return True

    def visit_location(self):
        ''' visit_location enables the player to visit a location '''
        # print(self.world.cells[self.player_y][self.player_x].id)
        # combat happens immediately. return if player dies
        if not self.visit_combat():
            return
        self.visit_options()  # prompt user with post-combat options

    def visit_options(self):
        ''' visit_options presents the player with all post-combat options '''
        opt = self.travel_options(
        )  # directions player can travel from current location

        # output possible directions
        print('\nYou see a path to the', opt[0].capitalize(), end='')
        for i in range(1, len(opt)):
            print(',', opt[i].capitalize(), end='')
        print('.')

        # output items
        if len(self.world.cells[self.player_y][self.player_x].items) > 0:
            print('On the ground in front of you is a ', end='')
            for i, item in enumerate(
                    self.world.cells[self.player_y][self.player_x].items):
                if i > 0:
                    print(',', end=' ')
                opt.append(str(item))
                print(str(item).title(), end='')
            print('.')

        # prompt user and take action
        self.visit_action(opt)