~ 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 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()