<commit_before>from itertools import chain
from hearthstone.enums import CardType, PlayReq, Race, Rarity, Step, Zone
from . import actions, cards, rules
from .aura import TargetableByAuras
from .entity import BaseEntity, Entity, boolean_property, int_property, slot_property
from .managers import CardManager
from .targeting import is_valid_target
from .utils import CardList
from .exceptions import InvalidAction


THE_COIN = "GAME_005"


def Card(id):
	data = cards.db[id]
	subclass = {
		CardType.HERO: Hero,
		CardType.MINION: Minion,
		CardType.SPELL: Spell,
		CardType.ENCHANTMENT: Enchantment,
		CardType.WEAPON: Weapon,
		CardType.HERO_POWER: HeroPower,
	}[data.type]
	if subclass is Spell and data.secret:
		subclass = Secret
	return subclass(data)


class BaseCard(BaseEntity):
	Manager = CardManager
	delayed_destruction = False

	def __init__(self, data):
		self.data = data
		super().__init__()
		self.requirements = data.requirements.copy()
		self.id = data.id
		self.controller = None
		self.choose = None
		self.parent_card = None
		self.aura = False
		self.heropower_damage = 0
		self._zone = Zone.INVALID
		self.tags.update(data.tags)

	def __str__(self):
		return self.name

	def __hash__(self):
		return self.id.__hash__()

	def __repr__(self):
		return "<%s (%r)>" % (self.__class__.__name__, self.__str__())

	def __eq__(self, other):
		if isinstance(other, BaseCard):
			return self.id.__eq__(other.id)
		elif isinstance(other, str):
			return self.id.__eq__(other)
		return super().__eq__(other)

	@property
	def game(self):
		return self.controller.game

	@property
	def zone(self):
		return self._zone

	@zone.setter
	def zone(self, value):
		self._set_zone(value)

	def _set_zone(self, value):
		old = self.zone

		if old == value:
			self.logger.warning("%r attempted a same-zone move in %r", self, old)
			return

		if old:
			self.logger.debug("%r moves from %r to %r", self, old, value)

		caches = {
			Zone.HAND: self.controller.hand,
			Zone.DECK: self.controller.deck,
			Zone.GRAVEYARD: self.controller.graveyard
		}
		if caches.get(old) is not None:
			caches[old].remove(self)
		if caches.get(value) is not None:
			caches[value].append(self)
		self._zone = value

		if value == Zone.PLAY:
			self.play_counter = self.game.play_counter
			self.game.play_counter += 1

	def buff(self, target, buff, **kwargs):
		"""
		Summon \a buff and apply it to \a target
		If keyword arguments are given, attempt to set the given
		values to the buff. Example:
		player.buff(target, health=random.randint(1, 5))
		NOTE: Any Card can buff any other Card. The controller of the
		Card that buffs the target becomes the controller of the buff.
		"""
		ret = self.controller.card(buff, self)
		ret.source = self
		ret.apply(target)
		for k, v in kwargs.items():
			setattr(ret, k, v)
		return ret

	def is_playable(self) -> bool:
		"""
		Return whether the card can be played.
		Do not confuse with is_summonable()
		"""
		return False

	def play(self, *args):
		raise NotImplementedError


class PlayableCard(BaseCard, Entity, TargetableByAuras):
	windfury = int_property("windfury")
	playable_zone = Zone.HAND

	def __init__(self, data):
		self.cant_play = False
		self.entourage = CardList(data.entourage)
		self.has_battlecry = False
		self.has_combo = False
		self.overload = 0
		self.target = None
		self.rarity = Rarity.INVALID
		self.choose_cards = CardList()
		self.morphed = None
		super().__init__(data)

	@property
	def events(self):
		if self.zone == Zone.HAND:
			return self.data.scripts.Hand.events
		return self.base_events + self._events

	@property
	def cost(self):
		ret = 0
		if self.zone == Zone.HAND:
			mod = self.data.scripts.cost_mod
			if mod is not None:
				r = mod.evaluate(self)
				# evaluate() can return None if it's an Evaluator (Crush)
				if r:
					ret += r
		ret = self._getattr("cost", ret)
		return max(0, ret)

	@cost.setter
	def cost(self, value):
		self._cost = value

	@property
	def powered_up(self):
		"""
		Returns True whether the card is "powered up".
		"""
		if not self.data.scripts.powered_up:
			return False
		for script in self.data.scripts.powered_up:
			if not script.check(self):
				return False
		return True

	@property
	def entities(self):
		return chain([self], self.buffs)

	@property
	def zone_position(self):
		if self.zone == Zone.HAND:
			return self.controller.hand.index(self) + 1
		return None

	def _set_zone(self, zone):
		old_zone = self.zone
		super()._set_zone(zone)
		if old_zone == Zone.PLAY and zone not in (Zone.GRAVEYARD, Zone.SETASIDE):
			self.clear_buffs()

		if self.zone == Zone.HAND:<commit_msg>Return 0 in zone_position instead of None<commit_after>from itertools import chain
from hearthstone.enums import CardType, PlayReq, Race, Rarity, Step, Zone
from . import actions, cards, rules
from .aura import TargetableByAuras
from .entity import BaseEntity, Entity, boolean_property, int_property, slot_property
from .managers import CardManager
from .targeting import is_valid_target
from .utils import CardList
from .exceptions import InvalidAction


THE_COIN = "GAME_005"


def Card(id):
	data = cards.db[id]
	subclass = {
		CardType.HERO: Hero,
		CardType.MINION: Minion,
		CardType.SPELL: Spell,
		CardType.ENCHANTMENT: Enchantment,
		CardType.WEAPON: Weapon,
		CardType.HERO_POWER: HeroPower,
	}[data.type]
	if subclass is Spell and data.secret:
		subclass = Secret
	return subclass(data)


class BaseCard(BaseEntity):
	Manager = CardManager
	delayed_destruction = False

	def __init__(self, data):
		self.data = data
		super().__init__()
		self.requirements = data.requirements.copy()
		self.id = data.id
		self.controller = None
		self.choose = None
		self.parent_card = None
		self.aura = False
		self.heropower_damage = 0
		self._zone = Zone.INVALID
		self.tags.update(data.tags)

	def __str__(self):
		return self.name

	def __hash__(self):
		return self.id.__hash__()

	def __repr__(self):
		return "<%s (%r)>" % (self.__class__.__name__, self.__str__())

	def __eq__(self, other):
		if isinstance(other, BaseCard):
			return self.id.__eq__(other.id)
		elif isinstance(other, str):
			return self.id.__eq__(other)
		return super().__eq__(other)

	@property
	def game(self):
		return self.controller.game

	@property
	def zone(self):
		return self._zone

	@zone.setter
	def zone(self, value):
		self._set_zone(value)

	def _set_zone(self, value):
		old = self.zone

		if old == value:
			self.logger.warning("%r attempted a same-zone move in %r", self, old)
			return

		if old:
			self.logger.debug("%r moves from %r to %r", self, old, value)

		caches = {
			Zone.HAND: self.controller.hand,
			Zone.DECK: self.controller.deck,
			Zone.GRAVEYARD: self.controller.graveyard
		}
		if caches.get(old) is not None:
			caches[old].remove(self)
		if caches.get(value) is not None:
			caches[value].append(self)
		self._zone = value

		if value == Zone.PLAY:
			self.play_counter = self.game.play_counter
			self.game.play_counter += 1

	def buff(self, target, buff, **kwargs):
		"""
		Summon \a buff and apply it to \a target
		If keyword arguments are given, attempt to set the given
		values to the buff. Example:
		player.buff(target, health=random.randint(1, 5))
		NOTE: Any Card can buff any other Card. The controller of the
		Card that buffs the target becomes the controller of the buff.
		"""
		ret = self.controller.card(buff, self)
		ret.source = self
		ret.apply(target)
		for k, v in kwargs.items():
			setattr(ret, k, v)
		return ret

	def is_playable(self) -> bool:
		"""
		Return whether the card can be played.
		Do not confuse with is_summonable()
		"""
		return False

	def play(self, *args):
		raise NotImplementedError


class PlayableCard(BaseCard, Entity, TargetableByAuras):
	windfury = int_property("windfury")
	playable_zone = Zone.HAND

	def __init__(self, data):
		self.cant_play = False
		self.entourage = CardList(data.entourage)
		self.has_battlecry = False
		self.has_combo = False
		self.overload = 0
		self.target = None
		self.rarity = Rarity.INVALID
		self.choose_cards = CardList()
		self.morphed = None
		super().__init__(data)

	@property
	def events(self):
		if self.zone == Zone.HAND:
			return self.data.scripts.Hand.events
		return self.base_events + self._events

	@property
	def cost(self):
		ret = 0
		if self.zone == Zone.HAND:
			mod = self.data.scripts.cost_mod
			if mod is not None:
				r = mod.evaluate(self)
				# evaluate() can return None if it's an Evaluator (Crush)
				if r:
					ret += r
		ret = self._getattr("cost", ret)
		return max(0, ret)

	@cost.setter
	def cost(self, value):
		self._cost = value

	@property
	def powered_up(self):
		"""
		Returns True whether the card is "powered up".
		"""
		if not self.data.scripts.powered_up:
			return False
		for script in self.data.scripts.powered_up:
			if not script.check(self):
				return False
		return True

	@property
	def entities(self):
		return chain([self], self.buffs)

	@property
	def zone_position(self):
		"""
		Returns the card's position (1-indexed) in its zone, or 0 if not available.
		"""
		if self.zone == Zone.HAND:
			return self.controller.hand.index(self) + 1
		return 0

	def _set_zone(self, zone):
		old_zone = self.zone
		super()._set_zone(zone)
		if old_zone == Zone.PLAY and zone not in (Zone.GRAVEYARD, Zone.SETASIDE):
			self.clear_buffs()

		if self.zone == Zone.HAND:
{'fraction_non_alphanumeric': 0.11241016840072938, 'fraction_numerical': 0.002788801887804355, 'mean_word_length': 3.723404255319149, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, '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': '4579505', 'n_tokens_mistral': 3585, 'n_tokens_neox': 3201, 'n_words': 910}