Python question about functions classes and class methods - python

I am fairly new to intermediate programming, yet have played around with code for a while.
At the moment I am making a simple card game.
This issue is I am not sure where to use my functions or when to make them class methods.
For example, here is a simple function that deals out 5 cards to each player (from a predefined list) and then turns a card from the top of the pack (actually all just random selections).
The cards are returned as a list of items (3 lists).
I have also made a class called "Player".
p1_hand = []
p2_hand = []
flip_card = []
def deal_deck():
count = 0
for i in range(0, 10, 1):
count += 1
if count % 2 == 0:
card = random.choice(deck)
p2_hand.append(card)
deck.remove(card)
else:
card = random.choice(deck)
p1_hand.append(card)
deck.remove(card)
if count == 10:
flip_card.append(random.choice(deck))
return p2_hand, p1_hand, flip_card
In this example, it's just a deal, so I wonder why it would need to be a class method of "Player"?
Possibly the class "Player" does not do much at all except keep score, keep track of who is dealing and what the score is?
To put it simply I am having trouble understanding the class as an object that interacts and preforms actions, I have made other classes and they have ended up working like a mini database rather than using much complexity in the methods at all.

There is an art to designing classes and objects. The fundamental purpose of using classes is information hiding. The rest of your program should not have to know how a deck of cards is implemented, which allows you to change the implementation without redesigning the whole program. Thus, you create a Deck class that has all of the data stored internally, and only exposes to the outside world the things you want to DO with a deck, like shuffle and deal_card. A Player class might include a hand and a score, and functions to add another card, and the Game object (maybe) could coordinate dealing cards into the hand and triggering the plays.
The code you have is mixing all of this. It has to know how a deck is implemented, and how a hand is implemented, and how a card is flipped.
By the way, for the sake of realism, it would be better for you to shuffle the deck and deal cards off the top, instead of using random.choice.

I originally down-voted this as too broad, but changed my mind as I wrote up my notes for you. Classes are a programming tool whose implementation doesn't receive much treatment at the level you're asking. There are many examples of good card-game classes available on the Internet ... and many bad ones. The research isn't easy for you.
Use a class to represent a unit of your system (a card game, in this case) that is cohesive (a set of data and capabilities with a readily understood boundary) and interacts with other units, or with the main program.
In this case, you have a good start: you've identified card, player, and hand as entities in your game system. You may want to treat the deck as a hand instance (just another list of cards), or you may want to give it special treatment due to different functions within the game.
The classes and functions I've seen as useful include:
Deck
The impartial source of cards
data
a list of cards
methods
reset -- shuffle all 52 cards into a list
deal(n) -- return a list of n cards
Hand
cards held by a single player
data
a list of cards
methods
reset -- whatever is needed to return the hand to a game-start state
draw(n) -- acquire n cards
play(n) -- play n cards to the game area
Card
A single card, containing all information needed to identify it to the game
data
suit
rank
methods
none
Player
Game information about each player
data
hand -- see Hand class above
game-role -- depending on the game, this could be "dealer", "idle", "active", ...
... other, depending on the game: points, money, etc.
methods
... very dependent on the game being played
Game
the overall monitor for the game
data
roster -- list of players
deck -- see Deck class above
... other, depending on the game: round, player to play next, etc.
Some of these overlap a bit, such as "Deck.deal" and "Hand.draw".
One of the design decisions you face is to choose which entity will drive an interaction between two objects.
As for implementation, I do suggest that you make your basic operations a lot simpler.
For the desk, initialize by generating all 52 cards with a nested list comprehension.
shuffle the deck, and then use list slicing to deal the cards. For instance, to deal 5 cards to each player:
def deal(n):
take = deck[:n]
deck = deck[n:]
return take
# elsewhere, to drive the initial dealing ...
for player in roster:
player.hand = deck.deal(5)
Simply reduce the interactions to match the way you talk about the game: "deal each player five cards" should look just like that in the code. Bury the mechanics within each class.

Related

I want to make sure the code below includes sequences

def reset_score(game):
time.sleep(0.5) # the snake freezes for a moment when hitting a wall then the game resets
head.goto(0, 0)
head.direction = "stop"
score = 0
# I could not find a way to remove the tails once the snake hit the wall
# so I moved the tail to somewhere in the screen that is not visible____This is called creativity
for game in snake_tail:
game.goto(1000, 1000)
score_printer.clear()
score_printer.write("Score: {} High Score: {}".format(score, high_score), align="center", font=("italic", 24, "normal"))
snake_tail.clear()
is "game.goto(1000,1000)" a tuple? or does this code include any other sequence. I am really not sure about a clear definition of sequence so I am not sure whether these are sequences or not?
Let's try to understand Sequence in brief, The main sequence types in Python are lists, tuples, and range objects. The main differences between these sequence objects are:
Lists are mutable and their elements are usually homogeneous (things of the same type making a list of similar objects)
Tuples are immutable and their elements are usually heterogeneous (things of different types making a tuple describing a single structure)
Range objects are efficient sequences of integers (commonly used for loops), use a small amount of memory, and yield items only when needed.
Also regarding game.goto(), goto() is used to move the turtle(pre-installed Python library that enables users to create pictures and shapes by providing them with a virtual canvas) at x and y coordinates.
I hope it is understandable now.

Python noob: Creating D&D style characters for a game

Good day to anyone who is willing to help this Python newbie...
I have been working through Zed Shaw's LPTHW book which has been going well. I'm in Exercise 45 where we need to make a new game using classes. As a part of this, I'm trying to figure out the best way to develop some D&D style characters in the game. Characters would have things like hit points, armor class, a weapon that does X amount of damage, has a basic inventory, etc.
Doing some additional research, I've considered the following two options.
1.. CSV file use -- This exercise wants us to do a file import and I thought doing a CSV file import would be a solid way to organize the data, import/manipulate it in Python, etc. However, while it appears pretty straight forward on how to import a CSV file into Python, it seems difficult to work with individual fields within the data once the file is read. For example, if the initial player data is brought in from the CSV, it seems difficult to adjust the hit points in that field if the player took damage. (or if an enemy takes damage)
2.. class Character(object) -- In light of #1, I've considered creating this class that defines the different parts of the characters as strings/integers to be defined later as an instance of the class. (ie goblin = Character(x, y, z, etc) If this option is better, I am additionally curious how to format an initial empty inventory list for someone like the player who would start with nothing in their inventory.
QUESTION: Should I just stick with class use or is what I'm trying to do worth my desire to push myself to try working with import files like a CSV, etc?
I apologize if any of this is confusing. I'm really enjoying my Python studies thus far but this is my first post so it's entirely possible I'm not following good etiquette.
Thanks much!
I'd go with Python classes, because then you can use properties that have sensible names that can be useful for the scope of a game.
class Character:
'''
This represents a D&D character with a bunch of stats.
'''
# You can use a constructor to initialize a new character
# with default values for their desired properties
def __init__(self, health = 10, inventory = None, ...):
self.health = health
if inventory is None:
self.inventory = []
else:
self.inventory = inventory
...
# You can add methods for game logic
def attack(self, other):
...
other.health -= self.damage
...
Using properties in a class allows you to extract and manipulate relevant information quickly and clearly. Methods help you to control what part of the program should act and how.

Python: Square Brackets Inside of Parentheses

I saw square bracketed arguments inside of parentheses. I don't know what it means and how it differs from a normal argument, so I am hoping for some enlightenment on this matter.
Here are the code lines in question:
(I'll print the whole block since someone might find it useful but the important parts are line 3 of the first code snippet and line 2 of the second code snippet)
First case:
def __additional_cards(self, player):
while not player.is_busted() and player.is_hitting():
self.deck.deal([player])
print(player)
if player.is_busted():
player.bust()
The bracketed argument in this line represents an object of a Player class (there can be many players) for the game and the code's purpose is to deal an additional card if the player asks for it.
Second case:
def play(self):
self.deck.deal(self.players + [self.dealer], per_hand = 2)
self.dealer.flip_first_card() # hide dealer's first card
for player in self.players:
print(player)
print(self.dealer)
In this case, the method .deal is used to deal starting cards to all of the players in the game as well as to the dealer. Here's the code for the .deal method for reference:
def deal(self, hands, per_hand = 1):
for rounds in range(per_hand):
for hand in hands:
if self.cards:
top_card = self.cards[0]
self.give(top_card, hand)
As you can see, it only takes 1 argument as to which 'hands' the cards should be dealt with, but the code above adds the dealer with the command + [self.dealer] and this is something I see for the first time.
Is the dealer added to hands simply by the + and if so, why is it in square brackets?
The code is taken from Michael Dawson's book, Python Programming for the Absolute Beginner, 3rd Edition and it is a section where he teaches OOP by explaining how to make a simple game of Blackjack.
The method deck.deal() takes as an argument a list of people to deal to. You cannot pass it a single object, because the method probably loops through every object in the list passed in a deals to those people. When you input [player], you're telling it the only person to deal to is that single player. It's good to leave it as a list because it allows you to generalize the code to accept an arbitrary number of people to deal to.
Looking at self.deck.deal(self.players + [self.dealer], per_hand = 2), what is happening here is we're adding the dealer to the list of the people being dealt to. Again, deal takes in a list of people, and since we want to deal to all the current players and the dealer we can just combine those two things into a single list. self.players is probably just a list of all the people who are playing, and when we + [self.dealer] we create a new list that has all of the players and the dealer. Python has operator overloading, meaning we can "add" two lists to combine them into a single longer list.
If I was coding this, I would just make player always be a list itself and self.dealer always be a single element list, since the code is generalized to only accept lists it seems.
EDIT for clarification:
I imagine the method deal looks something like:
def deal(people):
'''
people = a list of person objects to deal to
'''
for person in people:
# do something to that person
player is just an instance of an object. If we want to deal to ONLY that person, we need to pass in player in a list: [player] otherwise the code to deal to an arbitrary number of people would break, since you cannot loop over the player object, only a list of people.

League table in Python - it doesn't insert teams into the list

I need to make a league table for a project. There has to be 3 files,2 files consist of 1 class and the last file is for running a program. I have done all of the parts but when I call a method to add a team, the program adds the name but it does not insert it into the list of teams(which should do). When I try to display the items in the list, the program displays an error message instead of showing the actual team.
How can I fix it?Any help would be appreciated. :)
A few things here:
When I try to display the items in the list, the program displays: team.Team object at 0x000000000332A978 insted of showing the actual team.
The default display for a user class is something like <team.Team object at 0x000000000332A978>. If you want it to display something different, you have to tell Python what you want to display. There are two separate functions for this: __repr__ and __str__. The idea is that the first is a representation for the programmer, the second for the user. If you don't need two different representations, just define __repr__ and it'll use that whenever it needs __str__.
So, a really simple way to fix this is to add this to the Team class:
def __repr__(self):
return 'Team("{}")'.format(self._name)
Now, if you call league.addTeam('Dodgers'), then print(l._table), you'll get [Team("Dodgers")] instead of [<team.Team object at 0x000000000332A978>].
Meanwhile, these two methods are probably not what you want:
def removeTeam(self,team):
self._table.remove(team)
def returnPosition(self,team):
return self._table.index(team)
These will remove or find a team given the Team object—not the name, or even a new Team created from the name, but a reference to the exact same object stored in the _table. This is not all that useful, and you seem to want to call them with just names.
There are two ways to fix this: You could change Team so that it compares by name instead of by object identity, by adding this method to the class:
def __eq__(self, other):
return self._name == other._name
What this means is that if you say Team('Giants') == Team('Giants'), it will now be true instead of False. Even if the first team is in a different league, and has a different W-L record, and so on (e.g., like the baseball "Giants" from San Francisco vs. the football "Giants" from New York), as far as Python is concerned, they're now the same team. Of course if that's not what you want, you can write any other __eq__ function that seems more appropriate.
Anyway, if you do this, the index and remove functions will now be able to find any Team with the same name, instead of just the exact same team, so:
def removeTeam(self,team_name):
self._table.remove(Team(team_name))
def returnPosition(self,team_name):
return self._table.index(Team(team_name))
If you go this way, you might want to consider defining all of the comparison methods, so you can, e.g., sort a list of teams, and they sort by name.
Or you could change these methods so they don't work based on equality, e.g., by redefining them like this:
def removeTeam(self,team_name):
self._table = [team for team in self._table if team._name != team_name]
def returnPosition(self,team_name):
return [team._name for team in self._table].index(team_name)
To understand how these work, if you're not used to reading list comprehensions, turn each one back into the equivalent loop:
self._table = [team for team in self._table if team._name != team_name]
temp = []
for team in self._table:
if team._name != team_name:
temp.append(team)
self._table = temp
If you step through this, temp ends up with a list of every team in the table, except the one you wanted to remove, and then you replace the old self._table with the new filtered one. (Another way to write the same idea is with filter, if you know that function.)
It's usually better to create a new filtered list than to modify a list in-place. Sometimes there are performance reasons not do this, and sometimes it ends up being very complex and hard to understand, but it's usually both faster and simpler to reason about. Also, modifying lists in place leads to problems like this:
for i, value in enumerate(mylist):
if value == value_to_remove:
del mylist[i]
Play with this for a while, and you'll see that it doesn't actually work. Understanding why is a bit complicated, and you probably don't want to learn that until later. The usual trick to solve the problem is to iterate over a copy of the list… but once you're doing that, you've now got the worst of filtering and the worst of deleting-in-place at the same time.
The second function may be a little too clever, but let's look at it:
def returnPosition(self,team_name):
return [team._name for team in self._table].index(team_name)
First, I'm creating a list like the original one, but it's a list of just the names instead of the team objects. Again, let's decompose the list comprehension:
temp = []
for team in self._table:
temp.append(team._name)
Or try to translate it into English: This is a list of the team name of every team in the table.
Now, because this is a list of team names, I can use index(team_name) and it will find it. And, because the two lists have the same shape, I know that this is the right index to use in the original team list as well.
A much simpler solution would be to change _tables from a list of Teams into a dict mapping names to Teams. This is probably the most Pythonic solution—it looks a lot simpler than writing list comprehensions to do simple operations. (It's also probably the most efficient, but that's hardly relevant unless you have some truly gigantic leagues.) And then you don't even need returnPosition for anything. To do that:
def __init__(self):
self._table={}
def addTeam(self,name):
self._table[name]=Team(name)
def removeTeam(self,team_name):
del self._table[team_name]
def returnPosition(self,team_name):
return team_name
def updateLeague(self,team1_name1,team_name2,score1,score2):
if score1>score2:
self._table[team_name1].win()
self._table[team_name2].loss()
elif score1==score2:
self._table[team_name1].draw()
self._table[team_name2].draw()
elif score1<score2:
self._table[team_name1].loss()
self._table[team_name2].win()
Note that I've defined returnPosition to just return the team name itself as the position. If you think about it, dict keys are used exactly the same way as list indices, so this means any code someone wrote for the "old" API that required returnPosition will still work with the "new" API. (I probably wouldn't try to sell this to a teacher who assigned a problem that required us to use returnPosition, but for a real-life library where I wanted to make it easier for my 1.3 users to migrate to 2.0, I probably would.)
This only requires a few other changes. In displayList and saveList, you iterate over self._table.values() rather than self._table; in loadList, you change self._table.append(team) to self._table[a] = team. Speaking of loadList: You might want to consider renaming those local variables from a, b, c, and d to name, wins, losses, and draws.
A few other comments:
As kreativitea says in the comments, you should not create "private" variables and then add do-nothing accessor methods in Python. It's just more boilerplate that hides the real code, and one more thing you can get wrong with a silly typo that you'll spend hours debugging one day. Just have members named name, wins, losses, etc., and access them directly. (If someone told you that this is bad style because it doesn't let you replace the implementation in the future without changing the interface, that's only true in Java and C++, not in Python. If you ever need to replace the implementation, just read up on #property.)
You don't need print("""""")—and it's very easy to accidentally miscount the number of " characters. (Especially since some IDEs will actually be confused by this and think the multi-line string never ends.) Just do print().
You've got the same ending condition both in the while loop (while x!="q":) and in an internal break. You don't need it in both places. Either change it to while True:, or get rid of the break (just make options("q") do print("Goodbye"), so you don't need to special-case it at all inside the loop).
Whenever you have a long chain of elif statements, think about whether you can turn it into a dict of short functions. I'm not sure it's a good idea in this case, but it's always worth thinking about and making the explicit decision.
The last idea would look something like this:
def addTeam():
name=input("Enter the name of the team:")
l.addTeam(name)
def removeTeam():
teamToRemove=input("Enter the name of the team you want to remove:")
l.removeTeam(teamToRemove)
def recordGame():
team1=input("What is the name of the team?")
ans1=int(input("Enter the number of goals for the first team:"))
team2=input("What is the name of the team?")
ans2=int(input("Enter the number of goals for the second time:"))
l.updateLeague(team1,team2,ans1,ans2)
optionsdict = {
"a": addTeam,
"d": l.displayList,
"s": l.saveList,
"l": l.loadList,
"r": removeTeam,
"rec": recordGame,
}
def options(x):
func = optionsdict.get(x)
if func:
func()
As I said, I'm not sure it's actually clearer in this case, but it's worth considering.

Why can I not delete list elements while iterating in an OO setting?

My situation is as follows: I'm working on an implementation of BlackJack. I've got classes for Card, Hand, Player, Deck, and Game. The main game object stores players and a deck, while players stores hands which store cards.
I often do something like the following. In this example I am dealing the initial cards to each player.
num_cards = 2
for player in self.players:
new_hand = Hand()
for i in range(num_cards):
new_hand.add_card(self.deck.get_next_card())
player.assign_hand(new_hand)
This works splendidly. My problem now is that I wish to delete a hand from a player's set of hands (a player can split in BlackJack, causing more than one hand to be generated). In the following function, I intend to loop through each player's hands. If the value of the hand is greater than 21, I want to delete the hand. (Note that the remove() functionality below is normally performed in the Player class, called via a Player method named fold_hand(). I was having the same problem, so I have moved the code to somewhere more visible for expository purposes.)
for player in self.players:
for hand in player.hands:
if hand.smallest_value() > 21:
player.hands.remove(hand)
This does not work. To be clear, I am able to print out the hand before the remove() line and it does not print out after. That is, it seems to be removed. However, the in the next turn of play, the hand is back again. Thus the players' hands grow every turn.
The above code is in a function called validate_player_hands() in the Game class. This function is called from a file called play.py, which exists to start/end the game and facilitate the primary game loop. Thus, the only call to validate_player_hands() is in the play.py file, one indent in, in the game loop. I call:
game.validate_player_hands()
I have also tried finding the index of the hand and using the 'del' keyword, but the result is the same.
Why would the list element (a Hand object in a list called player.hands) fail to delete when it looks like it has been deleted?
Thanks in advance,
ParagonRG
How about using a simple list comprehension to eliminate hands:
for player in self.players:
player.hands = [hand for hand in player.hands if hand.smallest_value() <= 21]
EDIT
With filter:
for player in self.players:
player.hands = filter(lambda x: x.smallest_value() <= 21, player.hands)
Create a copy and iterate over the the object with an index tied to the object length. Set every elements to be deleted with a 0 and then filter the hands to purge the zeros.
for player in self.players:
for hand_idx in range(len(player.hands)):
if player.hands[hand_idx].smallest_value() > 21:
player.hands[hand_idx]=0
player.hands=filter(None,hands)
You will want to do something like the following:
newList = hands[:]
for hand in newList:
if hand.smallest_value() > 21: #your code here
player.hands.remove(hand)
This allows you to make a copy of the list to be modified while iterating over that copy thus avoiding the "tree limb" scenario that Mihai mentioned.
This can be solved by adding [:] to the end of the "for" statement. This creates a copy of the list. You can then loop over the copy, while changing the original list:
for player in self.players:
for hand in player.hands[:]:
if hand.smallest_value() > 21:
player.hands.remove(hand)
(This is Python's list slicing syntax. It's one of the fastest way to copy a list. More often this syntax is used in the form some_list[3:8] to get the list entries from index 3 to 7 inclusive, but by leaving out the first number you can get everything from the start of the list, and by leaving out the last number you get everything up to the end of the list).

Categories