4

I'm wondering how to speed up the smoothness of my code written in Python using pygam. I'm guessing I have to make this more efficient somehow? When this is run, some balls move around randomly in a set area, however, the new position of each ball is not smooth at all, there is a jump between each movement as the cycle is very slow. How do I fix this? Or is there any suggestions on how to improve it? This is my code so far:

import pygame
from pygame import *
import random
pygame.init()
size = width, height = 800, 600
screen = display.set_mode(size)
pygame.display.set_caption("Year 12: Ideal Gas Simulation")


BLACK = (0, 0, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
WHITE=(255,255,255)
GREEN = (0, 255, 0)
BALLX = 0
BALLY = 1
BALLSPEEDX = 2
BALLSPEEDY = 3
List=[]
radius=5

running=True
myClock=time.Clock()


myClock.tick(60)
def initBall():


        for n in range(40):
            ballx = random.randint(0, 800) # randomly setting the x position
            bally = random.randint(0, 600) # randomly setting the y position
            dirx = random.randint(-5,5)    # randomly setting the x speed
            diry = random.randint(-5,5)    # randomly setting the y speed

            data=[ballx, bally, dirx, diry]
            List.append(data)
            # returning a list with all the data the ball needs
        return List # returning the list


def drawScreen(List):
    draw.rect(screen, WHITE, (0, 0, 800, 600))
    for x in range(40):
        BALLX=List[x][0]
        BALLY=List[x][1]
        draw.circle(screen, GREEN, (BALLX,BALLY),radius)
        display.flip()
        pygame.draw.rect(screen, BLACK, (100-radius,100-radius,600+(2*radius),400+(2*radius)), 1)

        f=pygame.font.SysFont(None,60)
        text=f.render("PV=nRT",True,(0,0,0))
        screen.blit(text,(300,height/20))


def moveBall(List):
    for x in range(40):
        BALLX=List[x][0]
        BALLY=List[x][1]
        SPEEDX=List[x][2]#####data[BALLX]== the first index of each list [x][0]
        SPEEDY=List[x][3]##data[BALLSPEEDX]= List[x][2]
        age=SPEEDX+BALLX
        List[x][0]=age
         # increases the position of the ball
        plus=SPEEDY+BALLY
        List[x][1]=plus
    # checks to see if the ball is hitting the walls in the x direction
        if BALLX > 700:
            List[x][0] = 700#NORMALLY 800
            third=List[x][2]
            answer=third*-1
            List[x][2]=answer
        elif BALLX < 100:#NORMALLY 0
            List[x][0] = 100
            third=List[x][2]
            answer=third*-1
            List[x][2]=answer

    # checks to see if the ball is hitting the walls in the y direction
        if BALLY < 100:
            List[x][1] = 100#NORMALLY 0
            third=List[x][3]
            answer=third*-1
            List[x][3]=answer
        elif BALLY > 500:
            List[x][1] = 500#NORMALLY 600
            third=List[x][3]
            answer=third*-1
            List[x][3]=answer
    return List#return updated list


List=initBall()
while running==True:
    for evnt in event.get():
        if evnt.type==QUIT:
            running=False
            quit()
        if evnt.type==MOUSEBUTTONDOWN:
            mx,my=evnt.pos
            button=evnt.button

    drawScreen(List)
    List=moveBall(List)
ash ash
  • 71
  • 1
  • 7
  • 1
    you would be more comfortable by replacing `for x in range(40)` by `for ball in List:` and then call `ball[0]`, `ball[1]`... everywhere – PRMoureu Jul 29 '17 at 15:00
  • You can post your code on http://codereview.stackexchange.com/ to get more tips. – skrx Jul 29 '17 at 15:16
  • Please post your code on https://codereview.stackexchange.com/. There are a lot of things that could be improved in your example as well as in the accepted answer. – skrx Jul 30 '17 at 16:15

2 Answers2

2

Call pygame.display.flip() only once per frame.

def drawScreen(List):
    draw.rect(screen, WHITE, (0, 0, 800, 600))
    for x in range(40):
        BALLX=List[x][0]
        BALLY=List[x][1]
        draw.circle(screen, GREEN, (BALLX,BALLY),radius)
        # display.flip()  # Don't call `display.flip()` here.
        pygame.draw.rect(screen, BLACK, (100-radius,100-radius,600+(2*radius),400+(2*radius)), 1)

        screen.blit(text,(300,height/20))

    pygame.display.flip()  # Call it here.

I also recommend to use a pygame.time.Clock to limit the frame rate.

# Define the font object as a global constant.
FONT = pygame.font.SysFont(None, 60)
# If the text doesn't change you can also define it here.
TEXT = FONT.render("PV=nRT", True, (0,0,0))
# Instantiate a clock to limit the frame rate.
clock = pygame.time.Clock()
running = True

while running:  # `== True` is not needed.
    for evnt in event.get():
        if evnt.type == QUIT:
            running = False
            # Better use `pygame.quit` and `sys.exit` to quit.
            pygame.quit()
            sys.exit()

    drawScreen(List)
    List = moveBall(List)

    clock.tick(30)  # Limit frame rate to 30 fps.
skrx
  • 19,980
  • 5
  • 34
  • 48
  • 1
    Thank you! It's so much smoother now! – ash ash Jul 29 '17 at 14:55
  • 1
    @ashash you can also avoid to write the text and the frame in each iteration of the loop, you can move the 4 last lines in drawScreen above the loop – PRMoureu Jul 29 '17 at 14:57
  • 1
    @PRMoureu good point. I'll add that to the example. ash ash, do you intend to change the text eventually during the run time or is it supposed to be constant? – skrx Jul 29 '17 at 15:04
2

In addition to skrx's answer, you can also refactor the code and avoid a lot of duplicate calls. Also, indexing the BALLS array directly might improve performance slightly.

Generally, avoid naming variables inside functions with uppercase. These names are typically given to constants defined at the top of your file.

The version I came up with is below:

import array
import pygame
pygame.init()
import random

from pygame import *

size = WIDTH, HEIGHT = 800, 600
screen = display.set_mode(size)
pygame.display.set_caption("Year 12: Ideal Gas Simulation")


BLACK = (0, 0, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
WHITE = (255,255,255)
GREEN = (0, 255, 0)
BALLX = 0
BALLY = 1
BALLSPEEDX = 2
BALLSPEEDY = 3
RADIUS = 5

BALLS = []

myClock = time.Clock()

myClock.tick(60)
def initBalls():
    for n in range(40):
        props = array.array('i', [
            random.randint(0, WIDTH),
            random.randint(0, HEIGHT),
            random.randint(-5, 5),
            random.randint(-5, 5),
        ])
        BALLS.append(props)


def drawScreen():
    draw.rect(screen, WHITE, (0, 0, 800, 600))
    props = (100-RADIUS, 100-RADIUS, 600+(2*RADIUS), 400+(2*RADIUS))
    pygame.draw.rect(screen, BLACK, props, 1)
    f = pygame.font.SysFont(None, 60)
    text = f.render("PV=nRT", True,(0, 0, 0))
    screen.blit(text,(300, HEIGHT / 20))
    for i in range(len(BALLS)):
        draw.circle(screen, GREEN, BALLS[i][:2],RADIUS)
    display.flip()


def moveBalls():
    for i in range(len(BALLS)):

        if BALLS[i][0] > 700:
            BALLS[i][0] = 700
            BALLS[i][2] *= -1
        elif BALLS[i][0] < 100:
            BALLS[i][0] = 100
            BALLS[i][2] *= -1
        else:
            BALLS[i][0] += BALLS[i][2]

        if BALLS[i][1] < 100:
            BALLS[i][1] = 100
            BALLS[i][3] *= -1
        elif BALLS[i][1] > 500:
            BALLS[i][1] = 500
            BALLS[i][3] *= -1
        else:
            BALLS[i][1] += BALLS[i][3]


def main():
    initBalls()
    while True:
        for evnt in event.get():
            if evnt.type == QUIT:
                pygame.quit()
                return
            elif evnt.type == MOUSEBUTTONDOWN:
                mx, my = evnt.pos
                button = evnt.button

        drawScreen()
        moveBalls()


if __name__ == "__main__":
    main()
fsimkovic
  • 1,078
  • 2
  • 9
  • 21