Code Review - Pong

Started by
2 comments, last by jHaskell 8 years, 8 months ago

So I made Pong. It was a good experience. I learned a lot, but questions were raised I had trouble finding answers to.

Code (Python):


#   --- Imports-Start ---

import pygame; import random

pygame.init() # Initiate pygame

#   --- Imports-End ---
#   --- Globals-Start ---

# Display settings
displayDim = [640, 480]
display = pygame.display.set_mode((displayDim))
pygame.display.set_caption('pong.py')

# Color(s)
WHITE = (255,   255,    255)
BLACK = (0,     0,      0)
RED =   (255,   0,      0)
GREEN = (0,     155,    0)
BLUE =  (110,   170,    255)
GRAY =  (230,   230,    240)

# Time
FPS = 30
clock = pygame.time.Clock()

# Fonts
fontBlocky = "C:\\Users\\rabbitrabbit\\CodeBS\\Pong\\blocky.ttf"
fontSmooth = "C:\\Users\\rabbitrabbit\\CodeBS\\Pong\\smooth.ttf"


# Sprite group(s)
allSprites = pygame.sprite.LayeredUpdates()

# Collision groups
ballGroup = pygame.sprite.Group()
paddleGroup = pygame.sprite.Group()

#   --- Globals-End ---
#   --- Classes-Start ---

class Block(pygame.sprite.Sprite):

    def __init__(self, width, height, color, x, y):
        # Makes a surface, fills it, and adds to allSprites group
        super().__init__()

        # Set up image
        self.image = pygame.Surface((width, height))
        self.image.fill(color)

        # Set up surface
        self.rect = self.image.get_rect()

        # Position and center
        self.rect.x = x
        self.rect.y = y
        
        self.rect.center = (self.rect.x, self.rect.y)

        # Add to sprite group(s)
        allSprites.add(self)

class Paddle(Block):
    '''Class representing a player. Also contains the score attribute(s),
 but that's just for convenience, score code isn't actually handled within
 this class.'''

    # Determines whether paddles is moving or not, and how fast
    yChange = 0

    def update(self):
        # Takes the instance's yChange variable and uses it to move a paddle.
        self.rect.y += self.yChange

        # Keep paddle within boundaries.

        if self.rect.y < 175:
            pass
        elif self.rect.y > 325:
            pass

class Ball(Block):
    '''Class representing the ball. All the game's collisions are
handled here'''
    
    # Variables that determine the ball's speed
    xChange = 0
    yChange = 0

    def __init__(self, width, height, color, x, y):

        # Call's base class' constructor
        super().__init__(width, height, color, x, y)
        
        # Chooses the ball's direction at start and sends it off
        
        self.xChange = random.getrandbits(1)
        self.yChange = random.getrandbits(1)

        # Sets x/yChange to 2 or -2
        self.xChange = (self.xChange * 2 - 1) * 2
        self.yChange = (self.yChange * 2 - 1) * 2

        # Add to ballgroup for updates
        ballGroup.add(self)

    def update(self):
        # Makes the ball move according to direction, and checks for collisons

        self.rect.x += self.xChange
        self.rect.y += self.yChange

        # Check for walls
        if self.rect.y <= 176 or self.rect.y >= 409:
            self.yChange = self.yChange * -1

        # Check for paddles - spritecollide returns a list of all the sprites
        # in paddleGroup that collide with the ball
        spritesCollided = pygame.sprite.spritecollide(self, paddleGroup, False)

        if len(spritesCollided) != 0:
            self.xChange = self.xChange * -1 # Change the ball's direction

            # Increases ball's speed a little bit. Checks whether ball
            # x&yChange values are positive or negative to prevent accidentally
            # lowering the ball's speed instead of raising it.
            # Ball speed caps at 15. If it's any higher than 16 the ball would
            # fly through paddles.

            if self.xChange != 15 or self.xChange != -15:
            
                if self.xChange > 0:
                    self.xChange += 1
                elif self.xChange < 0:
                    self.xChange -= 1

                if self.yChange > 0:
                    self.yChange += 1
                elif self.yChange < 0:
                    self.yChange -= 1
            
            spritesCollided = [] # Clear the list

#   --- Classes-End ---
#   --- Functions-Start ---

def text(font, message, size, color, x, y):
    # Set up font
    myFont = pygame.font.Font(font, size)
    text = myFont.render(message, 1, color)
    textRect = text.get_rect()
    textRect.x = x
    textRect.y = y
    
    # Render text
    display.blit(text, [textRect.x, textRect.y])

def main(): # Main game loop
    
    # Variable to keep loop running
    done = False

    # Determines whether or not things are happening
    gameStarted = False

    # Player scores
    p1Score = 0
    p2Score = 0

    # Call instances of classes
    # Background elements
    topStrip = Block(640, 175, BLUE, 320, 88)
    botStrip = Block(640, 55, BLUE, 320, 452)
    #strip = Block(640, 250, WHITE, 320, 300) # Background element

    ball = Ball(16, 16, BLUE, 320, 300) # The bouncy thing

    # They move up and down
    p1 = Paddle(16, 64, BLUE, 580, 305) # Right side
    p2 = Paddle(16, 60, BLUE, 60, 305) # Left side

    # Add paddles to group for collisions
    paddleGroup.add(p1, p2)

    while not done:

        # Events-Start
        for event in pygame.event.get():
            # Quits if you press the little red x
            if event.type == pygame.QUIT:
                done = True

            # Keyboard inputs
            if event.type == pygame.KEYDOWN: # While key is held
                # p1 inputs
                if event.key == pygame.K_UP:
                    p1.yChange = -10
                elif event.key == pygame.K_DOWN:
                    p1.yChange = 10
                # p2 inputs
                if event.key == pygame.K_w:
                    p2.yChange = -10
                elif event.key == pygame.K_s:
                    p2.yChange = 10
            if event.type == pygame.KEYUP: # When the key's released
                # p1 un-inputs(?)
                if event.key == pygame.K_UP:
                    p1.yChange = 0
                elif event.key == pygame.K_DOWN:
                    p1.yChange = 0
                # p2 un-inputs(?)
                if event.key == pygame.K_w:
                      p2.yChange = 0
                elif event.key == pygame.K_s:
                    p2.yChange = 0

        if ball.rect.x <= -16:
            p1Score += 1 # Increases player one's score
            ball.kill() # Gets rid of the ball
            ball = Ball(16, 16, BLUE, 320, 300) # Makes a new one

        elif ball.rect.x >= 640:
            p2Score += 1 # Increases player two's score
            ball.kill() # Gets rid of the ball
            ball = Ball(16, 16, BLUE, 320, 300) # Makes a new one
                
        ballGroup.update()
        paddleGroup.update()

        # Events-End

        # Draw
        display.fill(WHITE)

        # Display the players' scores
        text(fontSmooth, str(p1Score), 100, GRAY, 440, 250)
        text(fontSmooth, str(p2Score), 100, GRAY, 160, 250)

        # Draw objects
        allSprites.draw(display)

        # Display the game's name and stuff
        text(fontBlocky, 'PONG', 105, WHITE, 180, -15)
        text(fontSmooth, 'Made by m(O)x. He is great.', 20, WHITE, 185, 440)

        # Time and update
        clock.tick(FPS)
        pygame.display.update()

    pygame.quit()

#   --- Functions-End ---

# Run

if __name__ == '__main__':
    main()

In case you were wondering about the rectangles, here's what it looks like.

Font was made by Kenney, on OGA.

Questions:

1. I tend to see people calling the update method of sprite groups that contain all of a game's sprites. If I have sprites that don't move (like the instances of the Block class), and I want to do this, would I just make an update method that passes whenever it's called?

2. Is there a way to not have all the input stuff in my main loop?

3. Is it considered bad form to have a lot of stuff in my main loop?

4. How can I add text to the allSprites group, so I don't have to render it strategically before or after calling the group's draw method?

5.How can I tell how fast my code is running aside from whether it lags or not?

6. How can I have multiple hitboxes on a single object, so the game doesn't glitch out when the ball hits the top/bottom of the paddle?

7. Is it just me, or is the ball throbbing?

Any input is appreciated.

Advertisement


1. I tend to see people calling the update method of sprite groups that contain all of a game's sprites. If I have sprites that don't move (like the instances of the Block class), and I want to do this, would I just make an update method that passes whenever it's called?

So long as you don't have thousands of static objects, it's perfectly fine to have empty update methods that don't do anything when they are called. The performance hit you take doing that is negligible until you start doing it thousands of times per frame.


2. Is there a way to not have all the input stuff in my main loop?

Sure. First step is have some type of 'GetInput' method. Second step is to separate game state changes from input processing. All those yChange lines should be in your Paddle update method. Input processing should only set an associated object variable indicating the action to be performed.


3. Is it considered bad form to have a lot of stuff in my main loop?

It's considered bad form to have a lot of stuff in any single method/function. For main, your init code, Input code, Update Code, and Draw code should all be factored into separate method calls. Timing code can remain in main. Some people even go a step further and factor all of that code into a RunGame method so that main just has the one line calling RunGame.


5.How can I tell how fast my code is running aside from whether it lags or not?

pygame.time.get_ticks() returns the number of milliseconds since pygame.init() was called. So call get_ticks before executing the code you want to time, call get_ticks after executing that code, subtract the first time from the second time, and you have how many milliseconds it took to execute that code.

There's just one caveat. The resolution of get_ticks() isn't guaranteed to be 1 millisecond. It may be 10. It may be more. TIMER_RESOLUTION is a constant that tells you the resolution. If it's in the 10+ range, it isn't going to be very good for timing small portions of the game loop that are likely to only take a few milliseconds to run, or less.

https://www.pygame.org/docs/ref/time.html

http://stackoverflow.com/questions/1938048/high-precision-clock-in-python


6. How can I have multiple hitboxes on a single object, so the game doesn't glitch out when the ball hits the top/bottom of the paddle?

Not really sure what you mean by the ball glitching out when it hits the top/bottom of the paddle, but it shouldn't require multipel hitboxes, just additional processing to determine where the ball hit the paddle, and then conditional processing based on where the hit occurred.


7. Is it just me, or is the ball throbbing?

Doesn't appear to be throbbing in the picture you posted. ;)


m(O)x, on 27 Jul 2015 - 2:42 PM, said:


6. How can I have multiple hitboxes on a single object, so the game doesn't glitch out when the ball hits the top/bottom of the paddle?



Not really sure what you mean by the ball glitching out when it hits the top/bottom of the paddle, but it shouldn't require multipel hitboxes, just additional processing to determine where the ball hit the paddle, and then conditional processing based on where the hit occurred.

Since whenever the ball touches the paddle, only its horizontal direction is reversed, when you move the paddle up/down onto the ball, it'll just sort of flicker as long as the paddle's on top of it. I guess the game doesn't actually glitch out, it just looks really weird.

How would I determine where the ball hit the paddle? Do I just write the numbers in, like for the top of the paddle: paddle.rect.y + 30?


Since whenever the ball touches the paddle, only its horizontal direction is reversed, when you move the paddle up/down onto the ball, it'll just sort of flicker as long as the paddle's on top of it.

The simplest resolution is to add a second check for the direction the ball is moving, and if it's moving towards the paddle, then reverse the direction. If it's already moving away from the paddle, you shouldn't reverse the direction again.

This topic is closed to new replies.

Advertisement