Python: How to compare elements within an array of cards - python

I am working on a texas hold-em game in python, and am looking to traverse an array containing a complete hand of 7 cards (2 in the hole + 5 on the board). The array contains elements of class Cards, where the Card class constructor is
class Card:
def __init__(self, suit, val):
self.suit = suit
self.value = val
So, I have a "hand" array within a "Player" class of 7 random cards, where the suit is one of 4 strings (spade, club, heart, diamond) and the value is one of 9 numbers (2-10) or 4 strings (jack-ace). I want to traverse the array to check if the list contains any of the hands in poker, but I'm failing to figure out how I can "extract" the suit/value of the cards from my array. I've started a method within my "Player" class to check for a suit, here that suit is a spade.
class Player:
def __init__(self, name):
self.name = name
self.pocket = []
self.hand = []
def spadeChecker(self):
card = Card("Blank", 0)
for i in self.hand:
card = self.hand[i]
if(card.suit == "Spade"):
print("Hi! you have a spade!")
else:
pass
When running the program from my terminal I receive a TypeError message:
in spadeChecker card = self.hand[i] TypeError: list indices must be integers or slices, not Card
I know my method is pretty bad but I'm very new to this and just can't figure out how to get it to work. Any advice?
Thanks

Here is the rewritten method, including an example with a 3 card hand.
class Card:
def __init__(self, suit, val):
self.suit = suit
self.value = val
class Player:
def __init__(self, name):
self.name = name
self.pocket = []
self.hand = [Card("Heart", 10), Card("Spade", 10), Card("Diamond", 10)]
def spadeChecker(self):
for card in self.hand:
if(card.suit == "Spade"):
print("Hi! you have a spade!")
#return True
else:
#return False
pass
p = Player("Bob")
p.spadeChecker()
Output is:
Hi! you have a spade!

In your code, your iterator is i and your iterable is self.hand, which is a list of Card objects. Thus, i will be a Card object each time the loop iterates. If you want to be able to get the index of the Card object as well as the Card object itself, I recommend using the enumerate() function. However, since you only reference the Card objects, you can just get the suit attribute directly.
class Player:
def __init__(self, name):
self.name = name
self.pocket = []
self.hand = []
def spadeChecker(self):
card = Card("Blank", 0)
for card_obj in self.hand:
if(card_obj.suit == "Spade"):
print("Hi! you have a spade!")
else:
pass

There's a Python library for generating, comparing & evaluating poker hands called tilted which can be found here: https://github.com/MaxAtkinson/tilted
I've used it myself before and it's really easy. This may allow you to avoid having to implement it yourself.

Related

Python Blackjack Drawing a Card from a Deck Problem

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()

How do I make my (derived?) python instance attributes "self-update" based on alterations to other same-instance attributes?

I know that I have a misunderstanding of how Python attributes work because I'm here writing this problem, but I don't know exactly what I'm misunderstanding. I'm trying to get
self.card = self.hand[self.card_number].split()
self.card_val = deck.ranks.get(self.card[0])
to attain their values based on self.hand, which I pass to __init__ upon instantiation. Throughout the game I am altering .hand, and I want .card & .card_val to change every time I change .hand, instead of having to tell it to do that elsewhere (outside of the attribute definitions). I know there is a way to do this, or at least I think there is, and by that I mean simply by defining their values as inherited based on whatever .hand is at any given time, without calling an internal or external function.
In the posted code, I have altered it to work as the game instructions require, using...
def get_card_vals(p1, p2):
for player in [p1, p2]:
player.card = player.hand[player.card_number].split()
player.card_val = deck.ranks.get(player.card[0])
print("{a} vs. {b}".format(a = p1.card, b = p2.card))
print("---------------------------")
...but that's what I want to change. I want what that function is doing to be executed more concisely inside of the attribute definitions upon handling of the instance. Basically my question is why can't these two attributes get their values directly from the first attribute that I define via "hand" passed to the init?
Any help would be appreciated, and more importantly, I think more than just solutions, it would help me even more to understand what I am misunderstanding about how attributes, instances, and instantiation and all that works so that I know where my thinking is wrong. Thanks!
import random
from random import shuffle
from collections import deque
class Deck():
def __init__(self):
self.ranks = {"Ace":14, "King":13, "Queen":12, "Jack":11, "10":10, "9":9, "8":8, "7":7, "6":6, "5":5, "4":4, "3":3, "2":2}
self.suites = ["Heart", "Diamond", "Spade", "Club"]
self.cards = []
def create_cards(self):
for suite in self.suites:
for key in self.ranks.keys():
self.cards.append(key + " " + suite)
def shuffle(self):
random.shuffle(deck.cards)
deck = Deck()
deck.create_cards()
deck.shuffle()
class Player():
def __init__(self, hand):
self.name = "name"
self.hand = hand
self.card_number = 1
self.card = self.hand[self.card_number].split()
self.card_val = deck.ranks.get(self.card[0])
def war(bool, p1, p2):
if bool == True:
for player in [p1, p2]:
player.card_number = 4
else:
for player in [p1, p2]:
player.card_number = 0
p2 = Player(deque(deck.cards[::2]))
p1 = Player(deque(deck.cards[1::2]))
p2.name = "The Computer"
def get_card_vals(p1, p2):
for player in [p1, p2]:
player.card = player.hand[player.card_number].split()
player.card_val = deck.ranks.get(player.card[0])
print("{a} vs. {b}".format(a = p1.card, b = p2.card))
print("---------------------------")
def cant_war_lose(winner, loser):
print("{a} doesn't have enough cards to go to war, so {b} wins the Battle!".format(a = loser, b = winner))
def battle_win(winner, loser):
print("{a} has run out of cards, therefore {b} has won via Battle!".format(a = loser, b = winner))
def play_cards(p1, p2):
war(False, p1, p2)
get_card_vals(p1, p2)
if p1.card_val > p2.card_val:
p1.hand.append(p2.hand.popleft())
p1.hand.rotate(-1)
elif p1.card_val == p2.card_val:
if len(p1.hand) < 5 or len(p2.hand) < 5:
if len(p1.hand) > len(p2.hand):
cant_war_lose(p1.name, p2.name)
else:
cant_war_lose(p2.name, p1.name)
return 0
else:
input("War is inititated! Press Enter to continue!")
print("---------------------------")
war(True, p1, p2)
get_card_vals(p1, p2)
if p1.card_val > p2.card_val:
for i in range(0,5):
p1.hand.append(p2.hand.popleft())
p1.hand.rotate(-5)
elif p1.card_val < p2.card_val:
for i in range(0,5):
p2.hand.append(p1.hand.popleft())
p2.hand.rotate(-5)
else:
p1.hand.rotate(-1)
p2.hand.rotate(-1)
elif p1.card_val < p2.card_val:
p2.hand.append(p1.hand.popleft())
p2.hand.rotate(-1)
if len(p1.hand) != 0 and len(p2.hand) != 0:
input("After the last round of Battle, {a} now has {b} cards, and {c} now has {d} cards! Press Enter to continue!".format(a = p1.name, b = len(p1.hand), c = p2.name, d = len(p2.hand)))
print("---------------------------")
else:
if len(p1.hand) > len(p2.hand):
battle_win(p1.name, p2.name)
else:
battle_win(p2.name, p1.name)
return 0
def game_run():
run = 1
p1.name = input("Player 1's name? ")
print("---------------------------")
while run == 1:
if play_cards(p1, p2) == 0:
run = 0
game_run()
You can use the property decorator to create a calculated property
class Player():
def __init__(self, hand):
self.name = "name"
self.hand = hand
self.card_number = 1
#property
def hand(self):
return self._hand
#hand.setter
def hand(self, value):
self._hand = value
self.card = self._hand[self.card_number].split()
self.card_val = deck.ranks.get(self.card[0])
What you misunderstand is variables, not instances. For instance, the attribute card is a scalar variable attached to the instance. Assigning to it with
self.card = <blah>
does not bind it to blah for constant recomputation. This is a value assignment, not a memory mapping. If you want that long-term binding, you must either write the maintenance routine yourself -- which you've already done, in a way, with the consistent recomputation -- or you must assign a mutable reference to self.card, so that card refers to teh same object as the expression you created.
Given that you are consistently rotating and altering the hand, this is not feasible in your design. Instead, simply write an access routine, perhaps get_next_card(hand), which will rotate the hand, extract the card, and return the desired rank and suit.
If you plan to program more card games, you will also find it handy to define a class card and class hand, with appropriate support routines. Maintain the card as a pair of integers; convert to strings only for printing.
Does that get you moving?
For anyone who wanted to compare a before and after of the problem & final solution, below is the working code for my specific issue. All I had to do was convert self.card and self.card_val to a calculated property. By passing in hand, and subsequently handling only hand, self.card & self.card_val are calculated, since every time I handle the instance of the class (by handling hand), these "method attributes" are being called and altered. Thanks for the input, guys!
class Player():
def __init__(self, hand):
self.name = "name"
self.card_number = 1
self.hand = hand
#property
def card(self):
return self.hand[self.card_number].split()
#property
def card_val(self):
return deck.ranks.get(self.card[0])

Delete a namedtuple object from a class defined list

Below is a class of a deck of cards from 'Fluent Python' by Luciano Romalho.
I hope it's ok that I have copied the code, I really don't have a better more concise class example than this one.
import collections
from random import choice
Card = collections.namedtuple('Card', ['rank', 'suit'])
class FrenchDeck:
ranks = [str(n) for n in range(2, 11)] + list('JQKA')
suits = 'spades diamonds clubs hearts'.split()
def __init__(self):
self._cards = [Card(rank, suit) for suit in self.suits
for rank in self.ranks]
def __len__(self):
return len(self._cards)
def __getitem__(self, position):
return self._cards[position]
so, an instance of this class will have 52 cards, each a namedtuple as defined in 'Card' object.
I wanted to draw a hand of n cards so that it will reflect in the deck.
I tried the following:
def a_hand(deck, size):
the_hand = []
for n in range(size):
c = choice(deck)
the_hand.append(c)
deck = [i for i in deck if i != c]
return the_hand
so when I try:
>> deck = FrenchDeck()
>> a = a_hand(deck, 5)
I get a hand but the deck is untouched:
>> hand
[Card(rank='9', suit='spades'),
Card(rank='A', suit='hearts'),
Card(rank='2', suit='diamonds'),
Card(rank='8', suit='clubs'),
Card(rank='10', suit='hearts')]
>> len(deck)
52
when I try directly in the interperter:
>> c = choice(deck)
>> alt = [i for i in deck if i != c]
it works:
>> len(alt)
51
I understand that this is due to the FrenchDeck's instance not being affected by what is happening in the scope of the function a_hand.
What would be the way to do it? I tried to define a dunder-delitem function in the class but didn't get it right, also wasn't sure if this was the right function to use and whether it was to be defined in the Card object or in the FrenchDeck object.
Really you just need to move a_hand to be a method of FrenchDeck:
class FrenchDeck:
# All previous code here, plus:
def a_hand(self, size):
the_hand = []
for n in range(size):
c = choice(self._cards)
the_hand.append(c)
self._cards.remove(c)
return the_hand
You're right that it's due to the fact that FrenchDeck instance is not modified inside a_hand function. Instead, you only override deck variable. To achieve your goal, you could e.g. add deal_hand method to FrenchDeck class, that will return a hand of given size and remove selected cards from deck itself.
You create a new deck instead of updating the existing deck.
deck = [i for i in deck if i != c]
This creates a new list, built by the list comprehension, and makes deck point to it, instead of pointing at the original list that was passed.
You need is to use deck.remove(...) if you are to alter the existing list.
(Also: try making the deck and hands sets, not lists. It matches the domain better.)
When you get a card from the deck, also remove it from the deck. Use list.pop in __getitem__.
class FrenchDeck:
ranks = [str(n) for n in range(2, 11)] + list('JQKA')
suits = 'spades diamonds clubs hearts'.split()
def __init__(self):
self._cards = [Card(rank, suit) for suit in self.suits
for rank in self.ranks]
def __len__(self):
print(f'__len__ called: {len(self._cards)}')
return len(self._cards)
def __getitem__(self, position):
print(f'getting item {position}')
return self._cards.pop(position)
def a_hand(deck, size):
the_hand = []
for n in range(size):
print('getting another card')
c = choice(deck)
the_hand.append(c)
return the_hand
deck = FrenchDeck()
a = a_hand(deck, 5)

Deck card class in python

I am working on creating a class for the first time, and I am thinking that I have done every thing to get it run, but I still get bunch of issues which is 'list' object has no attribute 'shffule' so the problem here is it will not shuffle the cards, and it will not tell the remaining cards, can any one tell me what am I doing wrong? Thanks in advance
import random
class card_deck:
def __init__(self, rank, suite, card):
self.rank= rank
self.suite = suite
def ranks(self):
return self.rank
def suites(self):
return self.suite
def cards(self,card):
suit_name= ['The suit of Spades','The suit of Hearts', 'The suit of Diamonds','Clubs']
rank_name=['Ace','2','3','4','5','6','7','8','9','10','Jack','Queen','King']
def value(self):
if self.rank == 'Ace':
return 1
elif self.rank == 'Jack':
return 11
elif self.rank == 'Queen':
return 12
elif self.rank == 'King':
return 13
def shffule(self):
random.shuffle(self.cards)
def remove(self,card):
self.cards.remove(card)
def cardremaining(self):
self.suite-self.rank
def main():
try:
deck=[]
for i in ['Spades','Hearts', ' Diamonds','Clubs']:
for c in ['Ace','2','3','4','5','6','7','8','9','10','Jack','Queen','King']:
deck.append((c, i))
deck.shffule
hand = []
user =eval(input('Enter a number of cards: 1-7 '))
print()
while user <1 or user >7:
print ("Only a number between 1-7:")
return main()
for i in range(user):
hand.append(deck[i])
print (hand)
except ValueError:
print("Only numbers")
main()
Apart from your code containing many small errors; I will try to answer your main problems.
If you are going to use shffule[sic] method of card_deck class, then you first need to create an instance of that class(whereas you tried to call that method with a list). Like this:
deck = card_deck(some_rank,some_suit,some_card)
deck.shffule() #Now you can call the method
Now, since you made it a class instance, you cannot get items from it like hand.append(deck[i])
Unless you defined the method __getitem__ in your class definition, like this:
#this will be in your class definition
def __getitem__(self,i):
return self.card_list[i] #Of course you have to define a list of cards in your class too.
In my opinion, you should spend a little more time trying to understand how is a class defined, how does methods work and how to access members of a class. After that you will be doing much better here
You are never actually creating an instance of the card_deck class. The expression
deck=[]
creates a variable named deck referring to an empty list.
In python, [a, b, c,...] is the syntax for creating list literals.
from collections import namedtuple
Card = namedtuple('Card', 'sign, value') # no need to write class to represent card
SIGNS = ['Hearts', 'Diamonds', 'Spades', 'Clubs']
class Deck:
def __init__(self):
self.cards = [Card(sign, value) for sign in SIGNS for value in range(2,
11) +
'J Q K A'.split()]
def __repr__(self):
return str([str(card) for card in self.cards])
def __len__(self):
return len(self.cards)
def __getitem__(self, item):
return self.cards[item]
def __setitem__(self, key, value):
self.cards[key] = value
deck = Deck()
print deck[11] # indexing works, prints Card(sign='Hearts', value='K')
print len(deck) # prints 52
print deck[13:16] # slicing works
import random
random.shuffle(deck) # shuffle works using no extra code

Create a function that consumes a list of cards and produces a list of those cards which are red and odd

I have to write the function red_odd, which consumes a list of cards, hand, and produces a list of those cards which are red (i.e., "diamonds" or "hearts") and have an odd value, in the order they appear in the consumed list. The consumed list cannot be mutated.
For example,
red_odd([card1, card2, card3, card4]) => [card2]
So I have this so far:
class card:
'Fields: suit, value'
def __init__(self, suit, value):
self.suit = suit
self.value = value
card1 = card('spades', 8)
card2 = card('hearts', 5)
card3 = card('diamonds', 6)
card4 = card('clubs', 5)
def red_odd(hand):
card_list = []
for c in hand:
if (c.suit == 'diamonds' or c.suit == 'hearts') and (c.value / 2 != 0):
card_list.append(c)
return card_list
It doesn't run and I'm not sure where I went wrong. Thanks for any help.
You just need to call your function:
odd_red_cards = red_odd([card1,card2,card3,card4])
print(odd_red_cards)
That'll give you a little more to debug.
Note that it might be useful to add a __str__ or __repr__ function to allow your cards to print a little more naturally which could be useful for finding other bugs down the road. e.g.:
class card(object):
...
def __repr__(self):
return '{suit} {value}'.format(suit=self.suit,value=self.value)

Categories