~ Creating a class based ball game in python and pygame


Challenge 1

We're going to start off by giving you the code for a very simple ball game that was set up in Series 3 (See Lessons - Series 3). It is a class based game and employs the basics of OOP!

CHALLENGE : 

1. Copy and paste the code below and run it to ensure it works

2. Extend the program to do the following: 

>> Add a menu screen like the one shown below. P allows you to play the game and Q will exit the application. We will add functionality for the other parts at a later stage.

CODE: 

import pygame
import random
import math

#Define variables wich represent the size of the map we want
SCREEN_WIDTH = 400
SCREEN_HEIGHT = 300


#Create the ball class
class Ball:
	def __init__(self, x, y, radius, color, screen):
		#A ball need a position (x,y), a radius, a color and the screen where we will paint it, therefore
		#the constructor will take these as arguments and save their values in variables of the ball class by using the word self
		self.x = x
		self.y = y
		self.radius = radius
		self.screen = screen
		self.color = color

	#The draw function will be responsible for drawing the ball in the screen
	def draw(self):
		pygame.draw.circle(screen, self.color, [self.x, self.y], self.radius)

#New class for the player's ball. This class extends Ball because it has the same base features but is more specific
class PlayerBall(Ball):
	def __init__(self, x, y, radius, color, screen):
		Ball.__init__(self, x, y, radius, color, screen)
		#Add variables to the PlayerBall class for the cooldown of touching the green ball and the red ball
		self.green_cooldown = 0
		self.red_cooldown = 0

	#The move function is responsible for changing the position of the ball based on the user input
	def move(self, mv_type):
		if mv_type == "UP":
			self.y -= 5
		elif mv_type == "DOWN":
			self.y += 5
		elif mv_type == "LEFT":
			self.x -= 5
		elif mv_type == "RIGHT":
			self.x += 5
                        	#If the ball has passed the bounds of the map then update it's position so that it stays inside
		if self.x - self.radius < 0:
			self.x = self.radius
		elif self.x + self.radius > SCREEN_WIDTH:
			self.x = SCREEN_WIDTH - self.radius
		if self.y - self.radius < 0:
			self.y = self.radius
		elif self.y + self.radius > SCREEN_HEIGHT:
			self.y = SCREEN_HEIGHT - self.radius

	#This function checks if the player's ball is touching another ball
	def check_contact(self, greenBall, redBall):
		#This variable will be returned with the summed value of the balls that are touching the player's ball
		to_return = 0
		#If the distance between the two centers is less that the sum of the radius of both balls then they are touching
		if math.sqrt((self.y - greenBall.y) ** 2 + (self.x - greenBall.x) ** 2) < self.radius + greenBall.radius:
			if self.green_cooldown == 0:
				#Update the green ball cooldown and add 10 to the return variable
				self.green_cooldown = 10
				to_return += 10
		#If the distance between the two centers is less that the sum of the radius of both balls then they are touching
		if math.sqrt((self.y - redBall.y) ** 2 + (self.x - redBall.x) ** 2) < self.radius + redBall.radius:
			if self.red_cooldown == 0:
				#Update the red ball cooldown and subtract 10 to the return variable
				self.red_cooldown = 10
				to_return -= 10
		return to_return

#This class represents the green ball that gives points when the player's ball touches it
class GreenBall(Ball):
	#Similarly to the player's ball, this class extends Ball but has two new variable
	def __init__(self, x, y, radius, color, screen):
		Ball.__init__(self, x, y, radius, color, screen)
		#vx and vy are the velocity values for this ball and are generated randomly when a GreenBall object is created
		self.vy = random.randint(0, 4) - 2
		self.vx = random.randint(0, 4) - 2
		while self.vy == 0 or self.vx == 0:
			self.vy = random.randint(0, 4) - 2
			self.vx = random.randint(0, 4) - 2

	#This function will update the ball's position
	def move(self):
		#Add the velocity value to the position
		self.x += self.vx
		self.y += self.vy

		#If the ball is outside the bounds put it inside and multiply the velocity to -1 to change the ball's direction
		if self.x - self.radius < 0:
			self.x = self.radius
			self.vx *= -1
		elif self.x + self.radius > SCREEN_WIDTH:
			self.x = SCREEN_WIDTH - self.radius
			self.vx *= -1
		if self.y - self.radius < 0:
			self.y = self.radius
			self.vy *= -1
		elif self.y + self.radius > SCREEN_HEIGHT:
			self.y = SCREEN_HEIGHT - self.radius
			self.vy *= -1

#This class represents the green ball that makes the player loose points when he touches it
#It is similar to the GreenBall class, but it's colour is red and the velocity values are different
class RedBall(Ball):
	def __init__(self, x, y, radius, color, screen):
		Ball.__init__(self, x, y, radius, color, screen)
		self.vy = random.randint(0, 6) - 3
		self.vx = random.randint(0, 6) - 3
		while self.vy == 0 or self.vx == 0:
			self.vy = random.randint(0, 6) - 3
			self.vx = random.randint(0, 6) - 3

	def move(self):
		#Add the velocity value to the position
		self.x += self.vx
		self.y += self.vy

		#If the ball is outside the bounds put it inside and multiply the velocity to -1 to change the ball's direction
		if self.x - self.radius < 0:
			self.x = self.radius
			self.vx *= -1
		elif self.x + self.radius > SCREEN_WIDTH:
			self.x = SCREEN_WIDTH - self.radius
			self.vx *= -1
		if self.y - self.radius < 0:
			self.y = self.radius
			self.vy *= -1
		elif self.y + self.radius > SCREEN_HEIGHT:
			self.y = SCREEN_HEIGHT - self.radius
			self.vy *= -1

#The next two lines initiate the game and set the window size by the values we defined on the variables SCREEN_HEIGHT and SCREEN_WIDTH
pygame.init()
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
#The next variable represents if the user wants to quit the game (when the value is True) or not (when the value is False)
#Since we want the game to run we start it with as False
done = False
#The score variable will store the player's current score
score = 0

#Set the font and size that will be used
myfont = pygame.font.SysFont("monospace", 15)

#Create a clock value that allows us to set the FPS value we want
clock = pygame.time.Clock()

#Create a ball of each of the classes that are implemented
ball1 = PlayerBall(100, 100, 20, (0, 0, 0), screen)
ball2 = GreenBall(200, 200, 5, (0, 255, 0), screen)
ball3 = RedBall(250, 300, 90, (255, 0, 0), screen)

#While the user doesn't quit
while not done:
	#Listen to all events that happen
	for event in pygame.event.get():
		#If it's a quit event then set done to true so the game will finish
		if event.type == pygame.QUIT:
			done = True

	#If the players didnt' win or lose yet then keep the balls updating
	if score > -1000 and score < 1000:
		#Listen for keys pressed
		pressed = pygame.key.get_pressed()
		if pressed[pygame.K_UP]:
			ball1.move("UP")
		if pressed[pygame.K_DOWN]:
			ball1.move("DOWN")
		if pressed[pygame.K_LEFT]:
			ball1.move("LEFT")
		if pressed[pygame.K_RIGHT]:
			ball1.move("RIGHT")

		#Paint the screen white
		screen.fill((255, 255, 255))

		#Set the label value to be black and write the actual score
		label = myfont.render("SCORE: " + str(score), 1, (0,0,0))
		#Print that label in the screen
		screen.blit(label, (10, SCREEN_HEIGHT - 20))

		#Call the move functions for the red and green ball to update their position
		ball2.move()
		ball3.move()
		#Update the score based on the balls that are touching the player's ball
		score += ball1.check_contact(ball2, ball3)

		#Draw all the balls by calling their draw function
		ball2.draw()
		ball3.draw()
		ball1.draw()

		#Update the cooldowns for touching the red and green ball
		if ball1.green_cooldown > 0:
			ball1.green_cooldown -= 1
		if ball1.red_cooldown > 0:
			ball1.red_cooldown -= 1
	#if the players won or lost then stop updating the balls and add a message to the screen
	else:
		#Create a new font, bigger than before
		myfont = pygame.font.SysFont("monospace", 50)
		#if the score is -1000 then the player lost and we create the "YOU LOSE!" label and add it to the screen
		if score == -100:
			label = myfont.render("YOU LOSE!", 1, (0,0,0))
			screen.blit(label, (0,0))
		#if the score is 1000 then the player won and we create the "YOU WIN!" label and add it to the screen
		if score == 100:
			label = myfont.render("YOU WIN!", 1, (0,0,0))
			screen.blit(label, (0,0))

	#Update the screen
	pygame.display.flip()
	#Set the FPS value to 60
	clock.tick(60)