Search code examples
pythongame-physicsturtle-graphicspython-turtlepong

Python Ping-Pong game, the ball speed randomly changes during paddle movement


I started off with learning python and tried following tutorial on how to make a pong game in python. The speed of the ball in my game fluctuates somehow and is not consistent, especially when I'm moving either of the paddles using W or S and the arrow keys, the speed changes and causes inconvenience. I've checked the code many times for bugs but I'm unable to figure it out. Here's my code.

import turtle

win = turtle.Screen()
win.title("Pong by killkennyale")
win.bgcolor("black")
win.setup(width=800, height=600)
win.tracer(0)

# Score
score_a = 0
score_b = 0


# Paddle A
paddle_a = turtle.Turtle()
paddle_a.speed(0) #speed of animation of paddle
paddle_a.shape("square")
paddle_a.shapesize(stretch_wid=5, stretch_len=1) 
paddle_a.color("white")
paddle_a.penup() #turtle draws lines, we don't want that duh
paddle_a.goto(-350,0) #coordinates for turtle-paddle


# Paddle B
paddle_b = turtle.Turtle()
paddle_b.speed(0) #speed of animation of paddle
paddle_b.shape("square")
paddle_b.shapesize(stretch_wid=5, stretch_len=1) 
paddle_b.color("white")
paddle_b.penup() #turtle draws lines, we don't want that duh
paddle_b.goto(350,0) #coordinates for turtle-paddle


# Ball
ball = turtle.Turtle()
ball.speed(0) #speed of animation of ball
ball.shape("circle")
ball.shapesize(stretch_wid=1, stretch_len=1) 
ball.color("white")
ball.penup() #turtle draws lines, we don't want that duh
ball.goto(0,0) #coordinates for turtle-ball
ball.dx = 0.5 #speed of x
ball.dy = 0.5 #speed of y

# Pen
pen = turtle.Turtle()
pen.speed(0)
pen.color("white")
pen.penup()
pen.hideturtle() # we don't need to see it
pen.goto(0, 260)
pen.write("Player A: {} Player B: {}".format(score_a, score_b), align="center", font=("Courier", 16, "normal"))

# now we gotta make the paddle move using a keyboard

# Function
def paddle_a_up():
    y = paddle_a.ycor() #assign y-coordinate to variable y
    y = y + 20 #add 20 pixels to y-coordinate
    paddle_a.sety(y)

def paddle_a_down():
    y = paddle_a.ycor() #assign y-coordinate to variable y
    y = y - 20 #add 20 pixels to y-coordinate
    paddle_a.sety(y)

def paddle_b_up():
    y = paddle_b.ycor() #assign y-coordinate to variable y
    y = y + 20 #add 20 pixels to y-coordinate
    paddle_b.sety(y)

def paddle_b_down():
    y = paddle_b.ycor() #assign y-coordinate to variable y
    y = y - 20 #add 20 pixels to y-coordinate
    paddle_b.sety(y)

# Keyboard Binding
win.listen() # tells the turtle to listen to the keyboard
win.onkeypress(paddle_a_up, "w")
win.onkeypress(paddle_a_down, "s")
# when we press 'w'or's' then it calls paddle_a_up or down and adds or subtracts 20 to the y coordinate
win.onkeypress(paddle_b_up, "Up")
win.onkeypress(paddle_b_down, "Down")
# when we press 'up'or'down' arrow then it calls paddle_b_up or down and adds or subtracts 20 to the y coordinate


#Main Game Loop
while True:
    win.update()

    # Move the ball
    ball.setx(ball.xcor() + ball.dx)
    ball.sety(ball.ycor() + ball.dy)

    # Border Checking
    if ball.ycor() > 290:
        ball.sety(290) # it reaches roof at 300
        ball.dy = ball.dy * -1
   
    if ball.ycor() < -290:
        ball.sety(-290) # it reaches roof at 300
        ball.dy = ball.dy * -1
   
    if ball.xcor() > 390:
        ball.goto(0,0)
        ball.dx = ball.dx * -1
        score_a += 1
        pen.clear()
        pen.write("Player A: {} Player B: {}".format(score_a, score_b), align="center", font=("Courier", 16, "normal"))

    if ball.xcor() < -390:
        ball.goto(0,0)
        ball.dx = ball.dx * -1
        score_b += 1
        pen.clear()
        pen.write("Player A: {} Player B: {}".format(score_a, score_b), align="center", font=("Courier", 16, "normal"))

    # Paddle and ball collisions
    if (ball.xcor() > 340 and ball.xcor() < 350) and (ball.ycor() < paddle_b.ycor() + 40 and ball.ycor() > paddle_b.ycor() - 40):
        ball.setx(340)
        ball.dx *= -1

    if (ball.xcor() < -340 and ball.xcor() > -350) and (ball.ycor() < paddle_a.ycor() + 40 and ball.ycor() > paddle_a.ycor() - 40):
        ball.setx(-340)
        ball.dx *= -1


Solution

  • You need a main/game/animation loop that is set up in a way that moves things forward at a constant speed, instead of at a variable speed as you have it currently.

    With a while loop set up as in your example, the next iteration of the loop will be called immediately after the previous iteration, and each iteration can take a variable amount of time to complete. But your movement amount for the ball and paddles are fixed, and that's why you have a problem. For example, it might take 1ms for the first frame / iteration of the loop, but 7ms for the second frame / iteration because a key was pushed and more calculation is consequently done by the computer. So in frame 1 the ball would move dx and dy in just 1ms, but then in the next frame it takes the ball 7ms to move only dx and dy again — in other words, your ball/paddle movement is therefore variable, as it is happening at a variable number of frames per second instead of a fixed fps.

    So you could tell the computer how often to constantly advance the game/animation, rather than have it advance at a variable speed. (Else, adjust things to that variable speed if you prefer). You could do that through a callback function that runs at a constant framerate — e.g. the frame() function in Pong with Turtle Graphics: Part 2:

    framerate_ms = 40  # Every how many milliseconds must frame function be called?
    
    def frame () :
        screen.ontimer(frame, framerate_ms)  # schedule this function to be called again
        check_if_someone_scores()
        update_paddle_positions()
        update_ball_position()
        screen.update()                      # display the new frame
    
    frame()                                  # call it manually the first time