Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 115 additions & 8 deletions src/mtginator/cards.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ class Cost(object):

"""

def __init__(self, fromString="", B=0, G=0, R=0, U=0, W=0, c=0, X=False):
def __init__(self, fromString="", B=0, G=0, R=0, U=0, W=0, C=0, gen=0, X=False):
# C = colorless gen = generic
self.mana = {}
if fromString:
bracks = re.compile(r'[\{\}]')
Expand All @@ -88,18 +89,117 @@ def __init__(self, fromString="", B=0, G=0, R=0, U=0, W=0, c=0, X=False):
else:
print("Unknown mana symbol: %s" % (symbol))
raise

else:
# warning does not handle hybrid/phyrexian use fromString!!!
# does it handle wingding?
self.mana['generic'] = int(c)
self.mana['generic'] = int(gen)
self.mana['C'] = int(C)
self.mana['B'] = int(B)
self.mana['G'] = int(G)
self.mana['R'] = int(R)
self.mana['U'] = int(U)
self.mana['W'] = int(W)
self.mana['X'] = X

for value in land_mana.values():
if value not in self.mana.keys():
self.mana[value] = 0

def add_costs(self,additional_costs):
for cost in additional_costs:
self.mana['generic'] += cost.mana['generic']
self.mana['C'] += cost.mana['C']
self.mana['B'] += cost.mana['B']
self.mana['G'] += cost.mana['G']
self.mana['R'] += cost.mana['R']
self.mana['U'] += cost.mana['U']
self.mana['W'] += cost.mana['W']
self.mana['X'] = self.mana['X'] or cost.mana['X']
return self

class Context(object):
def __init__(self, battlefield, mana_pool):
self.available_mana = {}
self.battlefield = battlefield
#mana_pool is a list of characters that represent mana already in the mana pool
#assume for now that player is only playing basic lands and no other mana sources
if len(mana_pool) == 0:
for permanent in battlefield:
if permanent.is_land() and not permanent.tapped:
if land_mana[permanent.name] in self.available_mana:
self.available_mana[land_mana[permanent.name]] += 1
else:
self.available_mana[land_mana[permanent.name]] = 1
else:
print "floating mana is currently not implemented"
# need to initialize all basic colors
for value in land_mana.values():
if value not in self.available_mana.keys():
self.available_mana[value] = 0

def can_pay(self, cost):
if 'generic' in cost.mana.keys():
total_mana_needed = cost.mana['generic']
else:
total_mana_needed = 0
if 'B' in cost.mana.keys():
if cost.mana['B'] > self.available_mana['B']:
return False
else:
total_mana_needed += cost.mana['B']
if 'U' in cost.mana.keys():
if cost.mana['U'] > self.available_mana['U']:
return False
else:
total_mana_needed += cost.mana['U']
if 'C' in cost.mana.keys():
if cost.mana['C'] > self.available_mana['C']:
return False
else:
total_mana_needed += cost.mana['C']
if 'G' in cost.mana.keys():
if cost.mana['G'] > self.available_mana['G']:
return False
else:
total_mana_needed += cost.mana['G']
if 'R' in cost.mana.keys():
if cost.mana['R'] > self.available_mana['R']:
return False
else:
total_mana_needed += cost.mana['R']
if 'W' in cost.mana.keys():
if cost.mana['W'] > self.available_mana['W']:
return False
else:
total_mana_needed += cost.mana['W']
if (self.available_mana['B'] + self.available_mana['U'] + self.available_mana['C'] + self.available_mana['G'] + self.available_mana['R'] + self.available_mana['W']) < total_mana_needed:
return False
else:
return True

def pay(self, cost):
track_mana = {}
#first pass get tap all the colored mana
for permanent in self.battlefield:
if permanent.is_land() and not permanent.tapped:
if land_mana[permanent.name] in cost.mana:
if land_mana[permanent.name] in track_mana:
if track_mana[land_mana[permanent.name]] < cost.mana[land_mana[permanent.name]]:
track_mana[land_mana[permanent.name]] += 1
permanent.tapped = True
else:
track_mana[land_mana[permanent.name]] = 1
permanent.tapped = True
if 'generic' not in cost.mana:
return True
#second pass tap random mana equal to generic mana
random_taps = 0
for permanent in self.battlefield:
if permanent.is_land() and not permanent.tapped:
permanent.tapped = True
random_taps += 1
if random_taps == cost.mana['generic']:
return True

class Card(object):

Expand Down Expand Up @@ -230,11 +330,18 @@ def pay_cost(self, context):

def play(self, context):

self.pay_cost(context) # need some sort of game context object
self.zone = 'battlefield'
self.tapped = False
if self.cipt:
self.tapped = True
if context is None:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably we should remove this case, but it breaks a test or script.

self.zone = 'battlefield'
self.tapped = False
if self.cipt:
self.tapped = True
else:
self.pay_cost(context) # need some sort of game context object
self.zone = 'battlefield'
self.tapped = False
if self.cipt:
self.tapped = True


def __repr__(self):
return "[ %s (%s) ]" % (self.name, self.card_data.get('manaCost', '0'))
Expand Down
6 changes: 4 additions & 2 deletions src/mtginator/decks.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import sys
sys.path.insert(0,'/src/mtginator/')
import random
import json
import mtginator.cards as cards
import cards
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do the tests still work with this change?

import re


Expand Down Expand Up @@ -64,7 +66,7 @@ def load_deck(self, input_file='', file_type="txt"):

class CardDB(object):
''' this reads all MTG cards from JSON. It should be memoized '''
def __init__(self, input_file='data/mtgjson/AllSets-x.json'):
def __init__(self, input_file='./data/AllSets-x.json'):
self.all = json.load(open(input_file))

hashed = {}
Expand Down
67 changes: 48 additions & 19 deletions src/mtginator/game.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import sys
sys.path.insert(0,'/src/mtginator/')
import random

import cards

class Game(object):
''' Object that represents the game/board state of a given game '''
Expand All @@ -24,7 +26,7 @@ def is_over(self, turn=None, draw=(None, None)):
if turn and turn > self.maxturns:
return self.players
for player in self.players:
if (player.life <= 0) or (player.poison <= 0) or (len(player.deck) == 0 and player in draw):
if (player.life <= 0) or (player.poison <= 0) or (len(player.deck.main) == 0 and player in draw):
losers.update(player)

return list(losers)
Expand All @@ -38,7 +40,19 @@ def __str__(self):
class Mana(object):
''' Object that represents mana-producing board state of a player '''
def __init__(self, board, extras=[]):
for permanent in board:
self.available_mana = []
#extras is a list of characters that represent mana already in the mana pool
#assume for now that player is only playing basic lands and no other mana sources
if len(extras) == 0:
self.available_mana[:] = []
for permanent in board:
if permanent.is_land() and not permanent.tapped:
self.available_mana.append(cards.land_mana[permanent.name])
print self.available_mana





class Player(object):
''' Object that represents a player of the game'''
Expand All @@ -61,7 +75,7 @@ def _satisfied(self, rules, n):
if len(self.hand) and len(self.hand) < 5:
# keep all 4s
return True
elif len([c for c in self.hand if c.isLand()]) and len([c for c in self.hand if not c.isLand()]):
elif len([c for c in self.hand if c.is_land()]) and len([c for c in self.hand if not c.is_land()]):
# literally "lands and spells"
return True
else:
Expand Down Expand Up @@ -92,28 +106,39 @@ def main(self):
self.make_play(plays)

def land_drop(self):
lands = [c for c in self.hand if c.is_land]
pick = random.choice(lands)
# Note: should implment some color optimization
pick.play()
print("Played {} as Land for turn [ hand size: {} ]".format(pick, len(self.hand)))
lands = [c for c in self.hand if c.is_land()]
if len(lands) > 0:
pick = random.choice(lands)
# Note: should implment some color optimization
pick.play(None) #lands don't need context to play
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh here you have a null context, probably this should assume a board state/context, even if it's irrelvant for land drops

self.battlefield.append(pick)
self.hand.remove(pick)
print("Played {} as Land for turn [ hand size: {} ]".format(pick, len(self.hand)))

def cleanup(self):
while(len(self.hand) > self.max_handsize):
self.choose_discard()

def choose_discard(self):
discard = self.hand.pop(random.randrange(len(self.hand)))
self.graveyard.append(discard)

print("Dicarding {}".format(discard))

def make_play(self, possible_plays):
card_to_play = random.choice(possible_plays)
card_to_play.play()
print("Playing {}".format(card_to_play))
if possible_plays:
card_to_play = random.choice(possible_plays)
card_to_play.play(cards.Context(self.battlefield, []))
self.hand.remove(card_to_play)
if card_to_play.is_permanent():
self.battlefield.append(card_to_play)
else:
self.graveyard.append(card_to_play)
print("Playing {}".format(card_to_play))

def available_mana(self):
return Mana(self.battlefield)
''' return mana object based on board state '''
pass

def reset(self):
self.deck.main = self.deck.main + self.hand + self.graveyard + self.exile + self.battlefield
Expand All @@ -134,7 +159,7 @@ def mulligan(self, rules=None, n=7, verbose=True):
else:
print("Mulling to {}...".format(n))
for i in range(0, n):
self.hand.append(self.deck.drawCard())
self.hand.append(self.deck.draw_card())

while(not self._satisfied(rules, n)):
self.mulligan(rules, n-1, verbose=verbose)
Expand All @@ -144,16 +169,20 @@ def mulligan(self, rules=None, n=7, verbose=True):

def enumerate_plays(self):
''' For a given hand (or metahand) and available mana, what plays are available to Player
Returns: Set of Card objects
Returns: List of Card objects
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I made this a Set() on purpose, like what if you have Forest, Forest, Forest, Llnaowar Elves? You don't need an array of 4. But possibly i overlooked something.

'''
list_of_plays = []
if not self.hand:
return set()
return list_of_plays

context = cards.Context(self.battlefield,[])

for card in self.hand:
pass
# something like if card.can_play... add to list
if not card.is_land():
if context.can_pay(card.mana_cost):
list_of_plays.append(card)

return {self.hand}
return list_of_plays

def __repr__(self):
return("Name: {} Deck {} Life {} Poison {} #Cards-in-Hand {} WP% {}".format(self.name,
Expand Down
34 changes: 21 additions & 13 deletions src/mtginator/sim.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
#!/usr/bin/env python

import sys
sys.path.insert(0,'/src/mtginator/')
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok this is horrible and we need to fix imports if that's what you need.

import os
import mtginator.decks as decks
import mtginator.game as game
import decks
import game

DECKS_DIR = 'data/decks/'

DECKS_DIR = './data/decks/'


def run(turns, rounds, decks, goldfish=True):
Expand All @@ -32,16 +34,22 @@ def run(turns, rounds, decks, goldfish=True):
print("After mulligans player %s has %s cards." % (player.name, len(player.hand)))
print("%s" % ([str(c) for c in player.hand]))
game_state = game.Game(ourplayers, maxturns=turns)
current_turn = 0
(first_player, second_player) = game_state.play_or_draw()
print("{} goes first".format(first_player.name))
print("{} goes second".format(second_player.name))

while(not game_state.is_over(turn=current_turn)):
current_turn += 1
print(game_state)
first_player.take_turn()
second_player.take_turn()
game_state.turn = 0
if not goldfish:
(first_player, second_player) = game_state.play_or_draw()
print("{} goes first".format(first_player.name))
print("{} goes second".format(second_player.name))
while(not game_state.is_over(turn=game_state.turn)):
game_state.turn += 1
print(game_state)
first_player.take_turn()
second_player.take_turn()
else: #single player only one player is taking turns
while(not game_state.is_over(turn=game_state.turn)):
game_state.turn += 1
print(game_state)
ourplayers[0].take_turn()



def main():
Expand Down