# Dungeon Ball, a Pygame

I just completed the first release for my first game on Python, called Dungeon Ball. I'm looking for people to test out the app and give me some feedback and constructive criticism. I'd like to use this project as a way to improve my programming skills and hopefully learn some efficient programming practices.

The game is called Dungeon Ball. It is pretty basic at the moment. It is pretty similar to games on breaking bricks with a ball and a racquet/paddle but I haven't included the bricks just yet. Currently, the goal is to just keep the ball from falling using the paddle. By hitting the paddle you get points, which causes you to level up. The higher the level, the faster the paddle and ball move.

Main.py

import pygame
from pygame.locals import *
import numpy as np
import math
from sys import exit
pygame.init()

from variables import *

def gameOver():
pygame.mixer.music.stop()
sounds['gameOver'].play()
keyStatus = True
while keyStatus:
pygame.draw.rect(DISPLAYSURF, colours['grey'], dimensions['arena'])
# pygame.draw.rect(DISPLAYSURF, colours['brown'], dimensions['arena'], borderWidth)
textSurfaceObj = fonts['largeFont'].render('GAME OVER!', True, colours['red'])
textRectObj = textSurfaceObj.get_rect()
textRectObj.center = (boxSize[0]/2, boxSize[1]/2)
DISPLAYSURF.blit(textSurfaceObj, textRectObj)

scoreSurface = fonts['midFont'].render('Score : {}'.format(gameStatus['points']), True, colours['white'])
scoreSurfaceRect = scoreSurface.get_rect()
scoreSurfaceRect.center = (boxSize[0]/2, boxSize[1]/2 + 50)
DISPLAYSURF.blit(scoreSurface, scoreSurfaceRect)

for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_RETURN:
sounds['gameOver'].stop()
keyStatus = False
elif event.key == pygame.K_ESCAPE:
pygame.quit()
exit()

pygame.display.update()
fpsClock.tick(FPS)

if keyStatus == False:
break

main()

def renderFunction():
global gameStatus
pygame.draw.rect(DISPLAYSURF, colours['black'], dimensions['arena'])
pygame.draw.rect(DISPLAYSURF, colours['brown'], dimensions['arena'], borderWidth)
pygame.draw.circle(DISPLAYSURF, colours['blue'], (ball['position']['x'], ball['position']['y']), ball['rad'] , 0)
pointSurface = fonts['tinyFont'].render('Points : ' + str(gameStatus['points']), True, colours['white'])
pointSurfaceRect = pointSurface.get_rect()
pointSurfaceRect.center = (40, boxSize[1]-10)
DISPLAYSURF.blit(pointSurface, pointSurfaceRect)

levelSurface = fonts['tinyFont'].render('Level : ' + str(gameStatus['level']), True, colours['white'])
levelSurfaceRect = levelSurface.get_rect()
levelSurfaceRect.center = (boxSize[0]-40, boxSize[1]-10)
DISPLAYSURF.blit(levelSurface, levelSurfaceRect)

def introScreen():
keyStatus = True
pygame.mixer.music.play(-1, 0.0)
while keyStatus:
pygame.draw.rect(DISPLAYSURF, colours['grey'], dimensions['arena'])
# pygame.draw.rect(DISPLAYSURF, colours['brown'], dimensions['arena'], borderWidth)
textSurfaceObj = fonts['largeFont'].render(gameStatus['name'], True, colours['gold'])
textRectObj = textSurfaceObj.get_rect()
textRectObj.center = (boxSize[0]/2, boxSize[1]/2)
DISPLAYSURF.blit(textSurfaceObj, textRectObj)

spaceSurfaceObj = fonts['midFont'].render('Press Enter to Continue', True, colours['white'])
spaceRectObj = spaceSurfaceObj.get_rect()
spaceRectObj.center = (boxSize[0]/2, boxSize[1]/2+50)
DISPLAYSURF.blit(spaceSurfaceObj, spaceRectObj)

versionSurface = fonts['tinyFont'].render(gameStatus['version'], True, colours['white'])
versionSurfaceRect = versionSurface.get_rect()
versionSurfaceRect.center = (boxSize[0]-20, boxSize[1]-10)
DISPLAYSURF.blit(versionSurface, versionSurfaceRect)

for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_RETURN:
keyStatus = False
elif event.key == pygame.K_ESCAPE:
pygame.quit()
exit()
pygame.display.update()
fpsClock.tick(FPS)

keyStatus=True
pygame.mixer.music.stop()

def eventHandler():
global dimensions
keys=pygame.key.get_pressed()
if keys[K_LEFT] and not (dimensions['paddle'].left <= (dimensions['arena'].left+borderWidth)):
# print('hi left')
elif keys[K_RIGHT] and not (dimensions['paddle'].right >= (dimensions['arena'].right-borderWidth)):
# print('hi right')
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
exit()

def ballEngine():
global gameStatus
# print('LeftSideBounce')
ball['direction'] = 180 - ball['direction'] + np.random.randint(-1*gameStatus['random'],gameStatus['random'])
sounds['wallHit'].play()

# print('RightSideBounce')
ball['direction'] = 180 - ball['direction'] + np.random.randint(-1*gameStatus['random'],gameStatus['random'])
sounds['wallHit'].play()

# print('TopBounce')
ball['direction'] = 360 - ball['direction'] + np.random.randint(-1*gameStatus['random'],gameStatus['random'])

if ball['direction'] >= 250 and ball['direction'] <= 290:
ball['direction'] += np.random.randint(-2*gameStatus['random'],2*gameStatus['random'])

sounds['wallHit'].play()
elif ball['position']['y'] >= (dimensions['arena'].bottom - borderWidth - ball['rad']):
# print('BottomBounce')
# ball['speed'] = 0
# gameStatus = True
gameOver()
# print(ball['direction'])
ball['direction'] = 360 - ball['direction'] + np.random.randint(-1*gameStatus['random'],gameStatus['random'])
gameStatus['points'] = gameStatus['points'] + 1

if gameStatus['paddleHitsPerLevel'] == (gameStatus['level']*5) and not gameStatus['points']  == 0:
ball['speed'] += 2
gameStatus['level'] += 1
gameStatus['random'] += 2
sounds['levelUp'].play()

if gameStatus['points'] % 10 == 0 and not gameStatus['points']  == 0:

if (ball['direction']>360 or ball['direction'] < 0):
ball['direction'] %= 360

if ball['direction'] % 90 >= 85 and ball['direction'] % 90 <=89 or ball['direction'] % 90 >= 0 and ball['direction'] % 90 <= 5:
ball['direction'] += np.random.randint(-2*gameStatus['random'],2*gameStatus['random'])

ball['position']['x'] += int(ball['speed']*math.cos(ball['direction']*math.pi/180))
ball['position']['y'] += int(ball['speed']*math.sin(ball['direction']*math.pi/180))

def init():
ball['position']['x']=boxSize[0]/2
ball['position']['y']=int(boxSize[1]/3)
ball['direction']=np.random.randint(295, 325)
ball['speed']=5

gameStatus['points']=0
gameStatus['level']=1
gameStatus['random']=5

def main():
introScreen()
init()
pygame.mixer.music.play(-1, 0.0)

while True:
eventHandler()
ballEngine()
renderFunction()
pygame.display.update()
fpsClock.tick(FPS)

if __name__ == '__main__':
fpsClock = pygame.time.Clock()
DISPLAYSURF = pygame.display.set_mode(boxSize, 0, 32)
pygame.display.set_caption(gameStatus['name'])
main()


Variables.py

import numpy as np
import pygame
pygame.init()
from pygame.locals import *
import os

FPS = 30
borderWidth = 5

boxSize = (700, 400)

colours = {'black':(0, 0, 0),
'red': (255, 0, 0),
'blue':(0, 0, 255),
'brown':(210, 105, 30),
'green':(0, 255, 0),
'white':(255, 255, 255),
'gold':(255, 215, 0),
'silver':(192, 192, 192),
'grey':(128, 128, 128)}

ball = {'position':{'x':boxSize[0]/2, 'y':boxSize[1]/3}, 'direction':np.random.randint(295, 325), 'speed':5, 'rad':5}

paddle = {'position':{'x':boxSize[0]/2, 'y':boxSize[1]-50}, 'length':100, 'speed':5}

dimensions = {
'arena': pygame.Rect(0, 0, boxSize[0], boxSize[1]+10),
}

gameStatus = {'points': 0, 'level': 1, 'random': 5, 'paddleHitsPerLevel':0, 'name': 'Dungeon Ball', 'version': 'v1.0'}

fonts = {
'largeFont':pygame.font.Font(os.path.join(os.getcwd(),'fonts','Ancient_Modern_Tales_Regular.ttf'), 64),
}

sounds = {
'wallHit': pygame.mixer.Sound(os.path.join(os.getcwd(), 'audio', 'wall_hit.wav')),
'gameOver':pygame.mixer.Sound(os.path.join(os.getcwd(), 'audio', 'game_over.wav')),
'levelUp': pygame.mixer.Sound(os.path.join(os.getcwd(), 'audio', 'level_up.wav'))
}


Currently, the release is only for Linux and Windows. Mac users could try and build the environment and run the program directly. First step, you need to download your OS specific release and unzip it. Enter the extracted directory. Windows users just double click to run. Linux users need to open the directory in terminal and run ./main.

• The current question title, which states your concerns about the code, applies to too many questions on this site to be useful. The site standard is for the title to simply state the task accomplished by the code. Please see How to Ask for examples, and revise the title accordingly.
– Mast
Aug 4 '20 at 18:51
• @Mast thanks for the suggestion. Is this a better topic? Aug 4 '20 at 19:03
• No, I've fixed it for you. Leave the concerns to the question body.
– Mast
Aug 4 '20 at 19:04

I have not tried playing the game, but had a look at the code and I have some suggestions.

boxSize[0] and boxSize[1] is used all over your code and not very readable. There is only one place where you actually use the variable boxSize without indexes, so I would do the opposite and define width = 700 and height=400 so that you can refer to them where needed, and then in the one line that you used boxSize you change that to

ISPLAYSURF = pygame.display.set_mode((width, height), 0, 32)

 ball['direction']=np.random.randint(295, 325) I think the numbers 295 and 325 refer to angles but they could be named to make that clearer.

ball['rad']

I think rad is short for radius but it's not a good name. Generally avoid short versions of words. Especially rad which in mathematics commonly refers to radians used to measure angles, which confused me while thinking about the direction of the ball.

if keys[K_LEFT] and not (dimensions['paddle'].left <= (dimensions['arena'].left+borderWidth)):


not <= is requivalent to just > so it would be more readable to rather write

if keys[K_LEFT] and (dimensions['paddle'].left > (dimensions['arena'].left+borderWidth)):


Logic 1

    if blinkerCount % blinkTime == 0:


Since you're resetting blinkerCount to 0 every time, you don't need the modulo operation, you can just change the if-clause to if blinkerCount == blinkTime . The modulo operation would make sense if you didn't reset to 0.

Logic 2

    for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_RETURN:
sounds['gameOver'].stop()
keyStatus = False
elif event.key == pygame.K_ESCAPE:
pygame.quit()
exit()


Both elif here are redundant and can be replaced by just if. Since pygame.QUIT and pygame.KEYDOWN are different things, an event cannot by definition be equal to both, so the "else" in the elif is not needed.

Avoid repetition 1

You are making many calls to pygame.draw.rect(DISPLAYSURF which I would create a new function for, so that you don't have to repeat this over and over.

It would be something like

def rectangle(color, _dimensions):
pygame.draw.rect(DISPLAYSURF, colors[color], _dimensions)


and then in the other places of your code you can replace something like

pygame.draw.rect(DISPLAYSURF, colours['grey'], dimensions['arena'])

with just

rectangle('grey', dimensions['arena'])

Avoid repetition 2

sounds = {
'wallHit': pygame.mixer.Sound(os.path.join(os.getcwd(), 'audio', 'wall_hit.wav')),
'gameOver':pygame.mixer.Sound(os.path.join(os.getcwd(), 'audio', 'game_over.wav')),
'levelUp': pygame.mixer.Sound(os.path.join(os.getcwd(), 'audio', 'level_up.wav'))
}


See how 70% of each line here is identical to the other? This is where you want to create a function for this, like

def get_sound(filename):
return pygame.mixer.Sound(os.path.join(os.getcwd(), 'audio', filename))


so that you can replace the above with

sounds = {
'wallHit': get_sound('wall_hit.wav'),
'gameOver':get_sound('game_over.wav'),
'levelUp': get_sound('level_up.wav')
}


(It can be made even shorter if the keys were named same as the files)

if ball['position']['y'] < borderWidth+ball['rad']:


This kind of code is quite hard to read and surely a waste of space and time to write as well. I recommend you look up basic objects/classes, so that you can define a class ball and set its properties, so that you can instead write

if ball.y < borderWidth+ball.radius:

See how much easier that is?

• Okay these are really insightful. Thanks for your suggestions. If you can please try out the game when you're free? I'd like a critique on the aesthetic as well. Also do you suggest any features I could add? Aug 10 '20 at 20:24
• The problem with trying out the game is that I would have to set up an environment with pygame and maybe other libraries, and run code that I don't know all the details of, on my own computer, which is time consuming and a risk. I would try it if you could make it playable online somehow, in a virtual environment or something. Otherwise, I recommend making a recording of when you play it yourself and put that on Youtube, that makes it a lot more available to others and enables you to collect feedback from other people easier. Aug 11 '20 at 0:26
• There is also gamedev stackexchange, I don't know their policies for posting, but it might be open to feedback on your own games. If not, there are other websites and communities for that. Aug 11 '20 at 0:27
• I've created an exe file of the app so you do not have to create the environment. But no problem. Yeah I think I could do a video on YouTube or something similar. Aug 11 '20 at 3:09
• Running an unknown exe file from an unknown person is an even greater risk than setting up the environment and running the code that I can at least read through. Not worth it, I'm afraid. I'd be happy to provide more code review input to an updated version of the code though, perhaps via github? Aug 11 '20 at 18:57