How to optimize for loop - Python 3 - python

My very first post, sorry if I made any mistakes!
I'm messing around the pygame library, and I'm extremely new to python in general. I have created 3 enemies classes and they work as intended, however, this is how I make them move (I call the move function stored inside the class).
I wonder if there is a more clean way to do things. I have many other enemies to code and this seems very repetitive to type, so it's a sign I'm doing something wrong.
I tried creating a "control list" where I list every enemy list there is and I try to access them through their index but it isn't working. I also tried to concatenate but I'm getting an error saying I can't concatenate list names, another error was that my list names turned into strings (yes, I tried using quotation marks). I'm sure this will be a simple fix, but I spent 3 days and I wrap my head around it. If it's possible to do so
The sample of my code so far - it is located in the main run loop of my game.
for giant in lst_enemy_giants:
giant.move()
else:
pass
for spider in lst_enemy_spiders:
spider.move()
else:
pass
for goblin in lst_enemy_goblin:
giant.move()
else:
pass
# The pattern the I want
for ENEMY in lst_enemy_ENEMY:
ENEMY.move()
# where ENEMY is any enemy list that can be stored somewhere

Just chain the iterators together.
from itertools import chain
for enemy in chain(lst_enemy_giants, lst_enemy_spiders, lst_enemy_goblin):
enemy.move()
This is a slightly nicer way of writing a nested loop like
for enemy_list in [lst_enemy_giants, lst_enemy_spiders, lst_enemy_goblin]:
for enemy in enemy_list:
enemy.move()
Unrelated, but I recommend keeping a single dict like
enemies = {'giants': [...], 'spiders': [...], 'goblins': [...]}
rather than three separate variables referencing separate lists. You can still use, for example, enemies['giants'] anywhere you would have used lst_enemy_giants, but now you can write
for enemy in chain(*enemies.values()):
enemy.move()

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: 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.

"For" loop Python code - does this look right/acceptable?

I would like to create a "for" loop (in Python 2). I have a list of obstacles and for each obstacle, if they are true (i.e. exist and appear in the list) I would like to append them to a list called "tests" and call a function called "obstacle_detection" (which deals with what happens when an obstacle is detected) (and I use "tests" later). This is part of a much larger program and I can't quite tell whether it's working, so I was wondering if anyone would be able to tell me if it makes sense? Or advise me of a better way there might be of doing this?
obstacles = [obstacle, obstacle1, obstacle2]
tests = []
counter = 0
for obstacle in obstacles:
tests.append(0)
tests[counter] = obstacle_detection(obstacle, pos)
counter = counter + 1
Your code possibly makes sense, depending on how you define obstacles and obstacle_detection.
As it is, you could write your code this way :
tests = [obstacle_detection(obstacle, pos) for obstacle in obstacles]
It creates a new list automatically, with the same length as obstacles and filled with obstacle_detection values for each obstacle.

Sprite Values Must Move

I have a sprite and when it collides with another object I want it to increase the score that the user gets. However I am unsure how to export the fact that the sprite has collided to another part of my program. This is what I tried:
def checkCollision(self, sprite):
if self.rect.colliderect(sprite.rect):
self.x=3000
self.score=score+100
However it ignore the fact the score has increased despite me having made score global.
Any help would be appreciated.
I'm assuming that the collision function you provided is part of your player class. Making that assumption, you could simply assign the global score to = x.score (x = whatever you named that particular player object) Obviously this is less than ideal but it's a simple way to stick in the functionality you're looking for.

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