http://i.imgur.com/9Xin1x0.png
Here are the classes and their relations in this test case of Black Jack.
In python IDLE I import "BlackJackGame" and bind "game" variable to "BlackJackGame()", and thus I have initialized a game of Black Jack.
Now what if I want to initialize many games of Black Jack, and I want the "Card" class to have different attributes in every game?
For example, in one Black Jack game, the cards would have the classic suits, such as ['Diamond', 'Club', 'Heart', 'Spade'], and in other they would have made up suits, such as ['Test1', 'Test2', 'Test3', 'Test4'].
In short, how can I modify "Card" classes attributes when I'm binding BlackJackGame() to a variable, so that different games of Black Jack have different kinds of cards?
EDIT: I have coded a test case to show this problem in practice
class Card(object):
def __init__(self, attribute = 'Test 1'):
self.attribute = attribute
class Player(object):
def __init__(self):
self.card = Card()
class Deck(object):
def __init__(self, card = Card()):
self.card = card
class MainDeck(object):
def __init__(self, deck = Deck()):
self.deck = deck
class BlackJackGame(object):
def __init__(self, maindeck = MainDeck(), player = Player()):
self.maindeck = maindeck
self.player = player
#Initializing first game
game1 = BlackJackGame()
#We access "Card" classes that reside in "Deck" and "Player",
#to test "Card" class's variable.
print(game1.maindeck.deck.card.attribute) #Prints 'Test 1'
print(game1.player.card.attribute) #Prints 'Test 1'
#Initializing second game, but this time so that "Card" class's
#attribute has a different value, when card is in "Deck"
game2 = BlackJackGame(maindeck = MainDeck(deck = Deck(card = Card(attribute = 'Test 2'))))
#We test if the attribute assignment works correctly
print(game2.maindeck.deck.card.attribute) #Prints 'State 2'
Now you see, that in the second Black Jack game, I want the Card to have different attribute value than in the first one, but my method of doing this is very painful to write, and it only changes the Cards that are in Deck, I want the Card to have different kind of attribute value in different games of Black Jack.
Card = namedtuple('Card', 'attribute value')
class Deck(object):
def __init__(suits, basic_card):
'''Take list of suits'''
self.cards = [basic_card._replace(suit=suit, value=value)
for value in range(1, 14)
for suit in suits]
Usage:
poker = BlackJackGame(Deck(suits=['Hearts', 'Spades', 'Clubs', 'Diamonds'],
basic_card=Card(attribute='Test 1', value=0))
uno = BlackJackGame(Deck(suits=['Red', 'Yellow', 'Green', 'Blue'],
basic_card=Card(attribute='Test 2', value=0)))
Related
Question: I am attempting to make a blackjack game. I am at the part where I am trying to make a player class with a hand size which contains an empty list. My intuition tells me that I need to be targeting the deck where the cards are and to use the append and the pop methods. I have already written a function in another class called the deal cards method. Am I able to use functions from other classes inside a class? Or do I have to come up with some way of my class targeting the deck and getting the cards?
# Work on the player class give them the ability to have a hand and to deal the
# cards into that hand
from random import shuffle
class Card:
def __init__(self, rank, suit):
self.rank = rank
self.suit = suit
def __str__(self):
return str(self.rank) + " of " + self.suit
class Deck:
deck = []
def __init__(self):
suits = "Hearts", "Diamonds", "Clubs", "Spades"
ranks = 2,3,4,5,6,7,8,9,10,"J","Q","K","A"
for suit in suits:
for rank in ranks:
card= Card(rank,suit)
self.deck.append(card)
# need to be able to deal cards
def deal_card(self):
dealt_card = self.deck.pop()
return dealt_card # return gives back the value to whomever called it.
# needs to be able to shuffle
def shuffle(self):
shuffle(self.deck)
# display the deck
def display(self):
for card in self.deck:
print(card)
class Player:
def __init__(self, name, isdealer):
self.name = name
self.hand = []
self.isdealer = isdealer
def draw_cards(self):
player_cards = dealt_card();
#scratchpad thinking area
# self.hand is an empty list. I want to get cards from the deck and put them in that list! SO I need to target
# the deck somehow.
# maybe with pop?
# I would use append to add the result to my hand.
# Work on the player class give them the ability to have a hand and to deal the
# cards into that hand
def main(): # main can be called blackjack or gameplay
# Welcome the player and explain the rules
print("""Welcome to Blackjack! Here are the Rules
Try to get as close to 21 without going over.
Kings, Queens, and Jacks are worth 10 points.
Aces are worth 1 or 11 points.
Cards 2 through 10 are worth their face value.
(H)it to take another card.
(S)tand to stop taking cards.
The dealer stops hitting at 17""")
# Run a game of blackjack
# create a deck of cards outside of the main.
deck = Deck()
deck.shuffle()
deck.display()
# Make player 1 and the dealer
# while True:
# return cards to the deck
# Shuffle the deck of cards close to the start to start a new game.
# Deal 2 cards to the players
# Loop: display hands and scores
# Ask them to hit or stand.
# Determine Winner
# If the program is run (instead of imported), run the game:
if __name__ == '__main__':
main()
I have tried using the function from the deck class but I am unsure I can use methods from other classes. I know that append can be used to add to strings. I know I need to target the deck somehow but it is in another class.
Am I able to use functions from other classes inside a class? Or do I have to come up with some way of my class targeting the deck and getting the cards?
There are several ways to approach this dilemma:
You could have a method in the Deck class that takes a player instance as argument. It then has access to both the deck and the player's hand
You could have a method in the Player class that takes the deck instance as argument. This is similar, but approaching it from the opposite side
You could create yet another class, like BlackJack that would have attributes for the deck and the players, and then its methods can access both deck and player without issue. This looks like a nicer solution, but it seems less compatible with the main function you have. It would mean that all of the logic in main would move into that new class.
In below code I have taken the first approach in the deal method.
I also took some other decisions:
Make Deck a subclass of list, because a deck is mostly like a list with maybe just one or two extra methods. This means a deck instance no longer needs a deck attribute, but can apply pop, append, ..etc directly to self.
Make Player a subclass of Deck, because a player is mostly like a deck (a "hand") with a name and a score. This means there is no more hand attribute, as self is that hand.
Add a value attribute to the Card class, which would calculate the value of the card (1 for Ace, ...etc) at construction.
Add a score method to the Player class which sums up the card values, adding 10 if the hand includes at least one ace and that would not bust the score.
I also implemented the game loop with hitting/standing/quitting, and reporting the winner
Maybe some of this might not be acceptable for the code challenge you might be taking, but I thought these were the right thing to do:
from random import shuffle
class Card:
def __init__(self, rank, suit):
self.rank = rank
self.suit = suit
# Add a value to use in the blackjack scoring
self.value = rank if isinstance(rank, int) else (1 if rank == "A" else 10)
def __str__(self):
return f"{self.rank} of {self.suit}"
class Deck(list): # Inherit from list as a deck is mostly a list
def __init__(self):
suits = "Hearts", "Diamonds", "Clubs", "Spades"
ranks = 2,3,4,5,6,7,8,9,10,"J","Q","K","A"
for suit in suits:
for rank in ranks:
card = Card(rank, suit)
self.append(card)
def deal(self, player): # Pass player as argument so to act on deck & player
player.append(self.pop())
return player[-1]
def collect(self, player):
self.extend(player)
player.clear()
def __str__(self):
return ", ".join(map(str, self))
class Player(Deck): # Inherit from Deck, as a player really is a deck with a name
def __init__(self, name, isdealer):
self.name = name
self.isdealer = isdealer
def score(self):
total = 0
hasAce = False
for card in self:
total += card.value
hasAce = hasAce or card.value == 1
return total + 10 if hasAce and total < 12 else total
def __str__(self):
return f"{self.name} has {self.score()} ({super().__str__()})"
def main():
# Welcome the player and explain the rules
print("""Welcome to Blackjack! Here are the Rules
Try to get as close to 21 without going over.
Kings, Queens, and Jacks are worth 10 points.
Aces are worth 1 or 11 points.
Cards 2 through 10 are worth their face value.
(H)it to take another card.
(S)tand to stop taking cards.
The dealer stops hitting at 17
""")
deck = Deck()
# Make player 1 and the dealer
player = Player(input("What's your name? "), False)
dealer = Player("Dealer", True)
while True:
# return cards to the deck
deck.collect(player)
deck.collect(dealer)
shuffle(deck)
# Deal 2 cards to the players
deck.deal(player)
deck.deal(player)
deck.deal(dealer)
deck.deal(dealer)
# Loop: display hands and scores
print()
print("New game:")
print(dealer)
print(player)
# Ask them to hit or stand.
while player.score() <= 21:
choice = input(f"{player.name}, do you want to [h]it, [s]tand or [q]uit? ").upper()
if choice == "Q":
return
if choice == "S":
break
if choice == "H":
print(f"{player.name}, you receive {deck.deal(player)}")
print(player)
while dealer.score() <= 16:
print(f"{dealer.name} receives {deck.deal(dealer)}")
print(dealer)
# Determine Winner
if player.score() > 21 or player.score() <= dealer.score() <= 21:
print(f"{dealer.name} won this round")
else:
print(f"{player.name}, you won this round!")
if __name__ == '__main__':
main()
I'm trying to pass a class object as an argument to a global function.
Here's the function:
def CreatePlayers(p1_Name, p2_Name, cardDeck):
#Function to Create Players
#Takes 3 variables: Names of player 1 and player 2 and the card deck
#Returns a List of players [p1,p2]
print("Creating Players... \n")
print(f"Dealing a deck of ", len(cardDeck), " among 2 players")
player1 = Player(p1_Name)
player2 = Player(p2_Name)
#Share cards between players
for i in range(25):
player1.addCard(cardDeck.dealOne())
player2.addCard(cardDeck.dealOne())
print("Verify... player creation\n")
print(player1)
print(player2)
return [player1, player2]
The class object here's "cardDeck", and the class object is initialized before making the function call with the variable name, of course
And here's the class definition:
class Deck:
'''A Python Deck class. Holds a list of Card objects
Possesses the following
* Attributes: - myDeck (A list of cards. Expected = 50 cards in deck)
* Methods: - Constructor (creates list)
- Shuffle
- Deal a card
- return number of cards
'''
def __init__(self):
'''Method to initialize a deck'''
self.myDeck = []
#initialize cards
for rank in Ranks:
for order in Orders:
self.myDeck.append( Card(order, rank) )
##other functions....
def __len__(self):
'''Return the size of the card deck'''
return len(self.myDeck)
This is where I call my createPlayer() function:
myDeck = Deck().shuffle()
#Create my players
players = CreatePlayers("Adam", "Bob", myDeck)
And finally here's the error that I keep getting while running the 'createPlayer' function
File "/home/CardGame.py", line 32, in CreatePlayers
print(f"Dealing a deck of ", len(cardDeck), " among 2 players")
TypeError: object of type 'NoneType' has no len()
Deck().shuffle() doesn't return the deck
you can do this to solve it :
myDeck = Deck();
myDeck.shuffle();
players = CreatePlayers("Adam", "Bob", myDeck)
an other alternative is to change shuffle to be a class method :
class Deck:
def __init__(self):
...
#classmethod
def shuffle(cls):
new_deck = cls()
# Do shuffle here for new_deck
return new_deck
and use it that way :
myDeck = Deck.shuffle();
players = CreatePlayers("Adam", "Bob", myDeck)
but that way you can't shuffle an existing deck so it depend on what you want to do.
I am trying to add card c to deck d but I can't print the deck instead I keep getting <__main__.card at >
class Card():
def __init__(self,theName,theMoves):
self.name = theName
self.moves = list(theMoves)
class Deck():
theCards = []
def __init__(self):
pass
def addCard(self,theCard):
Deck.theCards.append(theCard)
print(Deck.theCards)
#adding card to my deck
c = Card("sh",["fire",258])
d = Deck()
d.addCard(c)
You're not referring to the specific class instance in your class functions. Change your Deck class definition as follows:
class Deck():
def __init__(self):
self.theCards = []
def addCard(self, theCard):
self.theCards.append(theCard)
print(self.theCards)
You should iterate over your cards and call vars() on them, because printing a list of objects just shows you only certain memory addresses but no properties. Instead of:
print(Deck.theCards)
use a loop similar to this one:
for card in Deck.theCards:
print(vars(card))
which prints all variables and their corresponding values of an object. I also assume you want to have different decks of different players, but your code currently saves the list of cards within the class definition. Thusly all objects of type Deck you create with the Deck() call are sharing the same list of cards. Lets say you add another deck:
#adding card to my deck
print("player 1")
c = Card("sh",["fire",258])
d = Deck()
d.addCard(c)
print("player 2")
c2 = Card("it",["water",258])
d2 = Deck()
d2.addCard(c2)
The deck of player 2 also includes the cards of player 1 and vice versa. The printed output of the suggested code extension:
player 1
{'name': 'sh', 'moves': ['fire', 258]}
player 2
{'name': 'sh', 'moves': ['fire', 258]}
{'name': 'it', 'moves': ['water', 258]}
The user 'insert random' already offered a solution before. To get two different decks with two different lists of cards you have to store the card list(s) within the class instance, not the class definition. Here the whole updated code:
class Card():
def __init__(self,theName,theMoves):
self.name = theName
self.moves = list(theMoves)
class Deck():
def __init__(self):
self.theCards = []
def addCard(self,theCard):
self.theCards.append(theCard)
for card in self.theCards:
print(vars(card))
#adding card to my deck
print("player 1")
c = Card("sh",["fire",258])
d = Deck()
d.addCard(c)
print("player 2")
c2 = Card("it",["water",258])
d2 = Deck()
d2.addCard(c2)
Note that self is used instead of Deck to store a different card list for every new deck created with the Deck() call. This list is created by the initial class constructor function not by the class definition itself.
Console output:
player 1
{'name': 'sh', 'moves': ['fire', 258]}
player 2
{'name': 'it', 'moves': ['water', 258]}
Which is I guess what you want.
Version with dictionaries:
#adding card to my deck
print("player 1")
c = {'name': 'sh', 'moves':["fire",258] }
d = { 'theCards': [c] }
print(list(card for card in d['theCards']))
print("player 2")
c2 = {'name': 'it', 'moves':["water",258] }
d2 = { 'theCards': [c2] }
print(list(card for card in d2['theCards']))
By default, when Python prints a list it prints the repr of the list's elements. For many objects, including custom classes, the default repr looks like
<__main__.MyClass object at 0x7f00ff75fd00>
You can override this behaviour by defining a __repr__ method on your classes. Conventionally, the output of __repr__ should be a string that would produce your object if it were passed to eval.
For the card class, it would look like this:
class Card:
def __init__(self, theName, theMoves):
self.name = theName
self.moves = list(theMoves)
def __repr__(self):
return "Card('{}', {})".format(self.name, self.moves)
Giving this output to your script (once you have fixed the indentation):
[Card('sh', ['fire', 258])]
You can also define a __str__ method to customise how your object appears when str(myobject) is called. If __str__ is not defined Python will use __repr__ instead.
You seem to have an indentation error. Your declaration of c, d and d.addCard(c) should be spaced indented back once. The same needs to go for everything inside your Card and Deck class.
class Card():
def __init__(self,theName,theMoves):
self.name = theName
self.moves = list(theMoves)
class Deck():
theCards = []
def __init__(self):
pass
def addCard(self,theCard):
Deck.theCards.append(theCard)
print(Deck.theCards)
#adding card to my deck
c = Card("sh",["fire",258])
d = Deck()
d.addCard(c)
I am attempting to construct classes to play out a game of MTG (A card game). I have three relevant classes: MTGGame(...), MTGCard(...), and AbilityList(). An object of MTGGame has several attributes about the player (turn, mana,..., deck).
A player must have a deck of cards to play, so I create a list of MTGCard objects for each player that is a deck, and create an MTGGame object for each from the respective decks. The cards have abilities, and when creating the cards I store abilities as functions/params into each MTGCard. However, I need the abilities to inherit and access methods/attributes from MTGGame and update them, but if I use super().__init__, then I will need to call my deck as a parameter for AbilityList when making MTGCards, which I wouldn't have yet.
Can this be achieved? If not, any suggestions improving my OOP logic to achieve this task?
I am aware that I can do something like this:
class MTGGame():
def __init__(self, deck, turn = 0, mana = 0, lifeTotal = 20, cavalcadeCount = 0, hand = [], board = []):
self.turn = turn
self.mana = mana
self.lifeTotal = lifeTotal
...
def gainLife(self, lifeGained):
self.lifeTotal = self.lifeTotal +lifeGained
def combatPhase(self):
for card in self.board:
card.attackingAbility()
class MTGCard():
def __init__(self, name, CMC, cardType, power, toughness, castedAbility, attackingAbility, activatedAbility, canAttack = False):
....
self.attackingAbility = attackingAbility
Class abilityList():
def healersHawkAbility(self, lifeAmt):
MTGGame.gainLife(lifeAmt)
But this would affect all instances of MTGGame, not the specific MTGGame object this would've been called from. I'd like it to simply update the specific object in question. I'd like to do something like this but I don't know how abilityList methods could access MTGGame attributes/methods ('AbilityList' object has no attribute 'gainLife'):
Class abilityList():
def healersHawkAbility(self, lifeAmt):
#How do I access methods/attributes in MTGGame from here? self?
self.gainLife(lifeAmt)
aL = abilityList()
#One example card:
card1 = MTGCard("Healers Hawk",1,'Creature',1,1, aL.nullAbility(), aL.healersHawkAbility, aL.nullAbility())
whiteDeck = [list of constructed MTGCard() objects, card1, card2,...,cardLast]
player1 = MTGGame(whiteDeck)
...
#Call the ability in a method contained in MTGGame:
player1.combatPhase()
#Would call something like this inside
card.attackingAbility()
#Which may refer to card.healersHawkAbility() since we stored healersHawkAbility() as an attribute for that MTGCard,
#and would declare gainLife(), which refers to self.lifeTotal or player1.lifeTotal in this case.
This is an excellent start and clearly you have already thought a lot of this through. However, you haven't thought through the relationship between the classes.
First thing to note:
MTGGame.gainLife(lifeAmt) is a method call accessed via the class rather than an instance. This means that the self paramter is not actually filled in i.e. you will get an error becuase your method expects 2 arguments but only receive one.
What you perhaps meant to do is the following:
class MTGGame:
lifeTotal = 20 # Notice this is declared as a class variable
def __init__(self, ...):
...
#classmethod
def healersHawkAbility(cls, lifeGained):
cls.lifeTotal = cls.lifeTotal + lifeGained
However, this requires class variables which here defeats the point of having an instance.
Your naming throughout the program should suggest that your classes are a little off.
For instance player1 = MTGGame(). Is player a game? No, of course not. So actually you might want to rename your class MTGGame to Player to make it clear it refers to the player, not the game. A seperate class called MTGGame will probably need to be created to manage the interactions between the players e.g. whose turn it is, the stack holding the cards whilst resolving.
The main focus of your question: how to deal with the cards accessing the game/player object.
Cards should be able to access instances of the player and game classes, and if the player has a is_playing attribute, the card should not have this. The rule of thumb for inheritance is 'is a'. Since card 'is not a' player, it should not inherit from it or MTGGame. Instead, card should be like this for example:
game = RevisedMTGGame()
player1 = Player()
player2 = Player()
class Card:
def __init__(self, name, text, cost):
self.name = name
self.text = text
self.cost = cost
self.owner = None
self.game = None
class Creature(Card):
def __init__(self, name, text, cost, power, toughness):
super().__init__(self, name, text, cost)
self.power = power
self.toughness = toughness
def lifelink(self):
self.owner.heal(self.power) # NOTE: this is NOT how I would implement lifelink, it is an example of how to access the owner
healersHawk = Creature("Healer's Hawk", "Flying, Lifelink", 1, 1, 1)
healersHawk.game = game
healersHawk.owner = player1
You can see from this incomplete example how you can set up your cards easily, even with complex mechanics, and as the base classes have been defined you can avoid repitition of code. You might want to look into the event model in order to implement the lifelink mechanic, as an example. I wish you luck in continuing your game!
I have this object Troll.
I made a random selection for his 'weapon'.
Every time he appears his weapon will change.
Under his object status section, I have a variable 'base_att' I want it to be the damage variable from his 'weapon'
How can I have the base_att variable get updated by the 'weapon' variable random.choice
note that I listed all my 'Weapons' in a different .PY file hence the items.weapon_for_troll calling.
class stick:
name = 'Wooden Stick'
damage = range (0,3)
effect = None
class rocks:
name = 'Rocks'
damage = range (0,5)
effect = None
class troll:
name = 'Troll'
class equipment:
weapon = random.choice(items.weapon_for_troll)
class inventory:
loot = None
class status:
level = 1
exp_point = 30
hp_point = 30
base_att =
base_def = 2
bonus_def = 0
You need to distinguis between ("static") class variables and ("non static") instance variables.
You are declaring all things static - thats great for things that are shared between all Trolls (like a "type" of monster - aka Troll) - not so great for things all "have" but are not "the same" - say like an inventory.
Try this approach:
import random
class Weapon: # no need to make each weapon its own class
def __init__(self,name,damage,effect):
self.name = name
self.damage = damage
self.effect = effect
# create some troll weapons to choose from
troll_weapons = [ Weapon("Wooden Stick", range(3), None), Weapon("Rocks",range(5), None) ]
class Equipment:
def __init__(self):
# every time you instantiate a new equipment, generate a random weapon
# for this instance you just created. They are not static, they differ
# between each Troll (who instantiates its own Equipment)
self.weapon = random.choice(troll_weapons)
class Troll:
name = 'Troll'
def __init__(self):
self.equipment = Equipment() # get a new instance of Eq for this troll
# instance
self.weapon = self.equipment.weapon # "link" the troll weapon to its equipment
# as shortcut - do smth similar to an
# instance of your "Stat" block
def __str__(self):
return f"Troll with: {self.weapon.name}"
# create a troll army
trolls = [Troll() for _ in range(10)]
for t in trolls:
print(t)
Output:
Troll with: Rocks
Troll with: Wooden Stick
Troll with: Rocks
Troll with: Rocks
Troll with: Wooden Stick
Troll with: Rocks
Troll with: Wooden Stick
Troll with: Wooden Stick
Troll with: Wooden Stick
Troll with: Wooden Stick
Reads to check:
Classes
Class variables vs Instance variables
Sidenotes:
there are convention about naming schemes, classes start with a Capital, members lowercase