12
\$\begingroup\$

I have finished my first project for Python. I would be very grateful if you could check it and give me some feedback. Just simple game with possibility of replay. There are probably lots of mistakes and code could be half the size, but I just started with programming.

I tried this game on a few computers and unfortunately I was unable to run it on some older laptops. There are also issues with the resolution on some computers and I don't know how to fix this.

Is this good enough to show this as an example of my skills for very entry level job in programming?

You can find code, all needed resources and game on GitHub.

# SPACE GAME! :-)

def game():

    # import libraries

    import pygame
    from random import randint, choice

    # starting pygame

    pygame.init()

    # screen

    size = (900, 700)
    screen = pygame.display.set_mode(size)
    pygame.display.set_caption("SPACEMAN")
    background = pygame.image.load("images/background2.jpg").convert()

    finish= False

    # clock and colors

    clock = pygame.time.Clock()
    black = (0, 0, 0)
    white = (255, 255, 255)

    #loading sounds

    explosion = pygame.mixer.Sound('sounds/Explosion.wav')
    rock_sound = pygame.mixer.Sound('sounds/rock.wav')
    hole_sound = pygame.mixer.Sound('sounds/hole.wav')
    nyan_sound = pygame.mixer.Sound('sounds/nyan2.wav')
    s1 = pygame.mixer.Sound('sounds/s1.wav')
    s2 = pygame.mixer.Sound('sounds/s2.wav')
    s3 = pygame.mixer.Sound('sounds/s3.wav')
    s4 = pygame.mixer.Sound('sounds/s4.wav')
    s5 = pygame.mixer.Sound('sounds/s5.wav')
    s6 = pygame.mixer.Sound('sounds/s6.wav')
    s7 = pygame.mixer.Sound('sounds/s7.wav')
    s8 = pygame.mixer.Sound('sounds/s8.wav')
    s9 = pygame.mixer.Sound('sounds/s9.wav')
    s10 = pygame.mixer.Sound('sounds/s10.wav')
    s11 = pygame.mixer.Sound('sounds/s11.wav')
    s12 = pygame.mixer.Sound('sounds/s12.wav')
    s13 = pygame.mixer.Sound('sounds/s13.wav')
    s14 = pygame.mixer.Sound('sounds/s14.wav')
    s15 = pygame.mixer.Sound('sounds/game.wav')
    sounds = [s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14]
    s15.play(-1)

    # function for in-game random sounds

    def fx():
        ingame_sound = choice(sounds)
        ingame_sound.play()

    # let's hide mouse!

    pygame.mouse.set_visible(False)

    # score variables

    frame_count = 0
    frame_rate = 30

    #  player ship class

    class Ship(pygame.sprite.Sprite):

        def __init__(self, location):
            pygame.sprite.Sprite.__init__(self)
            Ship.image = pygame.image.load("images/player.png").convert()
            self.image = Ship.image
            self.rect = self.image.get_rect()
            self.rect.topleft = location

    # player variables and object lists

    play = Ship([0, 0])
    life = True
    Ship.image.set_colorkey(black)
    rock_list = []
    hole_list = []
    nyan_list = []
    nyan_bonus = 0

    #object classes

    class ItemInSpace(pygame.sprite.Sprite):

        def __init__(self):
            self.image = 0
            self.start_list = []
            self.dx = 0
            self.dy = 0
            self.posx = 0
            self.posy = 0
            self.pos = [self.dx, self.dy]

    def rock_create():
        rock = ItemInSpace()
        angle = randint(0, 360)
        rock.image = pygame.image.load("images/rock2.png").convert()
        rock.image.set_colorkey(black)
        rock.image = pygame.transform.rotate(rock.image, angle)
        rock.posx = (choice([randint(0, 50), randint(800, 850)]))
        rock.posy = randint(0, 700)
        rock.pos = [rock.posx, rock.posy]
        rock.start_list = [-4, -3, -2, -1, 1, 2, 3, 4]
        rock.dx = choice(rock.start_list)
        rock.dy = choice(rock.start_list)
        rock_sound.play()
        rock_list.append(rock)

    def hole_create():
        hole = ItemInSpace()
        hole.image = pygame.image.load("images/black_hole.jpg").convert()
        hole.image.set_colorkey(black)
        hole.posx = (choice([randint(5, 845)]))
        hole.posy = randint(5, 645)
        hole.pos = [hole.posx, hole.posy]
        if (hole.posx < (pos[0] - 200)) or (hole.posx > (pos[0] + 200)) or (hole.posy < (pos[1] - 200)) or (hole.posy > (pos[0] + 100)):
            hole_sound.play()
            hole_list.append(hole)
        else:
            hole_create()

    def nyan_create():
        nyan = ItemInSpace()
        nyan.image = pygame.image.load("images/nyan.png").convert()
        nyan.image.set_colorkey(black)
        nyan.posx = choice([-100, 1000])
        nyan.posy = randint(5, 645)
        nyan.pos = [nyan.posx, nyan.posy]
        if nyan.posx == -100:
            nyan.dx = 6
        else:
            nyan.dx = -6
            nyan.image = pygame.transform.flip(nyan.image, True, False)
        if len(nyan_list) == 0:
            nyan_list.append(nyan)

    pygame.mouse.set_pos([450, 325])

    # object collision detection

    def collisionDetection():
        if (pos[0] >= (rock.pos[0] - 50)) and (pos[0] <= (rock.pos[0] + 50)) and (pos[1] >= (rock.pos[1] - 50)) and (pos[1] <= (rock.pos[1] + 50)):
            return True
        elif (pos[0] >= (rock.pos[0] - 50)) and (pos[0] <= (rock.pos[0] + 50)) and (pos[1] >= (rock.pos[1])) and (pos[1] <= (rock.pos[1] + 50)):
            return True
        elif (pos[0] >= (rock.pos[0])) and (pos[0] <= (rock.pos[0] + 50)) and (pos[1] >= (rock.pos[1] + 50)) and (pos[1] <= (rock.pos[1] - 50)):
            return True
        elif (pos[0] >= (rock.pos[0])) and (pos[0] <= (rock.pos[0] + 50)) and (pos[1] >= (rock.pos[1])) and (pos[1] <= (rock.pos[1] + 50)):
            return True
        else:
            return False

    def hole_collisionDetection():
        if (pos[0] >= (hole.pos[0] - 50)) and (pos[0] <= (hole.pos[0] + 50)) and (pos[1] >= (hole.pos[1] - 50)) and (pos[1] <= (hole.pos[1] + 50)):
            return True
        elif (pos[0] >= (hole.pos[0] - 50)) and (pos[0] <= (hole.pos[0] + 50)) and (pos[1] >= (hole.pos[1])) and (pos[1] <= (hole.pos[1] + 50)):
            return True
        elif (pos[0] >= (hole.pos[0])) and (pos[0] <= (hole.pos[0] + 50)) and (pos[1] >= (hole.pos[1] + 50)) and (pos[1] <= (hole.pos[1] - 50)):
            return True
        elif (pos[0] >= (hole.pos[0])) and (pos[0] <= (hole.pos[0] + 50)) and (pos[1] >= (hole.pos[1])) and (pos[1] <= (hole.pos[1] + 50)):
            return True
        else:
            return False

    def nyan_collisionDetection():
        if (pos[0] >= (nyan.pos[0] - 50)) and (pos[0] <= (nyan.pos[0] + 50)) and (pos[1] >= (nyan.pos[1] - 50)) and (pos[1] <= (nyan.pos[1] + 50)):
            return True
        elif (pos[0] >= (nyan.pos[0] - 50)) and (pos[0] <= (nyan.pos[0] + 50)) and (pos[1] >= (nyan.pos[1])) and (pos[1] <= (nyan.pos[1] + 50)):
            return True
        elif (pos[0] >= (nyan.pos[0])) and (pos[0] <= (nyan.pos[0] + 50)) and (pos[1] >= (nyan.pos[1] + 50)) and (pos[1] <= (nyan.pos[1] - 50)):
            return True
        elif (pos[0] >= (nyan.pos[0])) and (pos[0] <= (nyan.pos[0] + 50)) and (pos[1] >= (nyan.pos[1])) and (pos[1] <= (nyan.pos[1] + 50)):
            return True
        else:
            return False

    # main loop

    while not finish:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                finish = True

    # borders for player ship

        pos = list(play.rect)
        if pos[0] > 850:
            pos[0] = 850
        if pos[1] <= 0:
            pos[1] = (-10)
        if pos[1] > 660:
            pos[1] = 660

    #borders for rocks

        for rock in rock_list:
            if rock.pos[0] >= 900 and rock.dx > 0:
                rock.pos[0] = -35

            if rock.pos[0] <= -35 and rock.dx < 0:
                rock.pos[0] = 900

            if rock.pos[1] >= 700 and rock.dy > 0:
                rock.pos[1] = -35

            if rock.pos[1] <= -35 and rock.dy < 0:
                rock.pos[1] = 700

    # checking collisions

        for hole in hole_list:
            if hole_collisionDetection() == True:
                life = False
                explosion.play()
                play.rect = [-100, -100]

        for rock in rock_list:
            if collisionDetection() == True:
                life = False
                explosion.play()
                play.rect = [-100, -100]

        for nyan in nyan_list:
            if nyan_collisionDetection() == True:
                nyan_bonus = nyan_bonus + 50
                nyan_list.remove(nyan)

    # score and "game over" message variables

        score = (frame_count // frame_rate) + nyan_bonus
        font = pygame.font.SysFont('Stencil', 25, False, False)
        font2 = pygame.font.SysFont('Stencil', 50, False, False)
        text = font.render("Score:      " + str(score), True, white)
        game_over = font2.render("GAME OVER", True, white)
        play_again = font.render("Play Again? [Y] / [N]", True, white)

    # update screen

        screen.blit(background,[0, 0])

    # drawing objects while game is running

        if life == True:
            for hole in hole_list:
                screen.blit(hole.image, hole.pos)
            for rock in rock_list:
                screen.blit(rock.image, rock.pos)
            for nyan in nyan_list:
                screen.blit(nyan.image, nyan.pos)
            screen.blit(play.image, pos)
            screen.blit(text, [690, 15])
            play.rect = pygame.mouse.get_pos()

    #update rock and nyan positions

            for rock in rock_list:
                rock.pos[0] = (rock.pos[0] + rock.dx)
                rock.pos[1] = (rock.pos[1] + rock.dy)
            for nyan in nyan_list:
                nyan.pos[0] = (nyan.pos[0] + nyan.dx)
                if (nyan.pos[0] > 1000) or (nyan.pos[0] < (-100)):
                    nyan_list.remove(nyan)

    # random sounds

            music = randint(0, 600)
            if music >= 598:
                fx()

    # creating objects

            if (frame_count % 900 == 0) and (len(rock_list) <= 6):
                rock_create()
            if (frame_count > 0) and (frame_count % 1300) == 0:
                hole_create()
            nyan_count = randint(0, 6000)
            if (nyan_count >= 5997) and score >= 50:
                nyan_create()
            if len(nyan_list) == 1:
                nyan_sound.play()
            else: nyan_sound.stop()

    # speed up rocks!

            for rock in rock_list:
                if frame_count > 0 and frame_count % 1800 == 0:
                    if rock.dx > 0:
                        rock.dx = rock.dx + 1
                    if rock.dx < 0:
                        rock.dx = rock.dx - 1
                    if rock.dy > 0:
                        rock.dy = rock.dy + 1
                    if rock.dy < 0:
                        rock.dy = rock.dy - 1

            frame_count += 1

    #game over! :-(

        else:
            nyan_sound.stop()
            screen.blit(game_over, [330, 275])
            screen.blit(text, [398, 350])
            screen.blit(play_again, [335, 650])
            pressed = pygame.key.get_pressed()
            if pressed[pygame.K_n]:
                break
            elif pressed[pygame.K_y]:
                game()

        pygame.display.update()



    #clock tick

        clock.tick(60)

game()
\$\endgroup\$
12
\$\begingroup\$

Is this good enough to show this as an example of my...skills for very entry level job in programming?

No

At least this was my reaction after reading the first few lines of code: you don't just make one giant blob of a mother of god function that does everything; it is just wrong.

After reading further on, the code feels better than expected. You seems to be missing some basics, though, and some discipline.

Functions

For the most part, your functions are dedicated to a single task, which is a good thing. But you relly so much on the scope to get your variables, it is pretty hard to follow what exactly is going on.

Your collision detection functions, for instance, rely on a global variable being set prior to their call so they can access them. It is extremely error-prone, especially since you're using them quite a few lines after their definition. You should at least pass the object to detect as a parameter. This is even more important because the comparisons are exactly the same, except for the "obstacle" variable.

But most of these functions that acts on existing objects would also be better as methods within a class. Letting inheritance aside for the various obstacles creation, you should at least implement the collision detection something like:

class ItemInSpace(pygame.sprite.Sprite):

    def __init__(self):
        self.image = 0
        self.start_list = []
        self.dx = 0
        self.dy = 0
        self.posx = 0
        self.posy = 0
        self.pos = [self.dx, self.dy]

    def collide(self, x, y):
        if x >= (self.posx - 50) and x <= (self.posx + 50) and y >= (self.posy - 50) and y <= (self.posy + 50):
            return True
        elif (x >= (self.posx - 50)) and (x <= (self.posx + 50)) and (y >= (self.posy)) and (y <= (self.posy + 50)):
            return True
        elif (x >= (self.posx)) and (x <= (self.posx + 50)) and (y >= (self.posy + 50)) and (y <= (self.posy - 50)):
            return True
        elif (x >= (self.posx)) and (x <= (self.posx + 50)) and (y >= (self.posy)) and (y <= (self.posy + 50)):
            return True
        else:
            return False

And you use it like:

for hole in hole_list:
    if hole.collide(pos[0], pos[1]):
        life = False
        explosion.play()
        play.rect = [-100, -100]

for rock in rock_list:
    if rock..collide(pos[0], pos[1]):
        life = False
        explosion.play()
        play.rect = [-100, -100]

for nyan in nyan_list:
    if nyan..collide(pos[0], pos[1]):
        nyan_bonus = nyan_bonus + 50
        nyan_list.remove(nyan)

A few other things to note:

  • conditionals involving booleans don't need an extra equality check: it is redundant and perform unnecessary computations;
  • you use way too much parenthesis in your conditionals;
  • you can combine several comparisons at once in a single statement;
  • you can reduce collide to a single line.

Comparisons

By combining comparisons in collide, you can write it like:

class ItemInSpace(pygame.sprite.Sprite):
    ...
    def collide(self, x, y):
        if (self.posx - 50) <= x <= (self.posx + 50) and (self.posy - 50) <= y <= (self.posy + 50):
            return True
        elif (self.posx - 50) <= x <= (self.posx + 50) and (self.posy) <= y <= (self.posy + 50):
            # if this is true, then the previous conditional is true
            # so this will never be executed
            return True
        elif (self.posx) <= x <= (self.posx + 50) and (self.posy + 50) <= y <= (self.posy - 50):
            # Wait, how can y fullfil this condition? Always false, so never executed
            return True
        elif (self.posx) <= x <= (self.posx + 50) and (self.posy) <= y <= (self.posy + 50):
            # If this is true, then the first condition is also true
            # so this is never executed
            return True
        else:
            return False

Even if we "fixed" the third one to have an acceptable range for y, we can see that the first conditional is sufficient for all the cases. Thus the function can become:

class ItemInSpace(pygame.sprite.Sprite):
    ...
    def collide(self, x, y):
        return (self.posx - 50) <= x <= (self.posx + 50) and (self.posy - 50) <= y <= (self.posy + 50)

Granted that when you move your "rocks" and "nyans" you update their .posx and .posy instead of their .pos.

You can use the same comparisons combining technique to simplify holes creation:

if (pos[0] - 200) < hole.posx < (pos[0] + 200) and (pos[1] - 200) < hole.posy < (pos[1] + 100):
    hole_create()
else:
    hole_sound.play()
    hole_list.append(hole)

However, in this case, it is neater to not rely on recursion to check that the hole is not created too close to the player:

def hole_create(x, y):  # Adding player coordinates as parameter
    hole = ItemInSpace()
    hole.image = pygame.image.load("images/black_hole.jpg").convert()
    hole.image.set_colorkey(black)
    posx, posy = x, y  # bootstrap while loop
    while (x - 200) < posx < (x + 200) and (y - 200) < posy < (y + 100):
        # We were too close, chose another position
        posx = randint(5, 845)  # removing choice here, as you may have notice by using `randint` directly in the next line, it is not necessary
        posy = randint(5, 645)
    hole.posx = posx
    hole.posy = posy
    hole.pos = [posx, posy]
    hole_sound.play()
    hole_list.append(hole)

List comprehensions

When you want to build a list of elements constructed a similar way, you can often use list-comprehensions to do so. In your case, building all the various FX sounds can benefit from it:

sounds = [pygame.mixer.Sound('sounds/s{}.wav'.format(i)) for i in range(1, 15)]

Here the format function is prefered over the more "beginner" form: 'sounds/s' + str(i) + '.wav'.

Constants and magic numbers

There are a whole lot of values that you use without giving a hint about what they could represent. Defining them as constants (at the top of the file, outside any functions) would help a lot in understanding what the code does and why.

Coarse rewriting based on previous observations

# import libraries

import pygame
from random import randint, choice


ROCK_START_VELOCITY = [-4, -3, -2, -1, 1, 2, 3, 4]
NYAN_START_BEFORE = -100
NYAN_START_AFTER = 1000


class Ship(pygame.sprite.Sprite):
    """player ship class"""

    def __init__(self, location):
        pygame.sprite.Sprite.__init__(self)
        Ship.image = pygame.image.load("images/player.png").convert()
        self.image = Ship.image
        self.rect = self.image.get_rect()
        self.rect.topleft = location


class ItemInSpace(pygame.sprite.Sprite):
    def __init__(self):
        self.image = 0
        self.dx = 0
        self.dy = 0
        self.posx = 0
        self.posy = 0

    def collide(self, x, y):
        return (self.posx - 50) <= x <= (self.posx + 50) and (self.posy - 50) <= y <= (self.posy + 50)

    @property
    def pos(self):
        return self.posx, self.posy


def fx(sounds):
    """function for in-game random sounds"""
    ingame_sound = choice(sounds)
    ingame_sound.play()


def rock_create():
    rock = ItemInSpace()
    angle = randint(0, 360)
    rock.image = pygame.image.load("images/rock2.png").convert()
    rock.image.set_colorkey(black)
    rock.image = pygame.transform.rotate(rock.image, angle)
    rock.posx = choice([randint(0, 50), randint(800, 850)])
    rock.posy = randint(0, 700)
    rock.dx = choice(ROCK_START_VELOCITY)
    rock.dy = choice(ROCK_START_VELOCITY)
    return rock


def hole_create(x, y):
    hole = ItemInSpace()
    hole.image = pygame.image.load("images/black_hole.jpg").convert()
    hole.image.set_colorkey(black)
    posx, posy = x, y  # bootstrap while loop
    while (x - 200) < posx < (x + 200) and (y - 200) < posy < (y + 100):
        # We were too close, chose another position
        posx = randint(5, 845)
        posy = randint(5, 645)
    hole.posx = posx
    hole.posy = posy
    return hole


def nyan_create():
    nyan = ItemInSpace()
    nyan.image = pygame.image.load("images/nyan.png").convert()
    nyan.image.set_colorkey(black)
    nyan.posx = choice([NYAN_START_BEFORE, NYAN_START_AFTER])
    nyan.posy = randint(5, 645)
    if nyan.posx == NYAN_START_BEFORE:
        nyan.dx = 6
    else:
        nyan.dx = -6
        nyan.image = pygame.transform.flip(nyan.image, True, False)
    return nyan


def game():
    # starting pygame
    pygame.init()

    # screen
    size = (900, 700)
    screen = pygame.display.set_mode(size)
    pygame.display.set_caption("SPACEMAN")
    background = pygame.image.load("images/background2.jpg").convert()

    finish= False

    # clock and colors
    clock = pygame.time.Clock()
    black = (0, 0, 0)
    white = (255, 255, 255)

    #loading sounds
    explosion = pygame.mixer.Sound('sounds/Explosion.wav')
    rock_sound = pygame.mixer.Sound('sounds/rock.wav')
    hole_sound = pygame.mixer.Sound('sounds/hole.wav')
    nyan_sound = pygame.mixer.Sound('sounds/nyan2.wav')
    sounds = [pygame.mixer.Sound('sounds/s{}.wav'.format(i)) for i in range(1, 15)]
    main_sound = pygame.mixer.Sound('sounds/game.wav')
    main_sound.play(-1)

    # let's hide mouse!
    pygame.mouse.set_visible(False)

    # score variables
    frame_count = 0
    frame_rate = 30

    # player variables and object lists
    play = Ship([0, 0])
    life = True
    Ship.image.set_colorkey(black)
    rock_list = []
    hole_list = []
    nyan_object = None
    nyan_bonus = 0

    pygame.mouse.set_pos([450, 325])

    # main loop
    while not finish:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                finish = True

        # borders for player ship
        player_x = play.rect.x
        player_y = play.rect.y

        player_x = min(player_x, 850)
        player_y = min(player_y, 660)
        player_y = max(player_y, -10)

        #borders for rocks
        for rock in rock_list:
            if rock.posx >= 900 and rock.dx > 0:
                rock.posx = -35
            if rock.posx <= -35 and rock.dx < 0:
                rock.posx = 900
            if rock.posy >= 700 and rock.dy > 0:
                rock.posy = -35
            if rock.posy <= -35 and rock.dy < 0:
                rock.posy = 700
            # Checking collisions at the same time
            if rock.collide(player_x, player_y):
                life = False
                explosion.play()
                play.rect = [-100, -100]


        # checking collisions
        for hole in hole_list:
            if hole.collide(player_x, player_y):
                life = False
                explosion.play()
                play.rect = [-100, -100]

        if nyan_object is not None and nyan_object.collide(player_x, player_y):
            nyan_bonus += 50
            nyan_object = None
            nyan_sound.stop()

        # score and "game over" message variables
        score = (frame_count // frame_rate) + nyan_bonus
        font = pygame.font.SysFont('Stencil', 25, False, False)
        font2 = pygame.font.SysFont('Stencil', 50, False, False)
        text = font.render("Score:      " + str(score), True, white)
        game_over = font2.render("GAME OVER", True, white)
        play_again = font.render("Play Again? [Y] / [N]", True, white)

        # update screen
        screen.blit(background,[0, 0])

        # drawing objects while game is running
        if life:
            for hole in hole_list:
                screen.blit(hole.image, hole.pos)
            for rock in rock_list:
                screen.blit(rock.image, rock.pos)
            if nyan_object is not None:
                screen.blit(nyan_object.image, nyan_object.pos)
            screen.blit(play.image, (player_x, player_y))
            screen.blit(text, [690, 15])
            play.rect = pygame.mouse.get_pos()

            #update rock and nyan positions
            for rock in rock_list:
                rock.posx += rock.dx
                rock.posy += rock.dy
                # Speed up
                if frame_count and frame_count % 1800 == 0:
                    if rock.dx > 0:
                        rock.dx += 1
                    if rock.dx < 0:
                        rock.dx -= 1
                    if rock.dy > 0:
                        rock.dy += 1
                    if rock.dy < 0:
                        rock.dy -= 1

            if nyan_object is not None:
                nyan_object.posx += nyan_object.dx
                if nyan_objec.posx > NYAN_START_AFTER or nyan_object.dx < NYAN_START_BEFORE:
                    nyan_object = None
                    nyan_sound.stop()

            # random sounds
            if randint(0, 600) >= 598:
                fx(sounds)

            # creating objects
            if frame_count % 900 == 0 and len(rock_list) <= 6:
                rock_list.append(rock_create())
                rock_sound.play()

            if frame_count and frame_count % 1300 == 0:
                hole_list.append(hole_create(player_x, player_y))
                hole_sound.play()

            if randint(0, 6000) >= 5997 and score >= 50 and nyan_object is not None:
                nyan_object = nyan_create()
                nyan_sound.play()

            frame_count += 1

        #game over! :-(
        else:
            nyan_sound.stop()
            screen.blit(game_over, [330, 275])
            screen.blit(text, [398, 350])
            screen.blit(play_again, [335, 650])
            pressed = pygame.key.get_pressed()
            if pressed[pygame.K_n]:
                break
            elif pressed[pygame.K_y]:
                game()

        pygame.display.update()

        #clock tick
        clock.tick(60)


if __name__ == '__main__':
    game()

I could also have talked about how bad design the recursive call to game() is. But this answer is already pretty long, so I may let that to someone else.

\$\endgroup\$
3
\$\begingroup\$

Quite a bit of your code is reimplementing features already supported by the pygame module natively. For example (expanding from Mathias Ettinger's answer), you could use pygame.Rects instead of storing the positions and manually implementing your own collision detection, making the ItemInSpace class be:

class ItemInSpace(pygame.sprite.Sprite):
    def __init__(self):
        self.image = 0
        self.dx = 0
        self.dy = 0
        self.rect = pygame.Rect(0, 0, 100, 100)

    def collide(self, x, y):
        return self.rect.collidepoint((x,y))

    @property
    def pos(self):
        return self.rect.center

and the, in the *_create functions, set sprite.rect.center instead of sprite.posx and sprite.posy.

Another opportunity to use rects for collision detection is checking whether a hole trying to be created is too close to the player, where you could do:

around_player = pygame.Rect(0, 0, 400, 300)
around_player.center = (player_x, player_y - 50)
if around_player.collidepoint(posx, posy):
   [hole is too close]


Independently of using rects, your collision detection code could also be simplified by using sprite groups instead of lists to store your obstacles. Instead of doing:

rock_list = []
hole_list = []

, you could make a group:

obstacles = pygame.sprite.Group()

and add the obstacles with obstacles.add(*_create()).

This would then allow you to simplify the collision detection to pygame.sprite.spritecollide(ship, obstacles, False) and the drawing code to obstacles.draw(surface)

In addition, you could add an update method to the ItemInSpace class:

def update(self):
     self.rect.move(self.dx, self.dy)

and shorten the movement of the objects to obstacles.update().


Full code:

# import libraries

import pygame
from random import randint, choice


ROCK_START_VELOCITY = [-4, -3, -2, -1, 1, 2, 3, 4]
NYAN_START_BEFORE = -100
NYAN_START_AFTER = 1000


class Ship(pygame.sprite.Sprite):
    """player ship class"""

    def __init__(self, location):
        pygame.sprite.Sprite.__init__(self)
        Ship.image = pygame.image.load("images/player.png").convert()
        self.image = Ship.image
        self.rect = self.image.get_rect()
        self.rect.topleft = location


 class ItemInSpace(pygame.sprite.Sprite):
    def __init__(self):
        self.image = 0
        self.dx = 0
        self.dy = 0
        self.rect = pygame.Rect(0, 0, 100, 100)

    def collide(self, x, y):
        return self.rect.collidepoint((x,y))

    @property
    def pos(self):
        return self.rect.center

    def update(self, frame_count):
        self.rect.top = (self.rect.top + 35) % 935 - 35
        self.rect.left = (self.rect.left + 35) % 935 - 35
        self.rect.move_ip(self.dx, self.dy)
        # Speed up
        if frame_count and frame_count % 1800 == 0:
            if self.dx > 0:
               self.dx += 1
            if self.dx < 0:
               self.dx -= 1
            if self.dy > 0:
               self.dy += 1
            if self.dy < 0:
               self.dy -= 1
def fx(sounds):
    """function for in-game random sounds"""
    ingame_sound = choice(sounds)
    ingame_sound.play()


def rock_create():
    rock = ItemInSpace()
    angle = randint(0, 360)
    rock.image = pygame.image.load("images/rock2.png").convert()
    rock.image.set_colorkey(black)
    rock.image = pygame.transform.rotate(rock.image, angle)
    rock.rect.topleft = choice([randint(0, 50), randint(800, 850)]),
                        randint(0, 700)
    rock.dx = choice(ROCK_START_VELOCITY)
    rock.dy = choice(ROCK_START_VELOCITY)
    return rock


def hole_create(x, y):
    around_player = pygame.Rect(0, 0, 400, 300)
    around_player.center = (x, y - 50)
    hole = ItemInSpace()
    hole.image = pygame.image.load("images/black_hole.jpg").convert()
    hole.image.set_colorkey(black)
    posx, posy = x, y  # bootstrap while loop
    while around_player.collidepoint(posx, posy):
        posx = randint(5, 845)
        posy = randint(5, 645)
    hole.posx = posx
    hole.posy = posy
    return hole


def nyan_create():
    nyan = ItemInSpace()
    nyan.image = pygame.image.load("images/nyan.png").convert()
    nyan.image.set_colorkey(black)
    nyan.posx = choice([NYAN_START_BEFORE, NYAN_START_AFTER])
    nyan.posy = randint(5, 645)
    if nyan.posx == NYAN_START_BEFORE:
        nyan.dx = 6
    else:
        nyan.dx = -6
        nyan.image = pygame.transform.flip(nyan.image, True, False)
    return nyan


def game():
    # starting pygame
    pygame.init()

    # screen
    size = (900, 700)
    screen = pygame.display.set_mode(size)
    pygame.display.set_caption("SPACEMAN")
    background = pygame.image.load("images/background2.jpg").convert()

    finish= False

    # clock and colors
    clock = pygame.time.Clock()
    black = (0, 0, 0)
    white = (255, 255, 255)

    #loading sounds
    explosion = pygame.mixer.Sound('sounds/Explosion.wav')
    rock_sound = pygame.mixer.Sound('sounds/rock.wav')
    hole_sound = pygame.mixer.Sound('sounds/hole.wav')
    nyan_sound = pygame.mixer.Sound('sounds/nyan2.wav')
    sounds = [pygame.mixer.Sound('sounds/s{}.wav'.format(i)) for i in range(1, 15)]
    main_sound = pygame.mixer.Sound('sounds/game.wav')
    main_sound.play(-1)

    # let's hide mouse!
    pygame.mouse.set_visible(False)

    # score variables
    frame_count = 0
    frame_rate = 30

    # player variables and object lists
    play = Ship([0, 0])
    life = True
    Ship.image.set_colorkey(black)
    rock_list = []
    obstacles = pygame.sprite.Group()
    nyan_object = None
    nyan_bonus = 0

    pygame.mouse.set_pos([450, 325])

    # main loop
    while not finish:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                finish = True

        # borders for player ship
        player_x = play.rect.x
        player_y = play.rect.y

        player_x = min(player_x, 850)
        player_y = min(player_y, 660)
        player_y = max(player_y, -10)


        # Check collisions
        if pygame.sprite.spritecollide(play, obstacles, False):
            life = False
            explosion.play()
            play.rect = [-100, -100]

        if nyan_object is not None and nyan_object.collide(player_x, player_y):
            nyan_bonus += 50
            nyan_object = None
            nyan_sound.stop()

        # score and "game over" message variables
        score = (frame_count // frame_rate) + nyan_bonus
        font = pygame.font.SysFont('Stencil', 25, False, False)
        font2 = pygame.font.SysFont('Stencil', 50, False, False)
        text = font.render("Score:      " + str(score), True, white)
        game_over = font2.render("GAME OVER", True, white)
        play_again = font.render("Play Again? [Y] / [N]", True, white)

        # update screen
        screen.blit(background,[0, 0])

        # drawing objects while game is running
        if life:
            obstacles.draw(surface)
            if nyan_object is not None:
                screen.blit(nyan_object.image, nyan_object.pos)
            screen.blit(play.image, (player_x, player_y))
            screen.blit(text, [690, 15])
            play.rect = pygame.mouse.get_pos()

            #update rock and nyan positions
            obstacles.update(frame_count)
            if nyan_object is not None:
                nyan_object.rect.left += nyan_object.dx
                if nyan_object.posx > NYAN_START_AFTER or nyan_object.posx < NYAN_START_BEFORE:
                    nyan_object = None
                    nyan_sound.stop()

            # random sounds
            if randint(0, 600) >= 598:
                fx(sounds)

            # creating objects
            if frame_count % 900 == 0 and len(rock_list) <= 6:
                rock_list.append(rock_create())
                obstacles.add(rock_list[-1])
                rock_sound.play()

            if frame_count and frame_count % 1300 == 0:
                obstacles.add(hole_create(player_x, player_y))
                hole_sound.play()

            if randint(0, 6000) >= 5997 and score >= 50 and nyan_object is not None:
                nyan_object = nyan_create()
                nyan_sound.play()

            frame_count += 1

        #game over! :-(
        else:
            nyan_sound.stop()
            screen.blit(game_over, [330, 275])
            screen.blit(text, [398, 350])
            screen.blit(play_again, [335, 650])
            pressed = pygame.key.get_pressed()
            if pressed[pygame.K_n]:
                break
            elif pressed[pygame.K_y]:
                game()

        pygame.display.update()

        #clock tick
        clock.tick(60)


if __name__ == '__main__':
    game()
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.