<commit_before>#!python3
# This is a very simple implementation of the UCT Monte Carlo Tree Search algorithm in Python 2.7.
# The function UCT(rootstate, itermax, verbose = False) is towards the bottom of the code.
# It aims to have the clearest and simplest possible code, and for the sake of clarity, the code
# is orders of magnitude less efficient than it could be made, particularly by using a 
# state.GetRandomMove() or state.DoRandomRollout() function.
# 
# Example GameState classes for Nim, OXO and Othello are included to give some idea of how you
# can write your own GameState use UCT in your 2-player game. Change the game to be played in 
# the UCTPlayGame() function at the bottom of the code.
# 
# Written by <NAME>, <NAME>, <NAME> (University of York, UK) September 2012.
# 
# Licence is granted to freely use and distribute for any sensible/legal purpose so long as this comment
# remains in any distributed code.
# 
# For more information about Monte Carlo Tree Search check out our web site at www.mcts.ai

from math import *
import random
from hsgame.game_objects import *
from hsgame.targeting import *
from tests.testing_utils import generate_game_for
from hsgame.agents.basic_agents import DoNothingBot, RandomBot
from hsgame.constants import CHARACTER_CLASS
from hsgame.cards.spells.mage import Pyroblast
deck1 = Pyroblast  # Breaks if you finish with a spell, returning Game Ended
deck2 = Pyroblast
game = generate_game_for(deck1, deck2, RandomBot, RandomBot)
game.players[0].max_mana = 2  # Starting with 1 mana seems to give it issues with empty movelists
game.players[0].hero.health = 3
game.players[1].hero.health = 3
game._start_turn()

class HearthState:
    """ A state of the game, i.e. the game board. These are the only functions which are
        absolutely necessary to implement UCT in any 2-player complete information deterministic 
        zero-sum game, although they can be enhanced and made quicker, for example by using a 
        GetRandomMove() function to generate a random move during rollout.
        By convention the players are numbered 1 and 2.
    """
    def __init__(self, game):
        self.game = game
        self.playerJustMoved = 1

    def Clone(self):
        """ Create a deep clone of this game state.
        """
        st = HearthState(self.game)
        st.playerJustMoved = self.playerJustMoved
        return st

    def DoMove(self, move):
        """ Update a state by carrying out the given move.
            Must update playerJustMoved.
        """
        # print(str(self.game.current_player.mana) + "/" + str(self.game.current_player.max_mana))
        if move[0] == "end_turn":
            self.game._end_turn()
            self.game._start_turn()
            self.playerJustMoved = 1 - self.playerJustMoved
            # print("Player " + str(self.playerJustMoved) + " ends turn")
        elif move[1] == "summon_minion":
            self.game.current_player.agent.choose_index = move[2]
            move[1]  # DoNothingBot summons at index 0, that's ok for now, but need to pass index eventually
        elif move[2] is None:
            move[1]
        elif move[0] == "minion_attack" or move[0] == "hero_attack" or move[0] == "mage_power":
            self.game.current_player.agent.choose_target = move[2]
            move[1]
            # print(str(move[0]) + " used with target " + str(move[2]))
        else:
            raise NameError("DoMove ran into unclassified card", move)

    def GetMoves(self):
        """ Get all possible moves from this state.
            Going to return tuples, untargeted_spell, targetted_spell, equip_weapon, play_secret, mininion_attack, hero_attack, hero_power, end_turn
        """
        if self.game.players[1].hero.health <= 0 or self.game.players[0].hero.health <= 0:
            return []
        valid_moves = [["end_turn", self.game._end_turn(), None]]
        found_taunt = False
        targets = []
        for enemy in self.game.other_player.minions:
            if enemy.taunt and enemy.can_be_attacked():
                found_taunt = True
            if enemy.can_be_attacked():
                targets.append(enemy)

        if found_taunt:
            targets = [target for target in targets if target.taunt]
        else:
            targets.append(self.game.other_player.hero)

        for minion in copy.copy(self.game.current_player.minions):
            if minion.can_attack():
                for target in targets:
                    valid_moves.append(["minion_attack", minion.attack(), target])

        if self.game.current_player.hero.can_attack():
            for target in targets:
                valid_moves.append(["hero_attack", self.game.current_player.hero.attack(), target])

        for card in copy.copy(self.game.current_player.hand):
            if card.can_use(self.game.current_player, self.game) and isinstance(card, MinionCard):
                valid_moves.append(["summon_minion", self.game.play_card(card), 0])
            elif card.can_use(self.game.current_player, self.game) and isinstance(card, WeaponCard):
                valid_moves.append(["equip_weapon", self.game.play_card(card), None])
            elif card.can_use(self.game.current_player, self.game) and isinstance(card, SecretCard):
                valid_moves.append(["played_secret", self.game.play_card(card), None])
            elif card.can_use(self.game.current_player, self.game) and not card.targetable:
                valid_moves.append(["untargeted_spell", self.game.play_card(card), None])
            elif card.can_use(self.game.current_player, self.game) and card.targetable:
                for target in card.targets:
                    valid_moves.append(["targeted_spell", self.game.play_card(card), target])

        if self.game.current_player.hero.character_class == CHARACTER_CLASS.MAGE and self.game.current_player.hero.power.can_use():
            for target in hsgame.targeting.find_enemy_spell_target(game, lambda x: True):
                valid_moves.append(["mage_power", self.game.current_player.hero.power.use(), target])
        elif self.game.current_player.hero.character_class == CHARACTER_CLASS.PRIEST and self.game.current_player.hero.power.can_use():
            for target in hsgame.targeting.find_friendly_spell_target(game, lambda x: x.health != x.calculate_max_health()):
                valid_moves.append(["priest_power", self.game.current_player.hero.power.use(), target])
        elif self.game.current_player.hero.power.can_use():
            valid_moves.append(["hero_power", self.game.current_player.hero.power.use(), None])
        print(str(valid_moves))
        return valid_moves

    def GetResult(self, playerjm):
        """ Get the game result from the viewpoint of playerjm. 
        """
        assert self.game.current_player.hero.health <= 0 or self.game.other_player.hero.health <= 0
        if self.game.current_player.hero.health <= 0:
            return 0
        if self.game.other_player.hero.health <= 0:
            return 1

    def __repr__(self):
        s = "[" + str(self.game.players[1].hero.health) + " hp:" + str(len(self.game.players[1].hand)) + " in hand:" + str(self.game.players[1].deck.left) + " in deck] "
        for minion in copy.copy(self.game.players[1].minions):
            s += str(minion.calculate_attack()) + "/" + str(minion.health) + ":"
        s += "\n[" + str(self.game.players[0].hero.health) + " hp:" + str(len(self.game.players[0].hand)) + " in hand:" + str(self.game.players[1].deck.left) + " in deck] "
        for minion in copy.copy(self.game.players[0].minions):<commit_msg>Runs full games without errors<commit_after>#!python3
# This is a very simple implementation of the UCT Monte Carlo Tree Search algorithm in Python 2.7.
# The function UCT(rootstate, itermax, verbose = False) is towards the bottom of the code.
# It aims to have the clearest and simplest possible code, and for the sake of clarity, the code
# is orders of magnitude less efficient than it could be made, particularly by using a 
# state.GetRandomMove() or state.DoRandomRollout() function.
# 
# Example GameState classes for Nim, OXO and Othello are included to give some idea of how you
# can write your own GameState use UCT in your 2-player game. Change the game to be played in 
# the UCTPlayGame() function at the bottom of the code.
# 
# Written by <NAME>, <NAME>, <NAME> (University of York, UK) September 2012.
# 
# Licence is granted to freely use and distribute for any sensible/legal purpose so long as this comment
# remains in any distributed code.
# 
# For more information about Monte Carlo Tree Search check out our web site at www.mcts.ai

from math import *
import random
from hsgame.game_objects import *
from hsgame.targeting import *
from tests.testing_utils import generate_game_for
from hsgame.agents.basic_agents import DoNothingBot, RandomBot
from hsgame.constants import CHARACTER_CLASS
from hsgame.cards.spells.mage import Pyroblast, Fireball
deck1 = Fireball  # Breaks if you finish with a spell, returning Game Ended
deck2 = Fireball
game = generate_game_for(deck1, deck2, RandomBot, RandomBot)
# game.players[0].max_mana = 4  # Starting with 1 mana seems to give it issues with empty movelists
game.players[0].hero.health = 30
game.players[1].hero.health = 30
game._start_turn()

class HearthState:
    """ A state of the game, i.e. the game board. These are the only functions which are
        absolutely necessary to implement UCT in any 2-player complete information deterministic 
        zero-sum game, although they can be enhanced and made quicker, for example by using a 
        GetRandomMove() function to generate a random move during rollout.
        By convention the players are numbered 1 and 2.
    """
    def __init__(self, game):
        self.game = game
        self.playerJustMoved = 1

    def Clone(self):
        """ Create a deep clone of this game state.
        """
        st = HearthState(self.game)
        st.playerJustMoved = self.playerJustMoved
        return st

    def DoMove(self, move):
        """ Update a state by carrying out the given move.
            Must update playerJustMoved.
        """
        if self.game.players[1].hero.health <= 0 or self.game.players[0].hero.health <= 0:
            return
        def _choose_target(targets):
            return targets[targets.index(move[2])]
        def _choose_index(targets):
            return move[2]
        # print(str(self.game.current_player.mana) + "/" + str(self.game.current_player.max_mana))
        if move[0] == "end_turn":
            self.game._end_turn()
            self.game._start_turn()
            self.playerJustMoved = 1 - self.playerJustMoved
            # print("Player " + str(self.playerJustMoved) + " ends turn")
        elif move[0] == "hero_power":
            self.game.current_player.agent.choose_target = _choose_target
            move[1].power.use()
        elif move[1] == "summon_minion":
            self.game.current_player.agent.choose_index = _choose_index
            self.game.play_card(move[1])  # DoNothingBot summons at index 0, that's ok for now, but need to pass index eventually
        elif move[2] is None:
            self.game.play_card(move[1])
        elif move[0] == "minion_attack" or move[0] == "hero_attack" or move[0] == "mage_power":
            self.game.current_player.agent.choose_target = _choose_target
            move[1].attack()
        elif move[0] == "targeted_spell":
            self.game.current_player.agent.choose_target = _choose_target
            self.game.play_card(move[1])
            # print(str(move[0]) + " used with target " + str(move[2]))
        else:
            raise NameError("DoMove ran into unclassified card", move)

    def GetMoves(self):
        """ Get all possible moves from this state.
            Going to return tuples, untargeted_spell, targetted_spell, equip_weapon, play_secret, mininion_attack, hero_attack, hero_power, end_turn
        """
        if self.game.players[1].hero.health <= 0 or self.game.players[0].hero.health <= 0:
            return []
        valid_moves = [["end_turn", None, None]]
        found_taunt = False
        targets = []
        for enemy in self.game.other_player.minions:
            if enemy.taunt and enemy.can_be_attacked():
                found_taunt = True
            if enemy.can_be_attacked():
                targets.append(enemy)

        if found_taunt:
            targets = [target for target in targets if target.taunt]
        else:
            targets.append(self.game.other_player.hero)

        for minion in copy.copy(self.game.current_player.minions):
            if minion.can_attack():
                for target in targets:
                    valid_moves.append(["minion_attack", minion, target])

        if self.game.current_player.hero.can_attack():
            for target in targets:
                valid_moves.append(["hero_attack", self.game.current_player.hero, target])

        for card in copy.copy(self.game.current_player.hand):
            if card.can_use(self.game.current_player, self.game) and isinstance(card, MinionCard):
                valid_moves.append(["summon_minion", card, 0])
            elif card.can_use(self.game.current_player, self.game) and isinstance(card, WeaponCard):
                valid_moves.append(["equip_weapon", card, None])
            elif card.can_use(self.game.current_player, self.game) and isinstance(card, SecretCard):
                valid_moves.append(["played_secret", card, None])
            elif card.can_use(self.game.current_player, self.game) and not card.targetable:
                valid_moves.append(["untargeted_spell", card, None])
            elif card.can_use(self.game.current_player, self.game) and card.targetable:
                for target in card.targets:
                    valid_moves.append(["targeted_spell", card, target])

        if self.game.current_player.hero.character_class == CHARACTER_CLASS.MAGE and self.game.current_player.hero.power.can_use():
            for target in hsgame.targeting.find_enemy_spell_target(game, lambda x: True):
                valid_moves.append(["hero_power", self.game.current_player.hero, target])
        elif self.game.current_player.hero.character_class == CHARACTER_CLASS.PRIEST and self.game.current_player.hero.power.can_use():
            for target in hsgame.targeting.find_friendly_spell_target(game, lambda x: x.health != x.calculate_max_health()):
                valid_moves.append(["hero_power", self.game.current_player.hero, target])
        elif self.game.current_player.hero.power.can_use():
            valid_moves.append(["hero_power", self.game.current_player.hero, None])
        s = ""
        for move in valid_moves:
            if move[0] == "minion_attack":
                if move[2] == self.game.other_player.hero:
                    s += "[" + str(move[0]) + ", " + str(move[1].card.name) + ", Enemy Hero] "
                else:
                    s += "[" + str(move[0]) + ", " + str(move[1].card.name) + ", " + str(move[2].card.name) + "] "
            elif move[0] == "hero_attack":
                if move[2] == self.game.other_player.hero:
                    s += "[" + str(move[0]) + ", Friendly Hero, Enemy Hero] "
                else:
                    s += "[" + str(move[0]) + ", Friendly Hero, " + str(move[2].card.name) + "] "
            elif move[0] == "hero_power":
                if move[2] is not None:
                    if move[2] == self.game.other_player.hero:
                        s += "[" + str(move[0]) + ", " + str(move[1].character_class) + ", Enemy Hero] "
                    else:
                        s += "[" + str(move[0]) + ", " + str(move[1].character_class) + ", " + str(move[2].card.name) + "] "
                else:
                    s += "[" + str(move[0]) + ", " + str(move[1].character_class) + ", " + str(move[2]) + "] "
            elif move[0] == "end_turn":
                s += "[" + str(move[0]) + ", " + str(move[1]) + ", " + str(move[2]) + "] "
            elif move[0] == "summon_minion":
                s += "[" + str(move[0]) + ", " + str(move[1].name) + ", " + str(move[2]) + "] "
            elif move[2] is None:
                s += "[" + str(move[0]) + ", " + str(move[1].name) + ", " + str(move[2]) + "] "
            elif move[0] == "targeted_spell":
                if move[2] == self.game.other_player.hero:
                    s += "[" + str(move[0]) + ", " + str(move[1].name) + ", Enemy Hero] "
                elif move[2] == self.game.current_player.hero:
                    s += "[" + str(move[0]) + ", " + str(move[1].name) + ", Friendly Hero] "
                else:
                    s += "[" + str(move[0]) + ", " + str(move[1].name) + ", " + str(move[2].card.name) + "] "
        return valid_moves

    def GetResult(self, playerjm):
        """ Get the game result from the viewpoint of playerjm. 
        """
        assert self.game.current_player.hero.health <= 0 or self.game.other_player.hero.health <= 0
        if self.game.current_player.hero.health <= 0:
            return 0
        if self.game.other_player.hero.health <= 0:
            return 1

    def __repr__(self):
        s = "[" + str(self.game.players[1].hero.health) + " hp:" + str(len(self.game.players[1].hand)) + " in hand:" + str(self.game.players[1].deck.left) + " in deck] "
        for minion in copy.copy(self.game.players[1].minions):
            s += str(minion.calculate_attack()) + "/" + str(minion.health) + ":"
        s += "\n[" + str(self.game.players[0].hero.health) + " hp:" + str(len(self.game.players[0].hand)) + " in hand:" + str(self.game.players[1].deck.left) + " in deck] "
        for minion in copy.copy(self.game.players[0].minions):
{'fraction_non_alphanumeric': 0.12030582415111311, 'fraction_numerical': 0.009500787047447718, 'mean_word_length': 2.5635016025641026, 'pattern_counts': {'":': 16, '<': 23, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1471138', 'n_tokens_mistral': 5566, 'n_tokens_neox': 5357, 'n_words': 1389}