import re import copy import random import logging from observable import Observable class Game: def __init__(self): self.players = [] class Mana: types = { 'W': 'White', 'U': 'Blue', 'B': 'Black', 'R': 'Red', 'G': 'Green', } def __init__(self, mana=None): self.mana = {} for type in Mana.types.keys(): self.mana[type] = 0 if mana: # Can't just set self to result... ? result = self + mana self.mana = result.mana def __add__(self, other): result = Mana() if isinstance(other, str): for m in [c for c in other.upper() if c in Mana.types.keys()]: result.mana[m] = result.mana[m] + 1 elif isinstance(other,Mana): for (key, value) in other.mana.iteritems(): result.mana[key] = self.mana[key] + value else: # TODO: raise exception raise Exception() pass return result def __sub__(self, other): result = Mana() if isinstance(other, Mana): for (key, value) in other.mana.iteritems(): result.mana[key] = self.mana[key] - value if result.mana[key] < 0: raise Exception('Insufficient {0} Mana'.format(Mana.types[key])) elif isinstance(other,ManaCost): for (key, value) in other.mana.mana.iteritems(): result.mana[key] = self.mana[key] - value if result.mana[key] < 0: raise Exception('Insufficient {0} Mana'.format(Mana.types[key])) remaining = other.any for (key, value) in result.mana.iteritems(): while result.mana[key] > 0 and remaining > 0: result.mana[key] = result.mana[key] - 1 remaining = remaining - 1 if remaining > 0: raise Exception('Insufficient Mana') return result def __repr__(self): return ''.join([type * count for (type, count) in self.mana.iteritems()]) if self.converted() > 0 else '0' def converted(self): return sum(self.mana.values()) class ManaCost: hybridPattern = '[({](.*?)\/(.*?)[)}]' def __init__(self, cost=None): self.any = 0 self.mana = Mana() self.snow = 0 self.hybrid = [] if cost: result = self + cost self.any = result.any self.mana = result.mana self.hybrid = result.hybrid self.snow = result.snow def __add__(self, other): result = ManaCost() if isinstance(other, str): symbols = [] hybrid = re.findall(ManaCost.hybridPattern, other) # Remove the hybrid costs from the mana cost string before continuing other = re.sub(ManaCost.hybridPattern, '', other) # Clear any other unecessary tokens other = re.sub('[({})]', '', other) result.hybrid = self.hybrid + [(ManaCost(a), ManaCost(b)) for (a,b) in hybrid] result.mana = self.mana + Mana(other) value = '' for c in other: if c not in '0123456789': break value = value + c result.snow = self.snow + len([c for c in other.lower() if c == 's']) value = int(value) if len(value) > 0 else 0 result.any = self.any + value elif isinstance(other, Mana): result.mana = self.mana + other elif isinstance(other, ManaCost): result.any = self.any + other.any result.mana = self.mana + other.mana result.hybrid = self.hybrid + other.hybrid result.snow = self.snow + other.snow return result def __sub__(self, other): result = ManaCost() if isinstance(other, ManaCost): result.any = self.any - other.any result.mana = self.mana - other.mana result.hybrid = self.hybrid - other.hybrid result.snow = self.snow - other.snow return result def __repr__(self): return '{0}{1}{2}{3}'.format( self.any if self.any > 0 or (self.mana.converted() == 0 and not self.hybrid and not self.snow) else '', 'S' * self.snow, ''.join(['({0}/{1})'.format(a, b) for a,b in self.hybrid]), self.mana if self.mana.converted() > 0 else '' ) def converted(self): hybrid = sum([min(a, b).converted() for a, b in self.hybrid]) return self.mana.converted() + self.any + hybrid + self.snow class Player: def __init__(self, name, game, deck=None): self.name = name self.game = game self.life = 20 self.mana = Mana() self.deck = None self.library = CardList(self, 'library') self.hand = CardList(self, 'hand') self.graveyard = CardList(self, 'graveyard') self.battlefield = CardList(self, 'battlefield') self.setDeck(deck) self.lifeChanged = Observable() self.defeated = Observable() self.casts = Observable() logging.debug('Initialized %s', self) def __repr__(self): return 'Player: {0} [Life:{1},Mana:{2},Hand:{3},Library:{4}]'.format(self.name, self.life, self.mana, len(self.hand), len(self.library) if self.library else self.library) def setDeck(self, deck): if not deck: return self.deck = copy.deepcopy(deck) self.deck.owner = self self.library = copy.copy(self.deck) self.library.zone = 'library' for card in self.deck: # Re-initialize so the references are correct card.__init__() card.owner = self def setLife(self, life): self.life = life self.lifeChanged.emit(self.life) def affectLife(self, amount): self.life = self.life + amount if amount: self.lifeChanged.emit(self.life) def payMana(self, cost): try: self.mana -= cost except: logging.debug('%s could not pay mana cost %s', self, cost) return False logging.debug('%s paid mana cost %s', self, cost) return True def draw(self, count=1): for i in xrange(count): card = self.library[0] card.move(self.library, self.hand) logging.debug('%s drew %s', self, card) def cast(self, card): logging.debug('%s attempts to cast %s', self, card) if not self.payMana(card.cost): logging.debug('%s failed to cast %s: Not enough mana', self, card) return False card.move(self.hand, self.battlefield) logging.debug('%s successfully casts %s', self, card) self.casts.emit(card) return True class Card: rarities = { 'L': 'Land', 'C': 'Common', 'U': 'Uncommon', 'R': 'Rare', 'M': 'Mythic Rare', 'S': 'Special', } def __init__(self, name, type, attributes, cost=0, power=0, toughness=0, sets=None, rarity=None, text=[], owner=None): self.name = name self.type = type.lower() self.attributes = [a.lower() for a in attributes] self.cost = ManaCost(cost) self.power = power self.toughness = toughness self.sets = sets self.rarity = rarity self.text = text self.colors = [color for color, cost in self.cost.mana.mana.iteritems() if cost > 0] self.is_tapped = False self.abilities = [] # Get color from land type if 'land' in self.type.split(' '): if 'mountain' in self.attributes: self.colors.append('R') if 'plains' in self.attributes: self.colors.append('W') if 'swamp' in self.attributes: self.colors.append('B') if 'island' in self.attributes: self.colors.append('U') if 'forest' in self.attributes: self.colors.append('G') if not self.colors: # Try to determine the card's color based on its abilities. Not ideal, but maybe it'll help for now for line in self.text: for m in [ManaCost(c) for c in re.findall('{(\d*[WURGB]*)}',line)]: self.colors.extend([color for color, c in m.mana.mana.iteritems() if c > 0 and color not in self.colors]) self.owner = owner self.zone = None # Events self.moved = Observable() self.tapped = Observable() self.attacked = Observable() self.store() def __repr__(self): return 'Card: [{3}] {0} -- {1}: {2} [{4}/{5}]{6}'.format(self.name, self.type.title(), ' '.join([a.capitalize() for a in self.attributes]), self.cost, self.power, self.toughness, ' [T]' if self.is_tapped else '') def __mul__(self, other): result = [] for i in xrange(other): result.append(copy.copy(self)) return result def store(self): self.__stored = copy.copy(self) def restore(self): self = copy.copy(self.__stored) def move(self, origin, destination): origin.remove(self) destination.append(self) logging.debug('%s moved from %s to %s', self, origin.zone, destination.zone) self.moved.emit(origin.zone, destination.zone) def tap(self): if self.is_tapped: return False self.is_tapped = True logging.debug('%s is tapped', self) self.tapped.emit() return True class Ability: def __init__(self, card, name='Unknown', cost=0, tap=False): self.card = card self.name = name self.cost = ManaCost(cost) self.tap = tap self.init() def __repr__(self): return 'Ability: {0} [{1}{2}]'.format(self.name, self.cost, ', T' if self.tap else '') def init(self): pass def run(self): pass def activate(self): logging.debug('%s attempting to activate %s on %s', self.card.owner, self, self.card) if self.tap and self.card.is_tapped: logging.debug('%s failed to activate %s on %s', self.card.owner, self, self.card) return False if self.card.owner.payMana(self.cost): if self.tap: self.card.tap() logging.debug('%s succeeded activating %s on %s', self.card.owner, self, self.card) self.run() return True logging.debug('%s failed to activate %s on %s', self.card.owner, self, self.card) return False class CardList(list): def __init__(self, owner, zone): list.__init__(self) self.owner = owner self.zone = zone def append(self, item): item.list = self list.append(self, item) class Deck(CardList): def __init__(self): CardList.__init__(self, None, 'deck') def shuffle(self): random.shuffle(self) def cards(self): return self.__cards def load(self, filename, db): with open(filename, 'r') as f: for line in f: line = line.strip() if not re.match('^\d+ .+$', line): continue line = line.split(' ') count = int(line.pop(0)) name = ' '.join(line) card = db.getCard(name) if not card: # TODO: The database should log an error continue self.extend(card * count) def exportWagic(self, filename, name=None): cards = {} for card in self: cards[card] = cards[card] + 1 if card in cards.keys() else 1 lines = [ '#NAME:{0}\n'.format(name if name else filename), '#DESC: Exported deck\n\n', ] for card, count in cards.iteritems(): sets = card.sets if not sets: print 'Card not supported:', card continue line = '{0} ({1}) *{2}\n'.format(card.name, card.sets[-1], count) lines.append(line) with open(filename, 'w') as f: f.writelines(lines) f.close()