How to debug OOP Class/Method in Python - python

I'm attempting the canonical pedagogical programming exercise of building a program to simulate a card game. This is how far I am:
class Card:
def __init__(self, suit, rank):
self.suit = suit
self.rank = rank
self.card = str(self.suit) + " " + str(self.rank)
class Deck:
def __init__(self):
self.deck = []
def Deck_Maker(self):
ranks = range(1, 11) + ["J", "Q", "K", "A"]
suits = ["Club", "Heart", "Spade", "Diamond"]
return self.deck.append(Card(suits[0], ranks[0]))
Obviously I'm not done, I have to loop over suits and ranks to construct the deck. However, before continuing I want to check that I'm on track. To do this I want to print the results after invoking the Deck_Maker method. I have attempted to do this with the below code...
def __str__(self):
d = self.deck
d2 = d.Deck_Maker()
return str(d2)
Unfortunately, print(Deck().Deck_Maker()) returns None.
What am I doing wrong? More generally, how do programmers poke around within Classes and Methods while they make them?
Thanks!

The append method on a list returns None so this is expected. Try splitting up your lines:
deck = Deck()
deck.Deck_Maker()
print(deck)
or change the method Deck_Maker to be:
def Deck_Maker(self):
ranks = range(1, 11) + ["J", "Q", "K", "A"]
suits = ["Club", "Heart", "Spade", "Diamond"]
self.deck.append(Card(suits[0], ranks[0]))
return self
Edit: There is also a problem in your str method. When you set d to self.deck you are setting it to be a list. The list doesn't know anything about the method Deck_Maker. Try changing to the method below. This will turn your list into a string. To make it more readable, you will probably also want to add a str method to the card class.
def __str__(self):
return str(self.deck)

I know it doesn't sound super smart, but a good debuger, with breakpoints, helps. You can actually see the flow of execution.
The bumpy issues are mro (method resolution order along the inheritance chain), class methods (does the result comes from instance/class), and metaclasses (class that creates class with type). To understand these issues, initialisation and what object provided the result, drop a logging or a print statement in every init , and on methods, that says "enters method foo from object baz". This will make things clearer.

A few suggestions:
One change to Card I'd suggest: instead of having an attribute card, initialized in __init__, remove that last line from __init__ and add a __str__ method:
class Card():
def __init__(self, suit, rank):
self.suit = suit
self.rank = rank
def __str__(self):
return str(self.suit) + " " + str(self.rank)
Now every Card knows how to represent itself as a str on demand. You might add a __repr__ method, instead of or in addition to __str__:
def __repr__(self):
return 'Card(%s, %s)' % (self.suit, self.rank)
The Deck class
You can also add a __str__ method to the Deck class.
I've renamed the Deck_Maker method to make_Deck, and I've made it a classmethod, because the name suggests that you mean it to be a "factory method", callable on the Deck class rather than on Deck instances. The method now does need to return the Deck it creates.
I also renamed the deck member variable to cards, since after all it is a list of Cards, and we will want to use deck to refer to Decks :)
Finally, I made the lists of suits and ranks class attributes, rather than locals of make_Deck, on the theory that eventually some other method will want to use them. In Python 3, range returns an iterable, hence list() around it.
A couple of issues: I'm not sure what cards of rank 1 are, given that you have "A" for aces; 2 through 11 seems more likely. (Thus, Decks presently have 56 cards :) Also, it will prove awkward to have both ints and strs as ranks: surely you'll want to compare ranks, and this representation will fight you. (Solution: store ranks as int only, and convert in __str__ and __repr__ methods.) You may want rank comparison to be a Card method. In that case, _ranks and _suits should be accessible to that class too (module-globals, for example).
But these rank and suit problems are for another day, and I'll leave the definitions basically unchanged.
class Deck():
_ranks = list(range(1, 11)) + ["J", "Q", "K", "A"]
_suits = ["Club", "Heart", "Spade", "Diamond"]
def __init__(self):
self.cards = []
#classmethod
def make_Deck(cls):
"""cls is Deck"""
deck = cls() # make a Deck
for s in cls._suits:
for r in cls._ranks:
deck.cards.append(Card(s, r))
return deck
# A basic example:
def __str__(self):
return ', '.join([str(card) for card in self.cards])
Now you can use code like the following:
>>> deck = Deck.make_Deck()
>>> print(deck) # calls deck.__str__
which prints the str representations of all 56 (!) cards in deck, comma-separated and all on one line:
Club 1, Club 2, Club 3, Club 4, Club 5, Club 6, Club 7, Club 8, Club 9, Club 10, Club J, Club Q, Club K, Club A, Heart 1, Heart 2, Heart 3, Heart 4, Heart 5, Heart 6, Heart 7, Heart 8, Heart 9, Heart 10, Heart J, Heart Q, Heart K, Heart A, Spade 1, Spade 2, Spade 3, Spade 4, Spade 5, Spade 6, Spade 7, Spade 8, Spade 9, Spade 10, Spade J, Spade Q, Spade K, Spade A, Diamond 1, Diamond 2, Diamond 3, Diamond 4, Diamond 5, Diamond 6, Diamond 7, Diamond 8, Diamond 9, Diamond 10, Diamond J, Diamond Q, Diamond K, Diamond A

Related

Python homework not working - receving an error

I am trying to write this code but I Receve the following error and I am not sure exactly why it is not printing out.
The assigment is to make sure that whenever we pull out for example Ace of Spades, Ace of Hearts, Ace of Diamonds and Ace of Clubs they are not to be in the [my_cards] section.
I tried the following code:
import random
standard_cards = [2, 3, 4, 5, 6, 7, 8, 9, 10, "J", "Q", "K", "A"]
my_cards = {"Hearts": standard_cards, "Diamonds": standard_cards, "Clubs": standard_cards, "Spades": standard_cards}
new_cards = list(my_cards)
picked_cards = []
for card in range(3):
random.shuffle(my_cards)
chosen_card_color = random.choice(my_cards)
chosen_card = random.choice(chosen_card_color)
my_cards.remove(chosen_card)
picked_cards.append(chosen_card_color + " " + chosen_card)
print("User took these 3 cards:\r\n", picked_cards)
Here is the error I receive:
"C:\Python lectures\TestovProekt\venv\Scripts\python.exe" "C:/Python lectures/TestovProekt/Domashno.py"
Traceback (most recent call last):
File "C:\Python lectures\TestovProekt\Domashno.py", line 19, in <module>
random.shuffle(my_cards)
File "C:\Users\BOP\AppData\Local\Programs\Python\Python39\lib\random.py", line 362, in shuffle
x[i], x[j] = x[j], x[i]
KeyError: 0
Process finished with exit code 1
Any help would be really appreciated!
As noted in The_spider's answer, there are a number of issues with your original code. Their answer addresses those issues, so I will propose a different solution using a list of card tuples:
import random
import itertools
ranks = [2, 3, 4, 5, 6, 7, 8, 9, 10, "J", "Q", "K", "A"]
suits = ["Spades", "Hearts", "Clubs", "Diamonds"]
cards = list(itertools.product(ranks, suits))
deck = cards.copy()
random.shuffle(deck)
user_hand = [deck.pop() for _ in range(3)]
Output:
[(3, 'Diamonds'), ('J', 'Spades'), (10, 'Clubs')]
From here, it'd be pretty straight-forward to use a dictionary to store "players", and deal new hands to all the players in the correct order (instead of 3 cards at once for each player which is not how poker hands are dealt).
mycards is a dictionary, which you can't shuffle. As you want to choose a card color here, I think you intended to chose from new_cards, which does contain the 4 card colors. This also requires that you've to get the card itself with a dictionary key. Also, first shuffling and then choosing random appears me a bit useless, you can't concatenate strings and int and, as Random Davis said, you should copy your standard_list. Otherwise, the cards will be removed from every color, and not only from the one you just chosen, meaning that you'll never get 2 the same card of a different type.
The entire code should look something like this:
import random
standard_cards = [2, 3, 4, 5, 6, 7, 8, 9, 10, "J", "Q", "K", "A"]
my_cards = {"Hearts": standard_cards.copy(), "Diamonds": standard_cards.copy(), "Clubs": standard_cards.copy(), "Spades": standard_cards}
#you can leave one of them uncopied, as it won't affect the other ones anymore now.
new_cards = list(my_cards)
picked_cards = []
for card in range(3):
chosen_card_color = random.choice(new_cards)
chosen_card = random.choice(my_cards[chosen_card_color])
my_cards[chosen_card_color].remove(chosen_card)
picked_cards.append(chosen_card_color + " " + str(chosen_card))
print("User took these 3 cards:\r\n", picked_cards)

How to get a function to run twice dynamically

This is a function.
def cut_dk():
a = []; b = []
random.shuffle(deck__)
slice = deck__[:2]
a.append(slice[0])
b.append(slice[1])
return a,b
c = cut_dk()
p1 = c[0]
p2 = c[1]
I have this function at the top of the program along with other functions.
It draws from a predefined list.
When calling this function dynamically in the program it returns the same variables.
It's the cutting of a card deck (two cards, highest wins the draw), when the cards are equal it needs to draw again (this is the problem, a second draw), a new selection from the list, yet it just repeats the variables it has in memory.
Calling the function again in a conditional statement just returns the same initial variables acquired on the first run, so I am unable to repeat the cut dynamically as part of the game play.
I would manage my deck as an object here with a class. This would allow us to define a deck, which is stored as an object and perform multiple functions against the deck whilst retaining the state changes from different functions.
class deck:
"""
Class designed to manage deck including iterations of changes.
Shuffling the deck will retain shuffle changes
"""
def __init__(self):
self.deck = [1, 2, 3, 4, 5, 6, 7, 8, 9, 'J', 'Q', 'K', 'A']
self.original = self.deck[:]
def shuffle(self):
"""
Shuffle deck in-place. self.deck will be modified
"""
random.shuffle(self.deck)
def cut(self):
"""
Shuffles deck and draws the two top-most cards
Returns: tuple(2) two cards from top of the deck
"""
self.shuffle()
a, b = self.deck[:2]
return a, b
def highest_draw(self):
"""
Calls self.cut() to shuffle and retrieve 2x top-most cards.
If cards match the deck is shuffled and cut again.
Returns: 2 Unique cards from top of the deck
"""
a, b = self.cut()
while a == b:
a, b = self.cut()
return a, b
def reset(self):
self.deck = self.original[:]
game = deck()
game.deck
#[1, 2, 3, 4, 5, 6, 7, 8, 9, 'J', 'Q', 'K', 'A']
game.shuffle()
game.deck
#['A', 7, 5, 9, 8, 'J', 'K', 6, 4, 3, 1, 'Q', 2]
game.reset()
game.deck
#[1, 2, 3, 4, 5, 6, 7, 8, 9, 'J', 'Q', 'K', 'A']
game.cut()
#('A', 'Q')
game.highest_draw()
#('J', 2)
You would still need to define how you determine the "highest" card, however this is dependant on your deck, which you have left out of the question.
It sounds as if a generator function would be useful here. You call the function once to set up your iterator and then 'draw' cards from that iterator (in this case the iterator is 'cards'). Notice you have to catch the case where you run the whole deck and it's tied throughout. I've sprinkled print statements through this to make it easier to understand how generators work.
import random
deck__ = list(range(3))*2
def draw_from_shuffled():
random.shuffle(deck__)
print(f'Deck after shuffling: {deck__}')
for c in deck__:
yield c
cards = draw_from_shuffled() #cards is now an iterator
while True:
try:
a = next(cards)
b = next(cards)
except StopIteration:
print(f'End of deck!')
cards = draw_from_shuffled()
continue
print(f'Drew {a} and {b}')
if a != b:
break
print('Hand done.')
Sample output:
Deck after shuffling: [2, 2, 1, 1, 0, 0]
Drew 2 and 2
Drew 1 and 1
Drew 0 and 0
End of deck!
Deck after shuffling: [0, 0, 2, 2, 1, 1]
Drew 0 and 0
Drew 2 and 2
Drew 1 and 1
End of deck!
Deck after shuffling: [0, 2, 1, 0, 1, 2]
Drew 0 and 2
Hand done.
More on generators: https://realpython.com/introduction-to-python-generators/

How do you print values in lists with their index number in Python 2.7?

I am trying to make a Blackjack game in python. I ran into a problem because I am trying to use the random module with my game. I used the random module to get a number that coordinates with the index number in my list. The list I made consisted of card face values. I don't know how to print these values using the random index number, though. Here is my code:
# this is a blackjack game made in python
import random
import time
# make a list full of the card values
cards = (["A", "K", "Q", "J", 2, 3, 4, 5, 6, 7, 8, 9, 10])
indexNum1 = random.randint(0,12)
indexNum2 = random.randint(0,12)
indexNum3 = random.randint(0,12)
indexNum4 = random.randint(0,12)
indexNum5 = random.randint(0,12)
for card in cards:
print card(indexNum1)
print card(indexNum2)
print card(indexNum3)
print card(indexNum4)
print card(indexNum5)
I hope someone can help me solve this problem. Thanks!
You can index cards directly, e.g.:
print(cards[indexNum1])
But if you want in a loop you should iterate over the indexes:
for cardidx in (indexNum1, indexNum2, indexNum3, indexNum4, indexNum5):
print(cards[cardidx])
But you are making this much harder than you need to, because currently your code could return 5 Aces - which I assume you don't want:
cards = ["A", "K", "Q", "J", 2, 3, 4, 5, 6, 7, 8, 9, 10]
hand = random.sample(cards, k=5)
for card in hand:
print(card)
If you want to properly simulate a deck of cards, you'll have to do this:
import random
def clean_deck():
return list("AJQK234567890") * 4
def draw_cards(deck, n):
return [deck.pop() for _ in range(n)]
deck = clean_deck()
random.shuffle(deck)
for card in draw_cards(deck, 5):
print(card)
This keeps track of the deck of cards, literally shuffles them, which is conveniently a random function, and then "draws" from the deck (pops). You have to keep track of the deck so you could, for example, draw 4 aces but then there wouldn't be any more left. This approach is also persistent across card-drawings - when you draw cards from the deck, they are actually removed, so you also can't draw more than 4 aces in an entire game. It will crash if you try to draw from an empty deck, although you could put an if statement in draw_cards to refill the deck if needed, but you should be aware that this would happen if the deck ran out - it could lead to weird things like 5 aces in a game.
I've changed all your digits to strings, as there's no use for them as integers if some cards aren't integers. (An alternative is to keep them all as integers, which you can do with the range function - at a guess range(13), or if you want to go from 2, range(2, 15)). I've also changed 10 to 0 as 0 was unused and that made it a lot more concise. It should be easy enough to change back should you wish.
The difference between this and the other approaches is that the other approaches never remove cards (pop). They just randomly pick cards and then put them back, although they've been drawn.
If you want to randomly choose k cards without repetition and keep the indices, this may helps:
import random
cards = (["A", "K", "Q", "J", 2, 3, 4, 5, 6, 7, 8, 9, 10])
indices = random.sample(range(len(cards)), k=5)
print 'Indices are:', indices, '\nCards chosen are:',[cards[index] for index in indices]

Python List, taking one item from one to another

(newbie) I have looked for answers, however, other examples are not sufficient for what I am looking for. As the title states, I am simply trying to take one item, or in this case card , from one list to another. The two lists are called 'deck' and 'hand.'
Once you pull from one list, it goes into the other, and gets deleted from its original.
Edit made more specific:
deck = [cat, cat, cat, cat, cat, cat, cat, dog, dog, dog, dog, dog, dog, bird, bird, shark, shark, shark]
hand = []
new_deck = []
def startUpCards():
if len(deck) >= 7:
hand = random.sample(deck, 7)
new_deck = [item for item in deck if item not in hand]
deck = list(new_deck)
elif len(deck) < 7:
hand = random.sample(deck, len(deck))
new_deck = [item for item in deck if item not in hand]
deck = list(new_deck)
So above is what you start out with, and everything comes out correct as intended. However, this is where my problem comes in, although no error occurs:
def addNewCard():
if len(deck) > 0:
hand.extend(random.sample(deck, 1))
new_deck = [item for item in deck if item not in hand]
deck = list(new_deck)
else:
sleep(2)
print ("You don't have any more cards in your deck!")
startUpCards()
cardChosen = input("Which card do you want to draw?")
def rmv_hand():
hand.remove(cardChosen)
addNewCard()
`print(hand)`
The issue that I am finding is that after the first draw, hand is shortened by 1, possibly meaning it didn't draw from deck, right?
I also see that my print string ("You don't have any more cards in your deck!") is printing waaay before I expect it to! What's going on?
You're going through far too much work. Use randrange to select a card by position. Use pop to remove that element from the deck, and immediately append it to the receiving hand. Here is a simple example:
import random
deck = [1, 2, 3, 4, 5]
hand = [11, 12, 13, 14, 15, 16, 17]
# Choose a random card from the deck *by position*
draw_pos = random.randrange(len(deck))
print "Pulling card #", draw_pos, "from deck to hand"
hand.append(deck.pop(draw_pos))
print deck
print hand
Sample output:
Pulling card # 2 from deck to hand
[1, 2, 4, 5]
[11, 12, 13, 14, 15, 16, 17, 3]
Does that get you going?

Moving items around in a python list?

This is the final product. IF anyone else has any tips to cut it up, please let me know! Thanks a lot for the help!
def triple_cut(deck):
''' (list of int) -> NoneType
Modify deck by finding the first joker and putting all the cards above it
to the bottom of deck, and all the cards below the second joker to the top
of deck.
>>> deck = [2, 7, 3, 27, 11, 23, 28, 1, 6, 9, 13, 4]
>>> triple_cut(deck)
>>> deck
[1, 6, 9, 13, 4, 27, 11, 23, 28, 2, 7, 3]
'''
joker1 = deck.index(JOKER1)
joker2 = deck.index(JOKER2)
first = min(joker1, joker2)
first_cards = []
for cards in range(len(deck[:first])):
cards = 0
pop = deck.pop(cards)
first_cards.append(pop)
joker1 = deck.index(JOKER1)
joker2 = deck.index(JOKER2)
second = max(joker1, joker2)
second_cards = []
for cards in deck[second + 1:]:
pop = deck.pop(deck.index(cards))
second_cards.append(pop)
second_cards.reverse()
for card in second_cards:
deck.insert(0, card)
deck.extend(first_cards)
raah I need to type more because my post is mostly code: please add more details sss ss
A hint:
p = list('abcdefghijkl')
pivot = p.index('g')
q = p[pivot:] + p[:pivot]
List slicing is your friend.
answer to the second revision
when the function is finished, it won't mutate the deck.
The problem is that you didn't give the full code. I'm guessing it looks like this:
def triple_cut(deck)
…
deck = q
And according to your docstring you call it as
deck = […]
triple_cut(deck)
and have gotten confused that the assignment deck = q doesn't propagate out of triple_cut(). You can't modify the formal parameters of a method so the assignment remains local to triple_cut and does not affect the module level variable deck
The proper way to write this is
def triple_cut(cuttable)
…
return cuttable[first:] + cuttable[:first]
deck = […]
deck = triple_cut(deck)
where I changed the name of the argument to cuttable to for purposes of explanation. You could keep the argument name as deck but I wanted to show that when you thought you were assigning to deck you were really assigning to cuttable and that assignment wouldn't carry out of triple_cut().

Categories