## ~ Game Design

### Challenge 5

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!

### CHALLENGE :

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!

### CODE:

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

#FILE 4: Angel
#Added the SpecialBullet for angels and devils to extend
#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.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:

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

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

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

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)

timeToEnd = 20000
timeEllapsed = 0

won = False

#Group for rendering the angel
angelSprite = pygame.sprite.Group()
angel = Angel(0,0)

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

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

score = 0
won = False
timeEllapsed = 0
#Set the angel's random intervals
angel.setIntervals(timeToEnd)
done = True
if won:
saveScores(highScores)
scoreSavedScreen(won)
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
screen.blit(label, (0,0))