~ Game Design


Challenge 4

So, you've managed to set up a high scores feature to the game - this is rather difficult to scope if you're just starting out, so well done! You have also activated the other buttons on the menu screen so you can view and save high scores accordingly.

CHALLENGE : 

1. Copy and paste the code below (this is the solution to Challenge 3 and includes the high scores functionality that is now built in)

2. Extend the program to do the following: 

>>Add an angel sprite to the game (you can use any image of an angel that you please!) The angel sprite drops like a bullet from the top of the screen at three random intervals and from three random positions each time. If the player ball collides with the angel sprite, the score jumps to 100 each time. Please note that you need to add the image to the same folder that your .py game is in. 

CODE: 

import pygame
import random
import math
import sys
import os

#FILE 3: High Scores
#Added the following functions
#updateScores: Updates the scores in memory
#saveScores: Saves the highscores to an external file
#readScores: Read the scores from an external file
#scoreSavedScreen: Shows a confirmation screen for when scores are saved
#viewScoresScreen: Shows the highscores

#The menuChoice if has been expanded to include the aforementioned functions
#updateScores is called when the player wins
#Now the program asks for the player's name in the console before starting

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


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

#Function that updates the highscores in memory
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 

#Function that saves the scores to a file named score.txt
def saveScores(highScores):
   f = open("score.txt","w")
   for score in highScores:
       f.write(score[0] + " " + str(score[1]) + "\n")
   f.close()

#Function that reads the scores from score.txt
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)]

#Displays a score saved screen for a small amount of time
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)

#Displays the highscores
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)

#Before the game initiates, we ask the player to enter their name in the console
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

#Variable to determine if the player won
won = False

#List to store the three best scores
highScores = readScores()

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

        #Added the choices for saving and viewing scores 
        if menuChoice != 1:
                menuChoice = renderMenu()
                if menuChoice == 1:
                    score = 0
                    won = False
                    timeEllapsed = 0
                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:
                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

                timeEllapsed += clock.get_time()
        else:
                finalFont = pygame.font.SysFont("monospace", 50)
                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
                        #Update the highscores if the user won
                        highScores = updateScores(highScores,(name,score))
                        screen.blit(label, (0,0))
                
                #Return to the menu after the game has ended so the player can choose to play again, save or view scores
                menuChoice = 0
                #Display the current message (Win or Lose) and wait a while so the user can read it
                pygame.display.flip()
                clock.tick(60)
                pygame.time.delay(3000)
                continue

        #Update the screen
        pygame.display.flip()
        #Set the FPS value to 60
        clock.tick(60)
pygame.quit()