3
\$\begingroup\$

I am working on an app: a virtual sandbox of sorts, a lot like Powder Game.

The problem is, when working with an app, you must deal with hardware limitations. Don't get me wrong, the speed isn't like 2 FPS or anything, but I would just like for someone to look over the code and point out any spots that could use optimization, to speed things up. I just want max performance for the first stable build, and I might learn something.

I am using pgs4a to package it for Android.

This code is a bit lengthy, so if you'd like for me to post it as a zip file, just let me know.

import pygame
from random import choice
from pygame.locals import *

try:
    import android
except ImportError:
    android = None

pygame.init()
end=0

if android:
    android.init()
    android.map_key(android.KEYCODE_BACK, pygame.K_ESCAPE)
menuscreen=pygame.display.set_mode((480,320))
done=0
phone=pygame.image.load('phone.bmp').convert()
tablet=pygame.image.load('tablet.bmp').convert()
prect=pygame.Rect(70,50,117,41)
trect=pygame.Rect(70,180,117,41)
stype=1
while done==0:
    menuscreen.fill((0,0,0))
    menuscreen.blit(phone,(70,50))
    menuscreen.blit(tablet,(70,180))
    if android:
        if android.check_pause():
            android.wait_for_resume()
    for e in pygame.event.get():
        if e.type==MOUSEBUTTONDOWN:
            if prect.collidepoint(pygame.mouse.get_pos()[0],pygame.mouse.get_pos()[1]):
                stype=1
                done=1
            elif trect.collidepoint(pygame.mouse.get_pos()[0],pygame.mouse.get_pos()[1]):
                stype=2
                done=1
        if not android:
            if e.type==QUIT:
                end=1
                done=1
        if e.type == pygame.KEYDOWN and e.key == pygame.K_ESCAPE:
            end=1
            done=1
    pygame.display.flip()
if stype==1:
    screen = pygame.display.set_mode((480,320))
    partdraw=pygame.Surface((480,320))
else:
    screen = pygame.display.set_mode((800,480))
    partdraw=pygame.Surface((800,480))
def DrawPart(x,y,col):
    pygame.draw.rect(partdraw, col, (x,y,8,8), 0)

class PlantPart(object):
    def __init__(self,x,y):
        self.x=x
        self.y=y
        self.rect=pygame.Rect(self.x,self.y,8,8)
        self.color=(0,203,0)
        self.gone=0
    def update(self):
        self.rect=pygame.Rect(self.x,self.y,8,8)
        if self.y<screen.get_height()-8:
            if screen.get_at((self.x,self.y+8)) == (0,0,0,255) or screen.get_at((self.x,self.y+8)) == (0,0,255,255):          
                self.y+=8
        if self.gone==0:
            if self.x>0 and self.x<screen.get_width()-8:
                if screen.get_at((self.x-1,self.y))  == (255,0,0,255) or screen.get_at((self.x+8,self.y)) == (255,0,0,255):
                    self.gone='yes'
                    sand.remove(self)
        if self.y>0 and self.y<screen.get_height()-8:
            if screen.get_at((self.x,self.y+8))  == (255,0,0,255) or screen.get_at((self.x,self.y-1)) == (255,0,0,255):
                if self.gone==0:
                    self.gone=1
                    sand.remove(self)
class SandPart(object):
    def __init__(self,x,y):
        self.x=x
        self.y=y
        self.rect=pygame.Rect(self.x,self.y,8,8)
        self.color=(200,180,0)
    def update(self):
        self.rect=pygame.Rect(self.x,self.y,8,8)
        if self.y<screen.get_height()-8:
            if screen.get_at((self.x,self.y+8)) == (0,0,0,255) or screen.get_at((self.x,self.y+8)) == (0,0,255,255):          
                self.y+=8
class WaterPart(object):
    def __init__(self,x,y):
        self.x=x
        self.y=y
        self.rect=pygame.Rect(self.x,self.y,8,8)
        self.color=(0,0,255)
        self.gone=0
    def update(self):
        self.rect=pygame.Rect(self.x,self.y,8,8)
        do=choice([1,2,1,2,1,2,2,1])
        if self.x>0 and self.x<screen.get_width()-8:
            if screen.get_at((self.x-1,self.y))  == (255,0,0,255) or screen.get_at((self.x+8,self.y)) == (255,0,0,255):
                self.gone=1
                sand.remove(self)
            if screen.get_at((self.x-1,self.y))  == (0,203,0,255) or screen.get_at((self.x+8,self.y)) == (0,203,0,255):
                if self.gone==0:
                    sand.remove(self)
                    self.gone=1
                    sand.append(PlantPart(self.x,self.y))
        if self.y>0 and self.y<screen.get_height()-8:
            if screen.get_at((self.x,self.y+8))  == (255,0,0,255):
                if self.gone==0:
                    self.gone=1
                    sand.remove(self)
            if screen.get_at((self.x,self.y+8))  == (0,203,0,255):
                if self.gone==0:
                    sand.remove(self)
                    sand.append(PlantPart(self.x,self.y))
        if self.gone==0:
            if self.y>0:
                if screen.get_at((self.x,self.y-1)) == (255,0,0,255) or screen.get_at((self.x,self.y-1)) == (200,180,0,255):
                    sand.remove(self)        
        if self.y<screen.get_height()-8:
            try:
                if screen.get_at((self.x+4,self.y+8)) == (0,0,0,255):
                    self.y+=8
                else:
                    if do==1:
                        if screen.get_at((self.x+9,self.y+3)) == (0,0,0,255):
                            self.x+=8
                    elif screen.get_at((self.x-5,self.y+3)) == (0,0,0,255):
                        self.x-=8
            except:
                pass
class LavaPart(object):
    def __init__(self,x,y):
        self.x=x
        self.y=y
        self.rect=pygame.Rect(self.x,self.y,8,8)
        self.color=(255,0,0)
        self.gone=0
    def update(self):
        self.rect=pygame.Rect(self.x,self.y,8,8)
        do=choice([1,2,1,2,1,2,2,1])
        if self.x>0 and self.x<screen.get_width()-8:
            if screen.get_at((self.x-1,self.y))  == (0,0,255,255) or screen.get_at((self.x+8,self.y)) == (0,0,255,255):
                self.gone=1
                sand.remove(self)
        if self.y<screen.get_height()-8:
            if screen.get_at((self.x,self.y+8))  == (0,0,255,255):
                if self.gone==0:
                    sand.remove(self)
        if self.gone==0:
            if self.y>0:
                if screen.get_at((self.x,self.y-1)) == (0,0,255,255) or screen.get_at((self.x,self.y-1)) == (200,180,0,255):
                    sand.remove(self)
        if self.y<screen.get_height()-8:
            try:
                if screen.get_at((self.x+4,self.y+8)) == (0,0,0,255):
                    self.y+=8
                else:
                    if do==1:
                        if screen.get_at((self.x+9,self.y+3)) == (0,0,0,255):
                            self.x+=8
                    elif screen.get_at((self.x-5,self.y+3)) == (0,0,0,255):
                        self.x-=8
            except:
                pass
def main():
    global end
    global sand
    while True:
        menu=0
        clock=pygame.time.Clock()
        sand=[]
        menurect=pygame.Rect(20,15,117,41)
        img='clear.bmp'
        image1=pygame.image.load(img).convert()
        clearrect=pygame.Rect(screen.get_width()-260,15,117,41)
        img='menu.bmp'
        image2=pygame.image.load(img).convert()
        backrect=pygame.Rect(screen.get_width()-137,15,117,41)
        img='back.bmp'
        image3=pygame.image.load(img).convert()
        sel='SandPart'
        menuitems=[pygame.Rect(25,60,32,32),pygame.Rect(25,120,32,32),pygame.Rect(25,180,32,32),pygame.Rect(25,240,32,32)]
        while end==0:
            screen.fill((0,0,0))
            mse=pygame.mouse.get_pos()
            screen.blit(partdraw,(0,0))
            partdraw.fill((0,0,0))
            screen.blit(image2,(20,15))
            pygame.draw.rect(screen, (0,0,0), (-480,0,480,800), 0)
            eraserect=pygame.Rect(mse[0]-6,mse[1]-6,16,16)
            for s in sand:
                DrawPart(s.x,s.y,s.color)
                s.update()
            if pygame.mouse.get_pressed()==(1,0,0):
                if menurect.collidepoint(mse[0],mse[1]):
                    if android:
                        android.vibrate(0.2)
                    menu=1
                    while menu==1:
                        screen.fill((0,0,0))
                        screen.blit(image3,(screen.get_width()-137,15))
                        screen.blit(image1,(screen.get_width()-260,15))
                        mse=pygame.mouse.get_pos()
                        if pygame.mouse.get_pressed()==(1,0,0):
                            if clearrect.collidepoint(mse[0],mse[1]):
                                sand=[]
                                if android:
                                    android.vibrate(0.1)
                            if backrect.collidepoint(mse[0],mse[1]):
                                if android:
                                    android.vibrate(0.2)
                                menu=0
                        if menu==1:
                            for m in menuitems:
                                if m==menuitems[0]:
                                    if m.collidepoint(mse[0],mse[1]):
                                        if pygame.mouse.get_pressed()==(1,0,0):sel='SandPart'
                                        if android:
                                            android.vibrate(0.1)
                                    if sel is not 'SandPart':
                                        pygame.draw.rect(screen, (200,180,0), m, 1)
                                    else:
                                        pygame.draw.rect(screen, (200,180,0), m, 0)
                                elif m==menuitems[1]:
                                    if m.collidepoint(mse[0],mse[1]):
                                        if android:
                                            android.vibrate(0.1)
                                        if pygame.mouse.get_pressed()==(1,0,0):sel='WaterPart'
                                    if sel is not 'WaterPart':
                                        pygame.draw.rect(screen, (5,5,210), m, 1)
                                    else:
                                        pygame.draw.rect(screen, (5,5,210), m, 0)
                                elif m==menuitems[2]:
                                    if m.collidepoint(mse[0],mse[1]):
                                        if android:
                                            android.vibrate(0.1)
                                        if pygame.mouse.get_pressed()==(1,0,0):sel='PlantPart'
                                    if sel is not 'PlantPart':
                                        pygame.draw.rect(screen, (0,203,0), m, 1)
                                    else:
                                        pygame.draw.rect(screen, (0,203,0), m, 0)
                                elif m==menuitems[3]:
                                    if m.collidepoint(mse[0],mse[1]):
                                        if android:
                                            android.vibrate(0.1)
                                        if pygame.mouse.get_pressed()==(1,0,0):sel='LavaPart'
                                    if sel is not 'LavaPart':
                                        pygame.draw.rect(screen, (255,0,0), m, 1)
                                    else:
                                        pygame.draw.rect(screen, (255,0,0), m, 0)
                            if not any(m.collidepoint(mse[0],mse[1]) for m in menuitems):
                                if pygame.mouse.get_pressed()==(1,0,0):
                                    sel='erase'

                        if android:
                            if android.check_pause():
                                android.wait_for_resume()
                        for e in pygame.event.get():
                            if not android:
                                if e.type==QUIT:
                                    menu=0
                                    end=1
                        if e.type == pygame.KEYDOWN and e.key == pygame.K_ESCAPE:
                            menu=0
                            end=1
                        pygame.display.flip()
                else:
                    if screen.get_at((mse[0],mse[1]))==(0,0,0,255):
                        if sel is not 'erase':
                            sand.append(eval(sel)((mse[0]/8)*8,(mse[1]/8)*8))
                    if sel=='erase':
                        for s in sand:
                            if s.rect.colliderect(eraserect):
                                sand.remove(s)
            if android:
                if android.check_pause():
                    android.wait_for_resume()
            for e in pygame.event.get():
                if not android:
                    if e.type==QUIT:
                        end=1
                if e.type == pygame.KEYDOWN and e.key == pygame.K_ESCAPE:
                    end=1
            clock.tick(65)
            pygame.display.flip()
        break


# This isn't run on Android.
if __name__ == "__main__":
    main()
\$\endgroup\$
1
  • 4
    \$\begingroup\$ The best way to optimize code is to profile it and optimize only the critical parts. Optimizing for the sake of it loses readability and does nothing for performance (I've worked on hi performance programs for 8+ years). So, if you can run a profile on the code and come like: "This part of my code sucks processor, do you know how to make it work faster" 'd be easier :-) \$\endgroup\$ Jan 14 '14 at 16:58
2
\$\begingroup\$

The comment from fernando.reyes is 100% correct: if you haven't measured things, you can't tell what's slow, or if it gets faster. That said, there are two main things I see that I know from prior exposure are likely to be slow. Since I've not used python on android, much less pgs4a, even that is quite possibly not applicable or not relevant. So, again, if you don't measure, you cannot prove whether these are actually meaningful in your case, or if changing them helps.

(By the way, seriously, clean up the code before asking for reviews. In the nearly 300 lines of code, the only comment (or docstring) is # This isn't run on Android., there are multiple unhelpful—such as one-character—variable names, the use of horizontal whitespace is irregular, and vertical whitespace is missing.)

Avoid eval. Its use can prevent normal optimizations that python does for local variables in a function. For example:

sel='SandPart'
...
if sel is not 'SandPart':
...
sand.append(eval(sel)((mse[0]/8)*8,(mse[1]/8)*8))

Prefer working with callables as actual objects.

sel = SandPart
...
if sel is not SandPart:
...
sand.append(sel((mse[0]/8)*8, (mse[1]/8)*8))

Avoid looking up globals or deep attribute lookups in loops. For example:

if e.type==MOUSEBUTTONDOWN:
    if prect.collidepoint(pygame.mouse.get_pos()[0],pygame.mouse.get_pos()[1]):

could well be this, to avoid half of the lookups:

if e.type==MOUSEBUTTONDOWN:
    if prect.collidepoint(*pygame.mouse.get_pos()):

or this, to avoid the other half:

if e.type==MOUSEBUTTONDOWN:
    if prect.collidepoint(*e.pos):

Similarly you can consider capturing the globals (or better yet their deep attribute names) as locals to avoid excess lookups:

def main():
    Rect = pygame.Rect
    get_pressed = pygame.mouse.get_pressed
    draw_rect = pygame.draw.rect
    ...
\$\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.