~ Game Design


Challenge 6

We're nearly there! In the last challenge you managed to (or you're just going to cut and paste the code below) add a devil sprite to the game as well. The devil darted horizontally across the screen and on touching it you lost a lot of points! We're now at the final stage of the game Well done for persevering!

CHALLENGE : 

1. Copy and paste the code below (this is the solution to Challenge 5 and shows a scary devil darting horizontally across the screen. Oh, and if you touch it (the devil) your score decrements)

2. Finally, extend the program to do the following: 

>>Add a Level Feature to the game. IF the player achieves a 200 point score, the player is taken to the NEXT level. On the next level there is a background of clouds. There is a very fast moving green ball (which increments score) and two medium-large sized red balls that decrement score (making the game thus harder). The same rules apply. In 20  seconds, if '300' is not achieved, the game is over. Note: the score continues from Level 1 (in which it was 200). Again, these are given the option to be added to the high scores text file.

CODE: 

import pygame
import random
import math
import sys
import os

#FILE 5: Devil
#Added a subclass of SpecialBullet named Devil to implement the devil
#Added the devil calls to the main loop. They are similar to the angel calls
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

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)

menuChoice = 0

timeToEnd = 20000
timeEllapsed = 0

won = False

highScores = readScores()

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

#Group for rendering the devil
devilSprite = pygame.sprite.Group()
devil = Devil(0,0)
devilSprite.add(devil)

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
                    angel.setIntervals(timeToEnd)
                    #Set the devil's intervals
                    devil.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 and devil values
                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()
                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

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

                #If the devil is ingame, draw it to the screen and check for
                #collisions
                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)
                #Reset the angel and devil values
                angel.reset()
                devil.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()