# Final Project: The Skill Test
# INITIALISE
import pygame
import os
import random
os.environ["SDL_AUDIODRIVER"] = "dsp"
pygame.init()
# Generate window
WINDOW_WIDTH = 600
WINDOW_HEIGHT = 400
window = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
pygame.display.set_caption("The Skill Test")
# Initialise screen
screen = 0
# Colours
BLACK = (0, 0, 0)
PINK = (255, 0, 255)
PURPLE = (150, 0, 150)
BLUE = (0, 0, 255)
CYAN = (0, 255, 255)
GREEN = (0, 200, 0)
YELLOW = (255, 255, 0)
ORANGE = (255, 128, 0)
RED = (200, 0, 0)
WHITE = (255, 255, 255)
GREY = (128, 128, 128)
# Fonts
font = pygame.font.SysFont(None, 36)
gamefont = pygame.font.SysFont(None, 20)
bigfont = pygame.font.SysFont(None, 72)
# Initialise grade system
grades = [0, 0, 0, 0, 0]
grade_names = ["Rubbish", "F", "E", "D", "C", "B", "A", "S", "!"]
grade_colours = [WHITE, RED, ORANGE, YELLOW, GREEN, CYAN, BLUE, PURPLE, PINK]
currentgrade = 9
# Initialise games and variables (prevents 'not defined' errors)
# Typing system
letters = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]
typedtext = ""
text = ""
# Grade thresholds
thresholds2 = [-1.0, 30.0, 60.0, 80.0, 100.0, 120.0, 135.0, 150.0, 160.0]
thresholds3 = [0, 150, 300, 450, 600, 750, 900, 950, 990]
thresholds4 = [0, 12, 18, 24, 30, 35, 40, 50, 64]
thresholds5 = [3.00, 0.50, 0.40, 0.32, 0.28, 0.24, 0.20, 0.16, 0.12]
# Game 1
score1 = 0
# Game 2
time2 = 180.0
# Game 3
score3 = 0
skips3 = 2
playerX = 1
playerY = 1
redsquares = []
greensquares = []
# Game 4
score4 = 0
time4 = 0.0
num1 = 1
num2 = 1
answer = 2
# Game 5
framesToGreen = 40
framesWaited = 0
clickable = 0
time5 = 0.00
exitDelay = 0
clicked = 0
# Average grade
averageGrade = 1
# Initialise clock
clock = pygame.time.Clock()
FPS = 60
gametick = 0
obstick = 0
# DEFINITIONS
def createRedSquares(): # Generate red squares (game 3)
for index in range(300):
xPos = random.randint(20, 1600) // 2
yPos = random.randint(1, 4)
redsquares.append([xPos, yPos])
def createGreenSquares(): # Generate green squares (game 3)
for index in range(40):
xPos = random.randint(20, 1600) // 2
yPos = random.randint(1, 4)
if [xPos, yPos] in redsquares: # Prevents overlap between green and red squares
while [xPos, yPos] in redsquares:
xPos = random.randint(20, 1600) // 2
yPos = random.randint(1, 4)
greensquares.append([xPos, yPos])
def generateNewQuestion(): # Generates a new question (game 4)
global num1, num2, answer, typedtext, operator
typedtext = ""
operator = random.randint(1, 4)
# Generate addition question
if operator == 1:
num1 = int(round(random.uniform(1, 1.2**(score4+4)), 0))
num2 = int(round(random.uniform(1, 1.2**(score4+4)), 0))
answer = num1 + num2
# Generate subtraction question
if operator == 2:
num1 = int(round(random.uniform(1, 1.2**(score4+4)), 0))
num2 = int(round(random.uniform(1, 1.2**(score4+4)), 0))
answer = num1 - num2
# Generate multiplication question
if operator == 3:
num1 = int(round(random.uniform(1, 1.1**(score4+4)), 0))
num2 = int(round(random.uniform(1, 1.1**(score4+4)), 0))
answer = num1 * num2
# Generate division question
if operator == 4:
num1 = int(round(random.uniform(1, 1.1**(score4+4)), 0))
num2 = int(round(random.uniform(1, 1.1**(score4+4)), 0))
answer = num1 * num2
# GAME LOOP
running = True
while running:
# Events
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# CONTROLS
# Typing (game 2)
if screen == 2:
if event.type == pygame.KEYDOWN and time2 <= 179.9 and (not event.key == pygame.K_BACKSPACE) and (not (len(typedtext)==50 and event.key == pygame.K_SPACE)):
typedtext += event.unicode
typedtext = typedtext.upper()
# Check for mistake
if len(typedtext) > 0 and screen == 2:
if not typedtext[len(typedtext)-1] == text[len(typedtext)-1]:
time2 += -3.0
time2 = round(time2, 1)
typedtext = typedtext[:len(typedtext)-1]
# Player Controls (screen 3)
if screen == 3:
if event.type == pygame.KEYDOWN:
# Movement up and down
if event.key == pygame.K_UP:
if playerY < 4:
playerY += 1
if event.key == pygame.K_DOWN:
if playerY > 1:
playerY += -1
# Green square click
if event.key == pygame.K_SPACE:
if spacebarPressedThisTick == 0:
spacebarPressedThisTick = 1
greenSquareFound = 0
for index in range(40):
if greensquares[index][0]-1 == playerX and greensquares[index][1] == playerY:
score3 += 10 # Reward for clicking space on a green square
greenSquareFound = 1 # Prevents penalty
if greenSquareFound == 0:
score3 += -15 # Penalty for clicking space when not on a green square
# Use skip
if event.key == pygame.K_RIGHT:
if skips3 > 0:
skips3 += -1
playerX += 5
# Typing (game 4)
if screen == 4:
if event.type == pygame.KEYDOWN and event.key == pygame.K_BACKSPACE: # Allows backspaces
if len(typedtext) > 0:
typedtext = typedtext[:len(typedtext)-1]
elif event.type == pygame.KEYDOWN and event.key == pygame.K_MINUS:
if len(typedtext) == 0:
typedtext += "-"
elif event.type == pygame.KEYDOWN and event.unicode.isdigit(): # Prevents operators being entered for cheating
typedtext += event.unicode
typedtext = typedtext.upper()
# Answer submission (game 4)
if event.type == pygame.KEYDOWN and event.key == pygame.K_RETURN:
# Addition
if operator == 1:
if typedtext == str(answer):
score4 += 1
time4 += 10.0
time4 = round(time4, 1)
generateNewQuestion()
else:
time4 += -10.0
time4 = round(time4, 1)
generateNewQuestion()
# Subtraction
elif operator == 2:
if typedtext == str(answer):
score4 += 1
time4 += 10.0
time4 = round(time4, 1)
generateNewQuestion()
else:
time4 += -10.0
time4 = round(time4, 1)
generateNewQuestion()
# Multiplication
elif operator == 3:
if typedtext == str(answer):
score4 += 1
time4 += 10.0
time4 = round(time4, 1)
generateNewQuestion()
else:
time4 += -10.0
time4 = round(time4, 1)
generateNewQuestion()
# Division
elif operator == 4:
if typedtext == str(num1):
score4 += 1
time4 += 10.0
time4 = round(time4, 1)
generateNewQuestion()
else:
time4 += -10.0
time4 = round(time4, 1)
generateNewQuestion()
# Clicking (game 5)
if screen == 5:
if event.type == pygame.KEYDOWN:
if clickable == 1:
# Calculate grade after clicked
for index in range(9):
if time5 > 0.12:
if time5 > thresholds5[index]:
currentgrade = index
break
else:
currentgrade = 9
clicked = 1
else:
screen = 0
# Exit (end of test)
if screen == 0:
if event.type == pygame.KEYDOWN and event.key == pygame.K_0:
if 0 not in grades and averageGrade >= 5: # Exit only allowed with a C grade or above
exit()
# OPEN GAMES
# Game Selection (screen 0)
if event.type == pygame.KEYDOWN and screen == 0:
# Open game 1
if event.key == pygame.K_1:
screen = 1
time = 600
score1 = 50000
cubeY = 190
# Open game 2
elif event.key == pygame.K_2:
screen = 2
time2 = 180.0
chars = []
text = ""
typedtext = ""
# Create text string
for index in range(100):
# Add letters
if index == 0 or index == 99 or index == 49 or index == 50 or chars[index-1] == " ": # Prevent space for first char, last char, between lines and after a space
letterselect = random.randint(1, 26)
chars.append(letters[letterselect-1])
text = " ".join([text + chars[index]])
else:
spacerand = random.randint(1, 7) # 1 in 7 chance for space
lastTen = text[len(text)-10:] # Guarantee space at least once every 10 letters
if spacerand == 6 or not " " in lastTen :
letterselect = " "
chars.append(" ")
text = " ".join([text + chars[index]])
else:
letterselect = random.randint(1, 26)
chars.append(letters[letterselect-1])
text = " ".join([text + chars[index]])
# Open game 3
if event.key == pygame.K_3:
screen = 3
score3 = 0
playerX = 1
playerY = 1
redsquares = []
greensquares = []
skips3 = 2
createRedSquares()
createGreenSquares()
# Open game 4
elif event.key == pygame.K_4:
screen = 4
score4 = 0
time4 = 10.0
operator = 1
num1 = 1
num2 = 1
answer = 1+1
typedtext = ""
# Open game 5
elif event.key == pygame.K_5:
screen = 5
time5 = 0.00
framesToGreen = random.randint(60, 360)
framesWaited = 0
clickable = 0
exitDelay = 0
clicked = 0
else:
pass
# Key Detection
keys = pygame.key.get_pressed()
# TEXT RENDERING
# Initialise corner grade render (screens 1-5)
if screen > 0:
if screen == 5: # Render grade only after button clicked on game 5
if exitDelay > 0:
gradeln = font.render(grade_names[currentgrade-1], True, grade_colours[currentgrade-1])
else:
gradeln = font.render("", True, WHITE)
else:
gradeln = font.render(grade_names[currentgrade-1], True, grade_colours[currentgrade-1]) # Show grades throughout game on games 1-4
else:
gradeln = font.render("", True, WHITE) # Not shown on main menu
# Average grade render (screen 0)
if screen == 0: # Only shows on main menu
if 0 not in grades: # Only shows after all games completed
averageln1 = gamefont.render("AVERAGE:", True, WHITE)
averageln2 = bigfont.render(grade_names[averageGrade-1], True, grade_colours[averageGrade-1])
# Player passes if their average grade is C or above
if averageGrade >= 5:
averageln3 = gamefont.render("YOU PASS: Replay the games using keys 1-5 or press 0 to exit The Skill Test", True, WHITE)
else:
averageln3 = gamefont.render("YOU FAIL: You need a C or above to complete the game. Replay the games using keys 1-5", True, WHITE)
else:
averageln1 = gamefont.render("", True, WHITE)
averageln2 = bigfont.render("", True, WHITE)
averageln3 = gamefont.render("", True, WHITE)
else:
averageln1 = gamefont.render("", True, WHITE)
averageln2 = bigfont.render("", True, WHITE)
averageln3 = gamefont.render("", True, WHITE)
# TEXT
# Screen 0 (Main Menu) Text
if screen == 0:
# Games List
gameln = font.render("The Skill Test | Press a number key to play", True, WHITE)
line1 = font.render("1 - Float", True, WHITE)
line2 = font.render("2 - Typewriter", True, WHITE)
line3 = font.render("3 - Obstacle Course", True, WHITE)
line4 = font.render("4 - Examination", True, WHITE)
line5 = font.render("5 - Reaction", True, WHITE)
creditln = font.render("BOYLE, Kainos Entry | Aug 2026", True, WHITE)
# Screen 1 (Float) Text
elif screen == 1:
gameln = font.render("Float | Press SPACE to float up", True, WHITE)
line1 = font.render("Stay as close to the green line as you can", True, WHITE)
line2 = font.render("You lose points faster the further you are from the", True, WHITE)
line3 = font.render("line", True, WHITE)
line4 = font.render("", True, WHITE)
line5 = font.render("", True, WHITE)
creditln = font.render(f"Score: {score1}", True, WHITE)
# Screen 2 (Typewriter) Text
elif screen == 2:
gameln = font.render("Typewriter | Type the gibberish below in 3 minutes", True, WHITE)
line1 = font.render("Each typo costs 3 seconds", True, WHITE)
line2 = font.render("", True, WHITE)
line3 = font.render("", True, WHITE)
line4 = font.render("", True, WHITE)
line5 = font.render("", True, WHITE)
creditln = font.render(f"Time: {time2}", True, WHITE)
lettersln1 = gamefont.render(text[:50], True, WHITE)
lettersln2 = gamefont.render(text[50:], True, WHITE)
typedln1 = gamefont.render(typedtext[:50], True, GREEN)
if len(typedtext) > 50:
typedln2 = gamefont.render(typedtext[50:], True, GREEN)
else:
typedln2 = gamefont.render("", True, GREEN)
# Screen 3 (Obstacle Course) Text
elif screen == 3:
gameln = font.render("Obstacle Course | Use UP and DOWN to move", True, WHITE)
line1 = font.render("Press SPACE when on green squares, avoid red", True, WHITE)
line2 = font.render("Clicking space at the wrong time costs points", True, WHITE)
line3 = font.render("Press RIGHT to skip forward", True, WHITE)
line4 = font.render("", True, WHITE)
line5 = font.render("", True, WHITE)
creditln = font.render(f"Score: {score3} | Skips: {skips3}", True, WHITE)
# Screen 4 (Examination) Text
elif screen == 4:
gameln = font.render("Examination | Get the questions right", True, WHITE)
line1 = font.render("Correct answers give 10 seconds", True, WHITE)
line2 = font.render("Wrong answers take away 10 seconds", True, WHITE)
line3 = font.render("Press RETURN to submit your answer", True, WHITE)
line4 = font.render("", True, WHITE)
line5 = font.render("", True, WHITE)
creditln = font.render(f"Score: {score4} | Time: {time4}", True, WHITE)
lettersln1 = gamefont.render(text[:50], True, WHITE)
lettersln2 = gamefont.render(text[50:], True, WHITE)
typedln1 = gamefont.render(typedtext[:50], True, GREEN)
if len(typedtext) > 50:
typedln2 = gamefont.render(typedtext[50:], True, GREEN)
else:
typedln2 = gamefont.render("", True, GREEN)
elif screen == 5:
gameln = font.render("Reaction | Press ANY KEY when green", True, WHITE)
line1 = font.render("Don't press too early", True, WHITE)
line2 = font.render("", True, WHITE)
line3 = font.render("", True, WHITE)
line4 = font.render("", True, WHITE)
line5 = font.render("", True, WHITE)
creditln = font.render(f"Time: {round(time5, 2)}", True, WHITE)
# GAME RENDERING
# Black Window
window.fill(BLACK)
# Render text lines
window.blit(gameln, (10, 10))
window.blit(line1, (10, 40))
window.blit(line2, (10, 70))
window.blit(line3, (10, 100))
window.blit(line4, (10, 130))
window.blit(line5, (10, 160))
window.blit(creditln, (10, 360))
# Render average grade
window.blit(averageln1, (10, 230))
window.blit(averageln2, (10, 250))
window.blit(averageln3, (10, 300))
# Render grades (screen 0)
if screen == 0:
for index in range(5):
if grades[index] > 0:
grade_text = grade_names[grades[index]-1]
window.blit(font.render(grade_text, True, grade_colours[grades[index]-1]), (300, 40+(30*(index))))
else:
window.blit(font.render("x", True, GREY), (300, 40+(30*(index))))
# Render current grade (screens 1-5)
else:
window.blit(gradeln, (480, 360))
# GAME RUNNING
# Screen 1 (Float)
if screen == 1:
# Green line
pygame.draw.rect(window, GREEN, (0, 190, 600, 20))
# Moving block
pygame.draw.rect(window, WHITE, (600-time, cubeY, 20, 20))
if gametick == 6:
time += -1
if time < 594 and time > 0:
if keys[pygame.K_SPACE]:
if cubeY >= 140:
cubeY += -1
else:
if cubeY <= 240:
cubeY += 1
if cubeY > 190:
score1 += -(cubeY - 190)
if cubeY < 190:
score1 += (cubeY - 190)
if time < 0:
cubeY = 190
if time < -20 or score1 < 1:
if time < 0 and currentgrade > grades[0]:
grades[0] = currentgrade
screen = 0
# Grade
if score1 >= 20000:
currentgrade = (score1 // 4000) - 3
else:
currentgrade = 1
# Screen 2 (Typewriter)
if screen == 2:
window.blit(lettersln1, (10, 130))
window.blit(lettersln2, (10, 160))
window.blit(typedln1, (10, 230))
window.blit(typedln2, (10, 260))
# Timer
if gametick == 6:
time2 += -0.1
time2 = round(time2, 1)
# Grade
for index in range(9):
if time2 < 155.0:
if time2 < thresholds2[index]:
currentgrade = index
break
else:
currentgrade = 9
if time2 < 0.0 or len(typedtext) == 100:
if len(typedtext) == 100 and currentgrade > grades[1]:
grades[1] = currentgrade
screen = 0
# Screen 3 (Obstacle Course)
# Course Area
if screen == 3:
pygame.draw.rect(window, BLUE, (0, 160, 600, 80))
# Player
pygame.draw.rect(window, WHITE, (20, 240-(20*playerY), 20, 20))
# Screen Scroll
if obstick == 0:
playerX += 1
# Red Squares
for index in range(150):
pygame.draw.rect(window, RED, (((redsquares[index][0]-playerX)*(20)), (240-(redsquares[index][1]*20)), 20, 20))
# Green Squares
for index in range(40):
pygame.draw.rect(window, GREEN, (((greensquares[index][0]-playerX)*(20)), (240-(greensquares[index][1]*20)), 20, 20))
# Check if red squares hit
for index in range(150):
if redsquares[index][0]-1 == playerX and redsquares[index][1] == playerY:
if currentgrade > grades[2]:
grades[2] = currentgrade
screen = 0
# Check if red squares passed
if obstick == 0:
for index in range(150):
if redsquares[index][0] == playerX:
score3 += 4
# Check if course completed
if playerX > 810:
if currentgrade > grades[2]:
grades[2] = currentgrade
screen = 0
# Grade
for index in range(9):
if score3 < 990:
if score3 < thresholds3[index]:
currentgrade = index
break
else:
currentgrade = 9
# Prevent negative score
if score3 < 0:
score3 = 0
# Screen 4 (Examination)
if screen == 4:
# Question Render
window.blit(lettersln1, (10, 130))
window.blit(lettersln2, (10, 160))
window.blit(typedln1, (10, 230))
window.blit(typedln2, (10, 260))
if operator == 1:
text = f"{num1} + {num2}"
if operator == 2:
text = f"{num1} - {num2}"
if operator == 3:
text = f"{num1} x {num2}"
if operator == 4:
text = f"{answer} / {num2}"
# Timer
if gametick == 6:
time4 += -0.1
time4 = round(time4, 1)
# Grade
for index in range(9):
if score4 < 100:
if score4 < thresholds4[index]:
currentgrade = index
break
else:
currentgrade = 9
# End game
if time4 < 0:
if score4 > 0:
if currentgrade > grades[3]:
grades[3] = currentgrade
screen = 0
else:
screen = 0
# Screen 5 (Reaction)
# Check if button should be green
if screen == 5:
if clickable == 1:
if exitDelay == 0:
time5 += 1/60
else:
framesWaited += 1
if framesWaited > framesToGreen:
clickable = 1
# Render button
if clickable == 0:
pygame.draw.rect(window, RED, (250, 150, 100, 100))
else:
pygame.draw.rect(window, GREEN, (250, 150, 100, 100))
# Count down to exit after clicked
if clicked == 1:
exitDelay += 1
# Exit game if you take too long
if time5 > 3.0:
if 0 == grades[4]:
grades[4] = 0
screen = 0
# Exit game after clicked
if exitDelay > 60:
if currentgrade > grades[4]:
grades[4] = currentgrade
screen = 0
# Average grade calculation (screen 0)
averageGrade = int(round(sum(grades) / 5, 0))
# TICKS
# Clock tick
clock.tick(FPS)
# Game tick (0.1 seconds)
if gametick == 6:
gametick = 0
else:
gametick += 1
# Obstacle Course tick (screen 3)
if obstick > 20-19*((playerX-1)/1149): # Ticks are faster as game continues
obstick = 0
spacebarPressedThisTick = 0 # Allows spacebar to be pressed again
else:
obstick += 1
pygame.display.update()
pygame.quit()9 views