## ~ Game Design

### Final Solution

Well done! You've gone through all the steps to create a fully functional game in python and pygame, complete with classes, a menu screen, enemies, friendly sprites, reading and writing from a file (high scores) and a next level feature. This hopefully will give you an idea of how things work at a very basic level. You could now devise your very own unique idea and start to develop it using some of these key concepts.

### FINAL SOLUTION :

The code below is the answer/solution for Challenge 6 and introduces the idea of levels. Please feel free to test the game, change variables, and then consider further extensions!

### CODE:

```import pygame
import random
import math
import sys
import os

#FILE 6: Next Level
#Added variables and cases in the main loop to be able to get the next level functionality

SCREEN_WIDTH = 400
SCREEN_HEIGHT = 300

class Ball:
def __init__(self, x, y, radius, color, screen):
self.x = x
self.y = y
self.screen = screen
self.color = color

def draw(self):

class PlayerBall(Ball):
def __init__(self, x, y, radius, color, screen):
Ball.__init__(self, x, y, radius, color, screen)
self.green_cooldown = 0
self.red_cooldown = 0

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 self.x - self.radius < 0:
elif self.x + self.radius > SCREEN_WIDTH:
if self.y - self.radius < 0:
elif self.y + self.radius > SCREEN_HEIGHT:

#Modified the check_contact method to be more general
def check_contact(self, ball, isGreen):
to_return = 0
if math.sqrt((self.y - ball.y) ** 2 + (self.x - ball.x) ** 2) < self.radius + ball.radius:
if isGreen and self.green_cooldown == 0:
self.green_cooldown = 10
to_return += 10
elif not isGreen and self.red_cooldown == 0:
self.red_cooldown = 10
to_return -= 10

class GreenBall(Ball):
def __init__(self, x, y, radius, color, screen):
Ball.__init__(self, x, y, radius, color, screen)
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

def move(self):
self.x += self.vx
self.y += self.vy

if self.x - self.radius < 0:
self.vx *= -1
elif self.x + self.radius > SCREEN_WIDTH:
self.vx *= -1
if self.y - self.radius < 0:
self.vy *= -1
elif self.y + self.radius > SCREEN_HEIGHT:
self.vy *= -1

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):
self.x += self.vx
self.y += self.vy

if self.x - self.radius < 0:
self.vx *= -1
elif self.x + self.radius > SCREEN_WIDTH:
self.vx *= -1
if self.y - self.radius < 0:
self.vy *= -1
elif self.y + self.radius > SCREEN_HEIGHT:
self.vy *= -1

class SpecialBullet(pygame.sprite.Sprite):
def __init__(self,x,y,xSpeed,ySpeed,sprite,horizontal,scale):
super(SpecialBullet,self).__init__()
self.xSpeed = xSpeed
self.ySpeed = ySpeed

w,h = self.image.get_size()
self.image = pygame.transform.scale(self.image,(int(w*scale),int(h*scale)))

self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y

self.horizontal = horizontal
self.posIntervals = []
self.timeIntervals = []
self.ingame = False
self.currentInterval = 0

def isInScreen(self):
return self.rect.x >= 0 and self.rect.x <= SCREEN_WIDTH and self.rect.y >= 0 and self.rect.y <= SCREEN_HEIGHT

def collidedWithPlayer(self,ball):
res = (ball.x - ball.radius) < self.rect.x + self.rect.width
res = res and ball.x + ball.radius > self.rect.x
res = res and (ball.y - ball.radius) < self.rect.y + self.rect.height
return res and ball.y + ball.radius > self.rect.y

def update(self, timePassed):
if self.currentInterval == 3:
return
if not self.ingame:
if timePassed >= self.timeIntervals[self.currentInterval]:
self.ingame = True
if self.horizontal:
self.rect.y = self.posIntervals[self.currentInterval]
self.rect.x = 0
else:
self.rect.x = self.posIntervals[self.currentInterval]
self.rect.y = 0
else:
self.rect.x += self.xSpeed
self.rect.y += self.ySpeed
if not self.isInScreen():
self.ingame = False
self.currentInterval += 1

def reset(self):
self.currentInterval = 0
self.ingame = False

def setIntervals(self, finishTime):
if self.horizontal:
self.posIntervals = [random.randint(0,SCREEN_HEIGHT), random.randint(0,SCREEN_HEIGHT), random.randint(0,SCREEN_HEIGHT)]
else:
self.posIntervals = [random.randint(0,SCREEN_WIDTH), random.randint(0,SCREEN_WIDTH), random.randint(0,SCREEN_WIDTH)]

fragment = finishTime // 3
t1 = random.randint(0,fragment)
t2 = random.randint(fragment, fragment*2)
t3 = random.randint(fragment*2,finishTime)
self.timeIntervals = [t1,t2,t3]

class Angel(SpecialBullet):
def __init__(self,x,y):
super(Angel,self).__init__(x,y,0,4,"angel.png",False, 0.7)

#Class to make a specific SpecialBullet to represent a devil
class Devil(SpecialBullet):
def __init__(self,x,y):
super(Devil,self).__init__(x,y,4,0,"devil.png",True,1)

f = pygame.font.SysFont("monospace", 30)
title = f.render("Main Menu", 1, (0,0,0))
screen.blit(title, (SCREEN_WIDTH//4,0))
play = f.render("P - Play", 1, (0,0,0))
screen.blit(play, (0,50))
quit = f.render("Q - Quit", 1, (0,0,0))
screen.blit(quit, (0,100))
save = f.render("S - Save Scores", 1, (0,0,0))
screen.blit(save, (0,150))
view = f.render("V - View Scores", 1, (0,0,0))
screen.blit(view, (0,200))

res = 0

pressed = pygame.key.get_pressed()
if pressed[pygame.K_p]:
res = 1
if pressed[pygame.K_q]:
res = 2
if pressed[pygame.K_s]:
res = 3
if pressed[pygame.K_v]:
res = 4

pygame.display.flip()
clock.tick(60)
return res

for i in range(len(highScores)):
if(score[1] > highScores[i][1]):
highScores = highScores[0:i] + [score] + highScores[i:-1]
break
return highScores

def saveScores(highScores):
f = open("score.txt","w")
for score in highScores:
f.write(score[0] + " " + str(score[1]) + "\n")
f.close()

if os.path.isfile("score.txt"):
f = open("score.txt","r")
highScores = []
for line in f:
l = line.split(" ")
highScores.append((l[0],int(l[1])))
f.close()
return highScores
else:
return [("Player",0),("Player",0), ("Player",0)]

def scoreSavedScreen(won):
screen.fill((255, 255, 255))
text = "Scores Saved!"
text2 = ""
if not won:
text = "You must have won"
text2 = "to save scores!"

f = pygame.font.SysFont("monospace", 30)
title = f.render(text, 1, (0,0,0))
screen.blit(title, (0,50))
secondLine = f.render(text2,1,(0,0,0))
screen.blit(secondLine, (0,100))
pygame.display.flip()
pygame.time.delay(2000)

def viewScoresScreen():
screen.fill((255, 255, 255))

s1 = hScores[0][0] + ": " + str(hScores[0][1])
s2 = hScores[1][0] + ": " + str(hScores[1][1])
s3 = hScores[2][0] + ": " + str(hScores[2][1])

f = pygame.font.SysFont("monospace", 30)
s1Label = f.render(s1, 1, (0,0,0))
screen.blit(s1Label, (0,50))
s2Label = f.render(s2, 1, (0,0,0))
screen.blit(s2Label, (0,100))
s3Label = f.render(s3, 1, (0,0,0))
screen.blit(s3Label, (0,150))

pygame.display.flip()
pygame.time.delay(2000)

name = input()

pygame.init()
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
done = False
score = 0

myfont = pygame.font.SysFont("monospace", 15)

clock = pygame.time.Clock()

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)

#The two medium sized balls for the next level
ball4 = RedBall(100, 300, 45, (255, 0, 0), screen)
ball5 = RedBall(300, 100, 45, (255, 0, 0), screen)

timeToEnd = 20000
timeEllapsed = 0

won = False

angelSprite = pygame.sprite.Group()
angel = Angel(0,0)

devilSprite = pygame.sprite.Group()
devil = Devil(0,0)

#Set up background for next level
bg = pygame.sprite.Sprite()
bg.rect = bg.image.get_rect()
bg.rect.x = 0
bg.rect.y = 0
bgSprite = pygame.sprite.Group()

#Variable to determine if we are in the next level
nextLevel = False

#Variables to determine the goal scores
goalScore = 100
nextLevelScore = 200

while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True

screen.fill((255, 255, 255))

score = 0
goalScore = 100
nextLevelScore = 200
won = False
timeEllapsed = 0
angel.setIntervals(timeToEnd)
devil.setIntervals(timeToEnd)
done = True
if won:
saveScores(highScores)
scoreSavedScreen(won)
viewScoresScreen()
continue

#Draw the clouds background if we are in the next level
if nextLevel:
bgSprite.draw(screen)

#Add the nextLevelScore as a termination condition
if score > -100 and score < nextLevelScore and timeEllapsed < timeToEnd:
angel.update(timeEllapsed)
devil.update(timeEllapsed)

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")

label = myfont.render("SCORE: " + str(score), 1, (0,0,0))
timeLabel = myfont.render("TIME: " + str((timeToEnd - timeEllapsed) // 1000 + 1), 1,(0,0,0))
screen.blit(label, (10, SCREEN_HEIGHT - 20))
screen.blit(timeLabel, (300, SCREEN_HEIGHT - 20))

ball2.move()
score += ball1.check_contact(ball2, True)
ball2.draw()

#Move, check and draw the appropiate balls depending on level
if nextLevel:
ball4.move()
score += ball1.check_contact(ball4,False)
ball4.draw()
ball5.move()
score += ball1.check_contact(ball5,False)
ball5.draw()
else:
ball3.move()
score += ball1.check_contact(ball3,False)
ball3.draw()

ball1.draw()

if ball1.green_cooldown > 0:
ball1.green_cooldown -= 1
if ball1.red_cooldown > 0:
ball1.red_cooldown -= 1

if angel.ingame:
angelSprite.draw(screen)
if angel.collidedWithPlayer(ball1):
score += 100
angel.ingame = False
angel.currentInterval += 1

if devil.ingame:
devilSprite.draw(screen)
if devil.collidedWithPlayer(ball1):
score -= 100
devil.ingame = False
devil.currentInterval += 1

timeEllapsed += clock.get_time()
else:
finalFont = pygame.font.SysFont("monospace", 50)
angel.reset()
devil.reset()
if score < goalScore:
label = finalFont.render("YOU LOSE!", 1, (0,0,0))
screen.blit(label, (0,0))
else:
label = finalFont.render("YOU WIN!", 1, (0,0,0))

#Determine where to go after winning
#If we were already at the next level, go back to the main menu
if nextLevel:
won = True
nextLevel = False
#If we were in the first level and we reached the score needed to get to the next level, go there.
#Also set up all starting values accordingly to start the level
elif score >= nextLevelScore:
nextLevel = True
score = 200
goalScore = 300
nextLevelScore = 300
timeEllapsed = 0
angel.setIntervals(timeToEnd)
devil.setIntervals(timeToEnd)
#If we won but we didn't reach the next level, go back to the main menu
else:
nextLevel = False

screen.blit(label, (0,0))

pygame.display.flip()
clock.tick(60)
pygame.time.wait(3000)
continue

pygame.display.flip()
clock.tick(60)

pygame.quit()

```