[SOLVED] What to do with many almost-same if-statements?

Issue

I need help. Im new on coding, so I’ve developed a game with pygame.
It’s a game where you fight as a robot against a zombie. If a fireball collides with the zombie, the heart picture will be updated from filled to half and so on.

The Tech-Lead said that this code is not efficient because of the many if statements in the def hearts() method in the Enemy class.

Could you please help me to shorten it? I have absolutely 0 idea what I could do. Thinking about loops, but dont know how to do it. Please help me

Here is my code:

#pygame importieren
import pygame

#pygame initialisieren
pygame.init()

#Konstante für die Farben initialisieren
WHITE   = [0xFF, 0xFF, 0xFF]
ORANGE  = [0xFF, 0x8C, 0x00]
RED     = [0xFF, 0x00, 0x00]
GREEN   = [0x00, 0xFF, 0x00]
BLUE    = [0x00, 0x00, 0xFF]
BLACK   = [0x00, 0x00, 0x00]

#Hintergrundbilder und Sounds einer Variablen zuweisen
background      = pygame.image.load("Grafiken/hintergrund.jpg")
attackLeft      = pygame.image.load("Grafiken/angriffLinks.png")
attackRight     = pygame.image.load("Grafiken/angriffRechts.png")
jumping         = pygame.image.load("Grafiken/sprung.png")
going_right     = [pygame.image.load("Grafiken/rechts1.png"), pygame.image.load("Grafiken/rechts2.png"), pygame.image.load("Grafiken/rechts3.png"), pygame.image.load("Grafiken/rechts4.png"), pygame.image.load("Grafiken/rechts5.png"), pygame.image.load("Grafiken/rechts6.png"), pygame.image.load("Grafiken/rechts7.png"), pygame.image.load("Grafiken/rechts8.png")]
going_left      = [pygame.image.load("Grafiken/links1.png"), pygame.image.load("Grafiken/links2.png"), pygame.image.load("Grafiken/links3.png"), pygame.image.load("Grafiken/links4.png"), pygame.image.load("Grafiken/links5.png"), pygame.image.load("Grafiken/links6.png"), pygame.image.load("Grafiken/links7.png"), pygame.image.load("Grafiken/links8.png")]
jumping_sound   = pygame.mixer.Sound("Sound/sprung.wav")
shooting_sound  = pygame.mixer.Sound("Sound/shoot.mp3")
#Fenster erstellen
screen = pygame.display.set_mode([1280, 800])

#Fenstertitel erstellen
pygame.display.set_caption("Mein erstes pygame-Spiel")

#Spielerklasse
class Spieler:
    def __init__(self, x, y, speed, jump, player_width, player_height, direction, steps_left, steps_right):
        self.x = x
        self.y = y
        self.speed = speed
        self.jump = jump
        self.player_width = player_width
        self.player_height = player_height
        self.direction = direction
        self.steps_left = steps_left
        self.steps_right = steps_right
        #Springt er?
        self.sprung = False
        #Anfangsposition des Spielers
        self.lastPosition = [1, 0]
        #Schießbereitschaft
        self.ready2shoot = True
    
    #Eine Methode zum nach links und nach rechts laufen
    def walking(self, liste):
        if liste[0]:
            #Index 0 des Arrays direction ist Links. Wenn nach Links, dann Position des Spielers auf x - Geschwindigkeit
            self.x -= self.speed
            self.direction = [1, 0, 0, 0]
            self.steps_left += 1
        if liste[1]:
            #Index 1 des Arrays direction ist Rechts. Wenn nach Rechts, dann Position des Spielers auf x + Geschwindigkeit
            self.x += self.speed
            self.direction = [0, 1, 0, 0]
            self.steps_right += 1
    
    #Schritte werden zurückgesetzt
    def resetsteps(self):
        self.steps_left = 0
        self.steps_right = 0
    
    #Index 3 des Arrays direction ist stehen. Wenn stehen, dann werden die Schritte zurückgesetzt
    def standing(self):
        self.direction = [0, 0, 1, 0]
        self.resetsteps()
    
    #Sprungmethode wenn die springen-Taste gedrückt wird
    def sprungsetzen(self):
        #Wenn der SPieler -16px springen will, ist die Sprungbereitschaft True.
        if self.jump == -16:
            self.sprung = True
            self.jump = 15
            pygame.mixer.Sound.play(jumping_sound)
    
    #Berechnung des Sprunges
    def springen(self):
        if self.sprung:
            self.direction = [0, 0, 0, 1]
            if self.jump >= -15:
                n = 1
                if self.jump < 0:
                    n = - 1
                self.y -= (self.jump**2)*0.15*n
                self.jump -= 1
            else:
                self.sprung = False
    
    #Die Erstellung des Spielers
    def spZeichnen(self):
        
        if self.steps_left == 63:
            self.steps_left = 0
        if self.steps_right == 63:
            self.steps_right = 0

        if self.direction[0]:
            screen.blit(going_left[self.steps_left//8], (self.x, self.y))
            self.lastPosition = [1, 0]
        if self.direction[1]:
            screen.blit(going_right[self.steps_right//8], (self.x, self.y))
            self.lastPosition = [0, 1]
        if self.direction[2]:
            if self.lastPosition[0]:
                screen.blit(attackLeft, (self.x, self.y))
            else:
                screen.blit(attackRight, (self.x, self.y))
        if self.direction[3]:
            screen.blit(jumping, (self.x, self.y))

class Enemy:
    def __init__(self, x, y, speed, player_width, player_height, direction, xMin, xMax):
        self.x = x
        self.y = y
        self.speed = speed
        self.player_width = player_width
        self.player_height = player_height
        self.direction = direction
        self.xMin   = xMin
        self.xMax   = xMax
        self.steps_left     = 0
        self.steps_right    = 0
        self.health = 12
        self.left_walk   = [pygame.image.load("Grafiken/l1.png"), pygame.image.load("Grafiken/l2.png"), pygame.image.load("Grafiken/l3.png"), pygame.image.load("Grafiken/l4.png"), pygame.image.load("Grafiken/l5.png"), 
        pygame.image.load("Grafiken/l6.png"), pygame.image.load("Grafiken/l7.png"), pygame.image.load("Grafiken/l8.png")]
        self.right_walk  = [pygame.image.load("Grafiken/r1.png"), pygame.image.load("Grafiken/r2.png"), pygame.image.load("Grafiken/r3.png"), pygame.image.load("Grafiken/r4.png"), pygame.image.load("Grafiken/r5.png"),
        pygame.image.load("Grafiken/r6.png"), pygame.image.load("Grafiken/r7.png"), pygame.image.load("Grafiken/r8.png")]
        self.health_full = pygame.image.load("Grafiken/voll.png")
        self.health_half = pygame.image.load("Grafiken/halb.png")
        self.health_zero = pygame.image.load("Grafiken/leer.png")
    
    def hearts(self):
        if self.health >= 2:
            screen.blit(self.health_full, (440, 75))
        if self.health >= 4:
            screen.blit(self.health_full, (500, 75))
        if self.health >= 6:
            screen.blit(self.health_full, (560, 75))
        if self.health >= 8:
            screen.blit(self.health_full, (620, 75))
        if self.health >= 10:
            screen.blit(self.health_full, (680, 75))
        if self.health == 12:
            screen.blit(self.health_full, (740, 75))
        
        if self.health == 1:
            screen.blit(self.health_half, (440, 75))
        if self.health == 3:
            screen.blit(self.health_half, (500, 75))
        if self.health == 5:
            screen.blit(self.health_half, (560, 75))
        if self.health == 7:
            screen.blit(self.health_half, (620, 75))
        if self.health == 9:
            screen.blit(self.health_half, (680, 75))
        if self.health == 11:
            screen.blit(self.health_half, (740, 75))
        
        if self.health == 0:
            screen.blit(self.health_zero, (440, 75))
        if self.health <= 2:
            screen.blit(self.health_zero, (500, 75))
        if self.health <= 4:
            screen.blit(self.health_zero, (560, 75))
        if self.health <= 6:
            screen.blit(self.health_zero, (680, 75))
        if self.health <= 8:
            screen.blit(self.health_zero, (680, 75))
        if self.health <= 10:
            screen.blit(self.health_zero, (740, 75))


    def enemy_zeichnen(self):
        if self.steps_left == 63:
            self.steps_left = 0
        if self.steps_right == 63:
            self.steps_right = 0

        if self.direction[0]:
            screen.blit(self.left_walk[self.steps_left//8], (self.x, self.y))
        if self.direction[1]:
            screen.blit(self.right_walk[self.steps_right//8], (self.x, self.y))
        
    def enemy_walking(self):
        self.x += self.speed
        if self.speed < 0:
            self.direction = [1, 0]
            self.steps_left += 1
        if self.speed > 0:
            self.direction = [0, 1]
            self.steps_right += 1

    def enemy_back_forth(self):
        if self.x > self.xMax:
            self.speed *= -1
        elif self.x < self.xMin:
            self.speed *= -1
        self.enemy_walking()

class FireBall:
    def __init__(self, spX, spY, fb_Direction, fb_radius, fb_color, speed):
        #Der Feuerball befindet sich auf der selben Position wie der Spieler
        self.x = spX
        self.y = spY
        #Feuerballrichtung nach Links, befindet sich immer 5px vom Spieler entfernt und bewegt sich *1,5 des speeds in - y-Richtung (-1 * speed)
        if fb_Direction[0]:
            self.x += 5
            self.speed = - 1 * speed * 1.5
        #Feuerballrichtung nach Rechts, befindet sich immer 90px vom Spieler entfernt und bewegt sich * 1,5 des speeds in y-Richtung
        elif fb_Direction[1]:
            self.x += 90
            self.speed = speed * 1.5
        #Feuerball ist 90px unter dem Spieler
        self.y += 90
        self.fb_radius = fb_radius
        self.fb_color = fb_color
    #Bewegung des Feuerballs
    def move(self):
        self.x += self.speed
    #Zeichnung des Feuerballs
    def fb_Zeichnen(self):
        pygame.draw.circle(screen, self.fb_color, (self.x, self.y), self.fb_radius, 0)

#Methode um Zeichnungen anzuzeigen
def show_player():
    screen.blit(background, (0,0))
    for f in fireball:
        f.fb_Zeichnen()

    spieler1.spZeichnen()
    zombie.hearts()
    zombie.enemy_zeichnen()
    zombie.hearts()
    pygame.display.flip()

#Verhalten vom Feuerball
def feuerkugeln():
    for f in fireball:
        if f.x >= 0 and f.x <= 1270:
            f.move()
        else:
            fireball.remove(f)

def collision():
    global fireball, win, loose, game_active
    zombieRect = pygame.Rect(spieler1.x+18,spieler1.y+36,spieler1.player_width-36, spieler1.player_height-36)
    
    player_rect = pygame.Rect(zombie.x+18,zombie.y+24,zombie.player_width-36, zombie.player_height-24)
    for f in fireball:
        fbRect = pygame.Rect(f.x-f.radius,f.y-f.radius,f.radius*2,f.radius*2)
        if zombieRect.colliderect(fbRect):
            fireball.remove(f)
            zombie.health -= 1
            if zombie.health <=0 and not verloren:
                gewonnen = True
                game_active = False
        
    if zombieRect.colliderect(player_rect):
        loose = True
        won = False
        game_active = False
        
#Grenzen erstellen
left_border     = pygame.draw.rect(screen, BLACK, [0, 0, 2, 800], 0)
right_border    = pygame.draw.rect(screen, BLACK, [1225, 0, 2, 800], 0)

#Klasse aufrufen
spieler1 = Spieler(300, 575, 5, -16, 30, 50, [0, 0, 1, 0], 0, 0)
zombie = Enemy(600, 575, 8, 30, 50, [0,0], 0, 1200)
loose = False
win = False
#fireball Array. Wieviele fireballs auf einmal auf dem Spielfeld sein können
fireball = []

#Bedingung für den Spielstart
game_active = True

#Bildschirmaktualisierung einstellen
clock = pygame.time.Clock()

#Hauptschleife des Spiels
while game_active:
    #Nutzeraktion überprüfen
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            game_active = False
    
    player_rect = pygame.Rect(spieler1.x, spieler1.y, spieler1.player_width, spieler1.player_height)
    
    pressed = pygame.key.get_pressed()

#Spielelogik wird berechnet (Bewegung der Figur, Kollision etc)
    #Ereignis wenn man sich nach links bewegt und auf die Grenze stößt
    if pressed[pygame.K_LEFT] and not player_rect.colliderect(left_border):
       spieler1.walking([1, 0])
    #Ereignis wenn man sich nach rechts bewegt und auf die Grenze stößt
    elif pressed[pygame.K_RIGHT] and not player_rect.colliderect(right_border):
        spieler1.walking([0, 1])
    #Ereignis wenn keine Taste gedrückt wird
    else:
        spieler1.standing()

    #Ereignis wenn die Sprung-Taste gedrückt wird
    if pressed[pygame.K_UP]:
        spieler1.sprungsetzen()

    #Ereignis wenn die Space-Taste gedrückt wird
    if pressed[pygame.K_SPACE]:
        #Solange 5 oder weniger Feuerbälle im fireball Array sind und der Spieler schießt, nimmt der Array um einen Index zu.
        if len(fireball) <= 4 and spieler1.ready2shoot:
            fireball.append(FireBall(spieler1.x, spieler1.y, spieler1.lastPosition, 8, RED, 7))
            pygame.mixer.Sound.play(shooting_sound)
        
        spieler1.ready2shoot = False

    if not pressed[pygame.K_SPACE]:
        spieler1.ready2shoot = True

    feuerkugeln()
    #Sprungvorgang
    spieler1.springen()
    #Die Anzeige vom Spieler
    show_player()
    zombie.hearts()
    #Die Anzeige vom Zombie
    zombie.enemy_back_forth()
    collision()
    #Framerate des Spiels
    clock.tick(60)

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            game_active = False
    show_player()
    
pygame.quit()

Solution

The tech-lead is wrong: your code is perfectly efficient the way it is written. Making the code shorter does not make it faster or more "elegant".

However, shorter code can be easier to maintain and change. Your code is fine as long as the number of heart containers is always exactly 12. But if you want to change that (to increase/decrease the difficultly of the game, or let the player get new heart containers) then this code won’t work. It is hard-coded to work with exactly 12 heart containers only.

To change this, put this repetitive code in a loop. You’ll need to look at the pattern of how the numbers change and create a small math formula for it. I’ve come up with the following. (I’ve also added constants instead of the integer literals, so that the code is easier to read and change.)

MAX_HEALTH = 12
LEFTMOST_HEART = 440
TOPMOST_HEART = 75
HEART_HALF_WIDTH = 30

def hearts(self):
    # Draw all the full hearts:
    for i in range(0, self.health, 2):
        screen.blit(self.health_full(LEFTMOST_HEART + (i * HEART_HALF_WIDTH), TOPMOST_HEART))

    # Draw the half-heart, if needed:
    if self.health % 2 == 1:
        screen.blit(self.health_half(LEFTMOST_HEART + ((i - 1) * HEART_HALF_WIDTH), TOPMOST_HEART))

    # Draw the empty hearts:
    for i in range(MAX_HEALTH, self.health, -2):
        screen.blit(self.health_zero(LEFTMOST_HEART + ((i - 2) * HEART_HALF_WIDTH), TOPMOST_HEART))        

This code works with any number of health (as long as it’s an even number). You can also change the placement of the hearts by changing LEFMOST_HEART and TOPMOST_HEART.

This code is just as "efficient" as your code, but it is easier to maintain because changing the max health or position of the hearts only requires changing some constants.

Answered By – Al Sweigart

Answer Checked By – Mildred Charles (BugsFixing Admin)

Leave a Reply

Your email address will not be published.