headbanger headbanger - 14 days ago 4
Python Question

Multidimensional Array in Python and Pygame

As you might probably be able to guess, I'm quite new to Python! I'm trying to write a (very) simple Space Invaders game. Nothing flashy. No sounds, no explosions, not even any scores tracking (although, at some point, the aliens will move and shoot!).
Right now though the problem with my code is that I can shoot (and destroy) any aliens in the bottom row - but I can't destroy aliens in any other row. It's most perplexing - and I'd welcome any advice that anyone can offer.
This is my code:

# !/usr/bin/python

import pygame

bulletDelay = 40

class Bullet(object):
def __init__(self, xpos, ypos):
self.image = pygame.image.load("bullet.bmp")
self.rect = self.image.get_rect()
self.x = xpos
self.y = ypos

def current_position(self):
return [self.x, self.y]

def draw(self, surface):
surface.blit(self.image, (self.x, self.y))


class Player(object):
def __init__(self, screen):
self.image = pygame.image.load("spaceship.bmp") # load the spaceship image
self.rect = self.image.get_rect() # get the size of the spaceship
size = screen.get_rect()
self.x = (size.width * 0.5) - (self.rect.width * 0.5) # draw the spaceship in the horizontal middle
self.y = size.height - self.rect.height # draw the spaceship at the bottom

def current_position(self):
return self.x

def draw(self, surface):
surface.blit(self.image, (self.x, self.y)) # blit to the player position

class Alien(object):
def __init__(self, xpos, ypos):
self.image = pygame.image.load("alien.bmp") # load the alien image
self.rect = self.image.get_rect() # get the size of the alien
self.x = xpos
self.y = ypos

def current_position(self):
return [self.x, self.y]

def draw(self, surface):
surface.blit(self.image, (self.x, self.y)) # blit to the player position

def collision(alien,bullet):
if ((alien.current_position()[0] < bullet.current_position()[0] + 10) and
(alien.current_position()[0] > bullet.current_position()[0] - 10) and
(alien.current_position()[1] == bullet.current_position()[1])):
return True
else:
return False

def destroyObject(objectArray,killList):
if len(killList) > 0: # remove any bullets that have hit an alien
for item in killList:
del objectArray[item]

pygame.init()
screen = pygame.display.set_mode((640, 480))
clock = pygame.time.Clock()
player = Player(screen) # create the player sprite
missiles = [] # create missile array
aliensW = 6
aliensH = 3

# layout the initial field of aliens
aliens = [[Alien((screen.get_rect().width/7)*(x+0.75),(screen.get_rect().height/5)*(y+0.5)) for x in range(aliensW)] for y in range(aliensH)]

running = True
counter = bulletDelay



while running: # the event loop
counter=counter+1
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
key = pygame.key.get_pressed()
dist = 5 # distance moved for each key press
if key[pygame.K_RIGHT]: # right key
player.x += dist
elif key[pygame.K_LEFT]: # left key
player.x -= dist
elif key[pygame.K_SPACE]: # fire key
if counter > bulletDelay:
missiles.append(Bullet(player.current_position(),screen.get_rect().height))
counter=0

screen.fill((255, 255, 255)) # fill the screen with white - without this the old graphics will remain onscreen.

for m in missiles:
if m.y < (screen.get_rect()).height+1 and m.y > 0:
m.draw(screen)
m.y -= 5
else:
missiles.pop(0)

alienGrid = []
for a in aliens:
killList=[]
spentBullets=[]
alienNumber=0
alienRow = a
for b in alienRow:
#need to move aliens as well!
missileNumber=0
for m in missiles:
if (collision(b,m)):
killList.insert(0,alienNumber)
spentBullets.insert(0,missileNumber)
missileNumber+=1
alienNumber += 1
b.draw(screen)

destroyObject(alienRow,killList)
destroyObject(missiles,spentBullets)

alienGrid.insert(0,alienRow)
aliens = alienGrid


player.draw(screen) # draw the spaceship to the screen
pygame.display.update() # update the screen
clock.tick(40)


Of course, I also welcome any other advice that you might be able to offer for improving my code!

Answer

Use print() to check values in variables and which part of code is executed. This way you can find problem (if you don't know how to use debuger)

Problem is

alien.current_position()[1] == bullet.current_position()[1]

which not always is true because missile moves 5 pixels, not 1px.

Besides you use 0.75 and 0.5 in calculations of alien positions so you have float values.

You could use self.rect to keep position and then you could use

def collision(alien, bullet):
    return alien.rect.colliderect(bullet.rect)

and

def draw(self, surface):
    surface.blit(self.image, self.rect)

My version with many modifications. I use class inherition. And I use time (instead of counting frame) to control missiles.

# !/usr/bin/python

import pygame

# --- constants ---

BULLET_DELAY = 1000 # 1000ms = 1s
BULLET_DIST = 5 # distance moved for each key press

RED = (255,  0,  0)
WHITE = (255, 255, 255)

FPS = 40

# --- classes ---

class Sprite(object):
    def __init__(self, xpos, ypos, image=None):
        if image:
            self.image = pygame.image.load(image)
        else:
            self.image = pygame.Surface(50,50)
            self.image.fill(RED)    

        self.rect = self.image.get_rect(x=xpos, y=ypos)

    def current_position(self):
        return self.rect

    def draw(self, surface):
        surface.blit(self.image, self.rect)


class Bullet(Sprite):

    def __init__(self, xpos, ypos):
        Sprite.__init__(self, xpos, ypos, "bullet.bmp")
        # modificate position
        self.rect.centerx = xpos
        self.rect.bottom = ypos

class Alien(Sprite):

    def __init__(self, xpos, ypos):
        Sprite.__init__(self, xpos, ypos, "alien.bmp")


class Player(Sprite):

    def __init__(self, xpos, ypos):
        Sprite.__init__(self, xpos, ypos, "spaceship.bmp")
        # modificate position
        self.rect.centerx = xpos
        self.rect.bottom = ypos

# --- main ---

pygame.init()
screen = pygame.display.set_mode((640, 480))
screen_rect = screen.get_rect()

# -

player = Player(screen_rect.centerx, screen_rect.bottom) # create the player sprite

# -

missiles = [] # create missile array

# -

aliensW = 6
aliensH = 3

# layout the initial field of aliens
sx = screen_rect.width/7
sy = screen_rect.width/7
aliens = [[Alien(sx*(x+0.75),sy*(y+0.5)) for x in range(aliensW)] for y in range(aliensH)]

# move direction
move_left = False

# ---

current_time = pygame.time.get_ticks()

# missile delay
next_missile_time = current_time

# --- mainloop ---

clock = pygame.time.Clock()

running = True

while running: # the event loop

    current_time = pygame.time.get_ticks()

    # --- events ---

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
             running = False

    key = pygame.key.get_pressed()

    if key[pygame.K_RIGHT]:    # right key
        player.rect.x += BULLET_DIST
        if player.rect.right > screen_rect.right:
            player.rect.right = screen_rect.right

    if key[pygame.K_LEFT]:   # left key
        player.rect.x -= BULLET_DIST
        if player.rect.left < screen_rect.left:
            player.rect.left = screen_rect.left

    if key[pygame.K_SPACE]:  # fire key
        if current_time >= next_missile_time:
            print('[D] fire')
            missiles.append(Bullet(player.rect.centerx, player.rect.top))
            # missile delay
            next_missile_time = current_time + BULLET_DELAY

    # --- updates (without draws) ---

    # move missiles (and remove if leaves screen)

    temp = []

    for m in missiles:
        m.rect.y -= BULLET_DIST
        if m.rect.bottom >= 0:
            temp.append(m)
    missiles = temp

    # move aliens (and check collisio)

    temp = []

    next_direction = move_left

    for row in aliens:
        temp_row = []
        for a in row:
            if move_left:
                # move
                a.rect.x -= 1
                # check collision
                for i, m in enumerate(missiles):
                    if a.rect.colliderect(m.rect):
                        # remove missile
                        del missiles[i]
                        # don't check collisions with other misilles
                        break
                else: # chech only if "no break" so "no collide"
                    # chech if change direction
                    if a.rect.left == screen_rect.left:
                        # need to change direction but don't change `move_left` yet
                        next_direction = False
                    # add if not collide
                    temp_row.append(a)
            else: 
                # move
                a.rect.x += 1
                # check collision
                for i, m in enumerate(missiles):
                    if a.rect.colliderect(m.rect):
                        # remove missile
                        del missiles[i]
                        # don't check collisions with other misilles
                        break
                else: # chech only if "no break" so "no collide"
                    # chech if change direction
                    if a.rect.right == screen_rect.right:
                        # need to change direction but don't change `move_left` yet
                        next_direction = True
                    # add if not collide
                    temp_row.append(a)
        temp.append(temp_row)

    # change after all checkings        
    move_left = next_direction

    # new list without hitted aliens
    aliens = temp

    # --- draws (without updates) ---

    screen.fill(WHITE)  # fill the screen with white - without this the old graphics will remain onscreen.

    for row in aliens:
        for a in row:
            a.draw(screen)

    for m in missiles:
        m.draw(screen)

    player.draw(screen)           # draw the spaceship to the screen

    pygame.display.update()       # update the screen

    # --- FPS ---

    clock.tick(FPS)