For reference, I looked at
https://github.com/Andrew-RK/Endless-Breakout/blob/a06551fd03a44684f2ca4591beb65f3edcce0a10/ebalphav031.py
I just picked some things that I spotted, hopefully these are useful.
if int(ElapsedTime % 1000 < 10):
displayMilliseconds = '00'+str(int(ElapsedTime % 1000))
elif int(ElapsedTime % 1000 < 100):
displayMilliseconds = '0'+str(int(ElapsedTime % 1000))
else:
displayMilliseconds = str(int(ElapsedTime % 1000))
This looks a bit bulky. First of all you're computing "int(ElapsedTime % 1000)" several times here (except in the first 'if' condition, but that may be an error).
You may want to compute it once, and store the value in a variable.
More importantly, Python has string-formatting, which can do the above for you, ie
displayMilliseconds = "{:03d}".format(int(ElapsedTime % 1000))
Your main loop starts at around line 150, and ends at around line 450. If you break that up in a few functions, things get more local, more modular, so it's easier to take out some part, and replace it with something else.
Also, the loop itself becomes much shorter then, so it's easier to understand. (Stuff that can be read from one screen or one page of paper are way easier to understand than when you have to scroll 5 pages down. This especially holds for ending loops. You jump back indenting, but to what point 4 pages ago do you now jump?)
fontSlot = 0
drawText('Highscore:', statsSurface, fontMargin, defaultFontSize * fontSlot + fontMargin, BLACK)
drawText(str(highscore), statsSurface, fontValuesPositionX, defaultFontSize * fontSlot + fontMargin, BLACK)
fontSlot = 1
drawText('Score:', statsSurface, fontMargin, defaultFontSize * fontSlot + fontMargin, BLACK)
drawText(str(score), statsSurface, fontValuesPositionX, defaultFontSize * fontSlot + fontMargin, BLACK)
fontSlot = 2
...
looks very same-ish too. If you align vertically, it becomes easier to read
drawText('Highscore:', statsSurface, fontMargin, defaultFontSize * fontSlot + fontMargin, BLACK)
drawText(str(highscore), statsSurface, fontValuesPositionX, defaultFontSize * fontSlot + fontMargin, BLACK)
Now you can clearly see that only the 1st and the 3rd argument are different.
One way to simplify this is to make a function that combines two of these 'draw' calls into one, but that will break the improvement further down with lines 422 and further.
Instead, why not make a list of values that you want to print:
lines = [('Highscore:', fontMargin, 0),
(str(highscore), fontValuesPositionX, 0),
...
]
for text, xPos, fontSlot in lines:
drawText(text, statsSurface, xPos, defaultFontSize * fontSlot + fontMargin, BLACK)
This is very compact, but understanding what it now actually does is complicated. It's fine to use more lines, and use intermediate variables
balls.append({'rect':pygame.Rect(random.randint(BALLSIZE, PLAYFIELDSIZE-BALLSIZE),
random.randint(BALLSIZE, PLAYFIELDSIZE-BALLSIZE), BALLSIZE, BALLSIZE),
'localspeed':1.0, 'direction':DOWNRIGHT, 'angle':45.0, 'Xdecimal':0, 'Ydecimal':0, 'freshlyspawned':False})
ball ={'rect':pygame.Rect(random.randint(BALLSIZE, PLAYFIELDSIZE-BALLSIZE),
random.randint(BALLSIZE, PLAYFIELDSIZE-BALLSIZE),
BALLSIZE,
BALLSIZE),
'localspeed':1.0,
'direction':DOWNRIGHT,
'angle':45.0,
'Xdecimal':0,
'Ydecimal':0,
'freshlyspawned':False, # Adding a "," at the end and putting the } at the next line makes it easier to add new fields.
}
balls.append(ball)
Not too happy with this, let's do another step:
ball_xpos = random.randint(BALLSIZE, PLAYFIELDSIZE-BALLSIZE)
ball_xpos = random.randint(BALLSIZE, PLAYFIELDSIZE-BALLSIZE)
ball ={'rect':pygame.Rect(ball_xpos, ball_ypos, BALLSIZE, BALLSIZE),
'localspeed':1.0,
'direction':DOWNRIGHT,
'angle':45.0,
'Xdecimal':0,
'Ydecimal':0,
'freshlyspawned':False,
} # Some people prefer the } at the left margin, below "ball" instead of below "{"
balls.append(ball)
You can make code look much better by adding intermediate variables and intermediate assignments. Since Python uses references, additional variables do not take extra time.
I don't know if you have read about classes yet, but your ball screams for one. I'll make it, and you can have a look at it after reading about classes.
class Ball(object): # In Python 3, it's just "class Ball:"
def __init__(self, rect, direction, angle, spawned):
self.rect = rect
self.localspeed = 1.0
self.direction = direction
self.angle = angle
self.Xdecimal = 0
self.Ydecimal = 0
self.freshlyspawned = spawned
ball = Ball(pygame.Rect(ball_xpos, ball_ypos, BALLSIZE, BALLSIZE), DOWNRIGHT, 45.0, False)
You can access eg "direction" with ball.direction, which is a lot better than ball["direction"] :)
I hard-coded values for some variables (self.localspeed = 1.0), and added others to the "Ball" creation call at the last line. This is a matter of preference, change it if you want.
Ball change direction can also be coded differently.
if ball['rect'].top < 0:
if ball['direction'] == UPLEFT:
ball['direction'] = DOWNLEFT
if ball['direction'] == UPRIGHT:
ball['direction'] = DOWNRIGHT
A minor point is that both inner 'if's test for separate conditions, and at most one of them holds, so the second 'if' (4th line) can be an "elif" instead.
You can code this simpler with a dictionary. Say we have
d = {'a': 1, 'b': 2}
# Normal indexing fails for unknown keys:
d['a'] returns 1
d['q'] error
# 'get', returns a default
d.get('a') returns 1
d.get('q') returns None (indicating 'no value found')
d.get('a', 21) returns 1 (it's still available in d)
d.get('q', 21) returns 21 (21 became a new value indicating 'no value found in the dictionary')
So what about (warning some magic ahead)
top_changes = {UPLEFT: DOWNLEFT,
UPRIGHT: DOWNRIGHT}
if ball['rect'].top < 0:
ball['direction'] = top_changes.get(ball['direction'], ball['direction'])
The first "ball['direction']" in the 'get' is the key we are looking for, if it's in the dictionary, you get its value. If it's not in the dictionary, you get the value of the second "ball['direction']", which means nothing changes.
One for pondering about: What is the relation between "direction" and "angle", in particular if angle runs from 0 to 360 degrees?
Finally, the duplicate-ish lines for updating the print score, see if you can improve that:
Lines 422 to around 460 look a lot like the initial text draw sequence. Can you make this a function, and use that function at both places?
If you add or move entries later, you have to change only that function, instead of at two places that you have to do now.