mtg/mtg.py

251 lines
8.5 KiB
Python

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:
def __init__(self, cost=None):
self.any = 0
self.mana = Mana()
if cost:
result = self + cost
self.any = result.any
self.mana = result.mana
def __add__(self, other):
result = ManaCost()
if isinstance(other, str):
result.mana = Mana(other)
value = ''
for c in other:
if c not in '0123456789': break
value = value + c
value = int(value) if len(value) > 0 else 0
result.any = value
elif isinstance(other, Mana):
result.mana = other
elif isinstance(other, ManaCost):
result.any = self.any + other.any
result.mana = self.mana + other.mana
return result
def __sub__(self, other):
result = ManaCost()
if isinstance(other, ManaCost):
result.any = self.any - other.any
result.mana = self.mana - other.mana
return result
def __repr__(self):
return '{0}{1}'.format(
self.any if self.any > 0 or self.mana.converted() == 0 else '',
self.mana if self.mana.converted() > 0 else ''
)
def converted(self):
return self.mana.converted() + self.any
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:
def __init__(self, name, type, attributes, cost=0, power=0, toughness=0, 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.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 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