339 lines
12 KiB
Python
339 lines
12 KiB
Python
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, unicode):
|
|
other = str(other)
|
|
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 = '[({](.*?)\/(.*?)[)}]'
|
|
symbolPattern = r'X?Y?Z?\d*S*(\((\d+|[{0}])/(\d+|[{0}])\))*[{0}]*'.format(''.join(Mana.types.keys()))
|
|
def __init__(self, cost=None):
|
|
self.any = 0
|
|
self.x = False
|
|
self.y = False
|
|
self.z = False
|
|
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
|
|
self.x = result.x
|
|
self.y = result.y
|
|
self.z = result.z
|
|
def __add__(self, other):
|
|
result = ManaCost()
|
|
if isinstance(other, unicode):
|
|
other = str(other)
|
|
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.upper():
|
|
if c == 'X':
|
|
result.x = True
|
|
if c == 'Y':
|
|
result.y = True
|
|
if c == 'Z':
|
|
result.z = True
|
|
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
|
|
result.x = self.x or other.x
|
|
result.y = self.y or other.y
|
|
result.z = self.z or other.z
|
|
return result
|
|
def __sub__(self, other):
|
|
result = ManaCost()
|
|
if isinstance(other, ManaCost):
|
|
#TODO: Subtract hybrid symbols
|
|
result.any = self.any - other.any
|
|
result.mana = self.mana - other.mana
|
|
result.snow = self.snow - other.snow
|
|
return result
|
|
def __repr__(self):
|
|
return '{0}{1}{2}{3}{4}'.format(
|
|
'X' if self.x else '' + 'Y' if self.y else '' + 'Z' if self.z else '',
|
|
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.is_tapped = False
|
|
self.abilities = []
|
|
|
|
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 colors(self):
|
|
return [color for color, cost in ManaCost(self.cost).mana.mana.iteritems() if cost > 0]
|
|
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()
|