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.
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!
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.radius = radius self.screen = screen self.color = color def draw(self): pygame.draw.circle(screen, self.color, [self.x, self.y], self.radius) 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: 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 #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 return to_return 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.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 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.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 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 self.image = pygame.image.load(sprite).convert_alpha() 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) def renderMenu(): 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 def updateScores(highScores, score): 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() def readScores(): 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)) hScores = readScores() 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) sys.stdout.write("Please enter your name: ") 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) menuChoice = 0 timeToEnd = 20000 timeEllapsed = 0 won = False highScores = readScores() angelSprite = pygame.sprite.Group() angel = Angel(0,0) angelSprite.add(angel) devilSprite = pygame.sprite.Group() devil = Devil(0,0) devilSprite.add(devil) #Set up background for next level bg = pygame.sprite.Sprite() bg.image = pygame.image.load("bg.jpg").convert_alpha() bg.rect = bg.image.get_rect() bg.rect.x = 0 bg.rect.y = 0 bgSprite = pygame.sprite.Group() bgSprite.add(bg) #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)) if menuChoice != 1: menuChoice = renderMenu() if menuChoice == 1: score = 0 goalScore = 100 nextLevelScore = 200 won = False timeEllapsed = 0 angel.setIntervals(timeToEnd) devil.setIntervals(timeToEnd) elif menuChoice == 2: done = True elif menuChoice == 3: if won: saveScores(highScores) scoreSavedScreen(won) elif menuChoice == 4: 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)) menuChoice = 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 menuChoice = 0 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: menuChoice = 1 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: menuChoice = 0 nextLevel = False highScores = updateScores(highScores,(name,score)) screen.blit(label, (0,0)) pygame.display.flip() clock.tick(60) pygame.time.wait(3000) continue pygame.display.flip() clock.tick(60) pygame.quit()