So, you've managed to create an angel that drops down from heaven like a bullet! The score goes up (on touching the angel) by +100! Great - so let's move on to the next step. We're nearly there!
1. Copy and paste the code below (this is the solution to Challenge 4 and the angel that drops down from heaven like a bullet thing(!) now works.....)
2. Extend the program to do the following:
>> Like we were saying, in life you always have an enemy, and games aren't terribly different. So, in that vein, add a devil sprite to the game (shudder!) Feel free to find an image of the scariest devil you can find. The devil races across (horizontally) like a bullet from left to right on the screen at three random intervals, and from different random starting positions. If the player ball collides with the devil sprite, the score is decremented -100 each. Ouch!
import pygame import random import math import sys import os #FILE 4: Angel #Added the SpecialBullet for angels and devils to extend #Added the Angel class #Added an angel object to the main loop and called functions inside of it to #make it work like expected 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 def check_contact(self, greenBall, redBall): to_return = 0 if math.sqrt((self.y - greenBall.y) ** 2 + (self.x - greenBall.x) ** 2) < self.radius + greenBall.radius: if self.green_cooldown == 0: self.green_cooldown = 10 to_return += 10 if math.sqrt((self.y - redBall.y) ** 2 + (self.x - redBall.x) ** 2) < self.radius + redBall.radius: if 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 #A class for special bullets (I.E Angels and Devils) class SpecialBullet(pygame.sprite.Sprite): #Set all the appropiate values for the object def __init__(self,x,y,xSpeed,ySpeed,sprite,horizontal,scale): #Call the Sprite constructor to get sprite properties super(SpecialBullet,self).__init__() #xSpeed and ySpeed are speeds of movement for the bullet self.xSpeed = xSpeed self.ySpeed = ySpeed #Load the sprite image for this bullet and set its size by scaling it 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))) #Get the sprite's rectangle and set its position self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y #These are variables used to determine functionality in other parts of #the class self.horizontal = horizontal self.posIntervals = [] self.timeIntervals = [] self.ingame = False self.currentInterval = 0 #Determine if the sprite is onscreen 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 #Determine if the sprite collided with the player. Uses simple rectangle #based collision 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 #Updates the object's status. Should be called every game cycle def update(self, timePassed): #First, determine if we have appeared onscreen enough times if self.currentInterval == 3: return #Execute these actions if we are not inside the game if not self.ingame: #If we have reached our current timeInterval, set the sprite to be #ingame and determine the starting position based on the horizontal #variable 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 #Execute these actions if we are inside the game else: #Move the sprite self.rect.x += self.xSpeed self.rect.y += self.ySpeed #Remove the sprite from the game if it stops being onscreen if not self.isInScreen(): self.ingame = False self.currentInterval += 1 #Reset the ingame state and the time interval def reset(self): self.currentInterval = 0 self.ingame = False #Set the time and position intervals for appearing onscreen def setIntervals(self, finishTime): if self.horizontal: self.posIntervals = [random.randint(0,SCREEN_WIDTH), random.randint(0,SCREEN_WIDTH), random.randint(0,SCREENWIDTH)] else: self.posIntervals = [random.randint(0,SCREEN_HEIGHT), random.randint(0,SCREEN_HEIGHT), random.randint(0,SCREEN_HEIGHT)] 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 to make a specific SpecialBullet to represent an angel class Angel(SpecialBullet): def __init__(self,x,y): super(Angel,self).__init__(x,y,0,4,"angel.png",False, 0.7) 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) menuChoice = 0 timeToEnd = 20000 timeEllapsed = 0 won = False highScores = readScores() #Group for rendering the angel angelSprite = pygame.sprite.Group() angel = Angel(0,0) angelSprite.add(angel) 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 won = False timeEllapsed = 0 #Set the angel's random intervals angel.setIntervals(timeToEnd) elif menuChoice == 2: done = True elif menuChoice == 3: if won: saveScores(highScores) scoreSavedScreen(won) elif menuChoice == 4: viewScoresScreen() continue if score > -100 and score < 200 and timeEllapsed < timeToEnd: #Update the angel's values angel.update(timeEllapsed) #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") #If the angel is ingame, draw it to the screen and check for #collisions if angel.ingame: angelSprite.draw(screen) if angel.collidedWithPlayer(ball1): score += 100 angel.ingame = False angel.currentInterval += 1 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() ball3.move() score += ball1.check_contact(ball2, ball3) ball2.draw() ball3.draw() ball1.draw() if ball1.green_cooldown > 0: ball1.green_cooldown -= 1 if ball1.red_cooldown > 0: ball1.red_cooldown -= 1 timeEllapsed += clock.get_time() else: finalFont = pygame.font.SysFont("monospace", 50) #Reset the angel's values angel.reset() if score < 100: label = finalFont.render("YOU LOSE!", 1, (0,0,0)) screen.blit(label, (0,0)) else: label = finalFont.render("YOU WIN!", 1, (0,0,0)) won = True highScores = updateScores(highScores,(name,score)) screen.blit(label, (0,0)) menuChoice = 0 pygame.display.flip() clock.tick(60) pygame.time.wait(3000) continue pygame.display.flip() clock.tick(60) pygame.quit()