Timed Mental Maths Game

Intermediate Python Project

Timed Mental Maths Game

The timed mental maths game is aimed at testing our maths capabilities. The game is in five levels and at each level, the player’s life increases and the number of seconds increases by 5 seconds for each level. Also, the difficulty increases for each level.

Functionalities of the Game

  1. The game is in 5 levels (Very Easy, Easy, Medium, Hard, and Very Hard).

  2. The player starts with 3 lives. When the player enters the Easy and Medium levels, his/her life increases by 2; at Hard, it increases by 3, and at Very Hard, it increases by 5.

  3. JSON module is used to store the highest score and the highest scorer’s name. It is also used to retrieve the data stored at the start of every game.

  4. The time module is used to time the gameplay. The player has 10 seconds for each question at the Easy level. The time increases by 5 seconds for each level.

  5. Only basic operations (multiplication, division, addition, and subtraction) are carried out.

  6. The random module would be used to generate random operands and operators for each question.

  7. Every division is made to produce an integer. In other words, no division operation would result in a float value.

  8. At the start of every game, the highest score is displayed

  9. At the end of every game, the player's score is displayed. If the player's score is greater than the highest score, his/her score would be saved in the high score file.

  10. Each question score is 10 x the remaining seconds.

Full Code

This is the full code. It would be explained in bits throughout this article.

import random   # For randint.
import json     # For saving the highest score.
import time     # To calculate the time spent on each question.

# Dummy score when the game has not been played at all.
dummy_score = {'Player Name': 'Anonymous', 'Score': 0}

filename = 'highest_score.json'

# Read into the high score file to print the high score and high scorer's name.
try:
    with open(filename) as highest_score:
        high_score = json.load(highest_score)

    highest_score.close()

    # The try ... except block is used when the game is yet to be played and the high score file has not been created.
    # When the high score has not been created, the dummy score is used.
except FileNotFoundError:
    high_score = dummy_score

# Print High Score.
# '\033[7;32;40m' - Green background and black text.
print('\033[7;32;40m' + 'High score' '\033[0;0m\n' + high_score['Player Name'] + ': ' + str(high_score['Score']) + '\n')

player_name = input("Name: ").title()

# List of operators used in the game
operators = ['*', '/', '+', '-']
random_operator = operators[random.randint(0, 3)]

player_lives = 3
player_score = 0

# '\033[0;31;40m' - Black background, red text.
print('\033[0;31;40m' + 'Life: ' + str(player_lives) + '\033[0;0m')


def operation(random_op1, random_op2, game_seconds):
    """Checks if the player's answer is correct within the given time."""

    # To change the global variable, the 'global' keyword is used.
    global player_lives
    global player_score
    start_time = time.time()
    player_answer = ""

    print('You have ' + str(game_seconds) + ' seconds')

    while player_answer == "":
        # try ... except to catch ValueError
        try:
            # This prints out the algebraic operation to the screen
            print(str(random_op1) + str(random_operator) + str(random_op2))
            player_answer = int(input("Answer: "))
        except ValueError:
            print('\033[0;31;40m' + 'Invalid input! Input an integer.' + '\033[0;0m')
            continue

    # Check if the question was answered before the time elapsed.
    time_used = int(time.time() - start_time)
    remaining_sec = game_seconds - time_used

    if remaining_sec <= 0:
        print('Time up!\n')
        player_lives = player_lives - 1
        print('\033[0;31;40m' + 'Life: ' + str(player_lives) + '\033[0;0m')

    # MULTIPLICATION
    elif random_operator == operators[0]:
        if player_answer == random_op1 * random_op2:
            player_score = player_score + (remaining_sec * 10)
            print('Player score:', player_score, '\n')

        else:
            print('Wrong! The answer is ' + str(random_op1 * random_op2) + '\n')
            player_lives = player_lives - 1
            print('\033[0;31;40m' + 'Life: ' + str(player_lives) + '\033[0;0m')

    # DIVISION
    elif random_operator == operators[1]:
        if player_answer == random_op1 / random_op2:
            player_score = player_score + (remaining_sec * 10)
            print('Player score:', player_score, '\n')

        else:
            print('Wrong! The answer is ' + str(int(random_op1 / random_op2)) + '\n')
            player_lives = player_lives - 1
            print('\033[0;31;40m' + 'Life: ' + str(player_lives) + '\033[0;0m')

    # ADDITION
    elif random_operator == operators[2]:
        if player_answer == random_op1 + random_op2:
            player_score = player_score + (remaining_sec * 10)
            print('Player score:', player_score, '\n')

        else:
            print('Wrong! The answer is ' + str(random_op1 + random_op2) + '\n')
            player_lives = player_lives - 1
            print('\033[0;31;40m' + 'Life: ' + str(player_lives) + '\033[0;0m')

    # SUBTRACTION
    elif random_operator == operators[3]:
        if player_answer == random_op1 - random_op2:
            player_score = player_score + (remaining_sec * 10)
            print('Player score:', player_score, '\n')

        else:
            print('Wrong! The answer is ' + str(random_op1 - random_op2) + '\n')
            player_lives = player_lives - 1
            print('\033[0;31;40m' + 'Life: ' + str(player_lives) + '\033[0;0m')


# The game continues so long the player's life does not deplete to 0.
while player_lives > 0:

    # LEVEL: Very Easy
    game_sec1 = 10

    while player_score < 1500 and player_lives != 0:
        random_operator = operators[random.randint(0, 3)]
        random_operand1 = random.randint(0, 9)
        random_operand2 = random.randint(1, 9)  # This starts from 1 to avoid ZeroDivisionError.

        # This ensures that the generated numbers are always divisible (that is, they don't give decimal numbers).
        if (random_operator == operators[1]) and (random_operand1 % random_operand2 != 0):
            continue

        # Calling the operation function.
        operation(random_operand1, random_operand2, game_sec1)

    # LEVEL: Easy
    if player_score > 1500:
        player_lives = player_lives + 2
        print('\033[0;31;40m' + 'Life: ' + str(player_lives) + '\033[0;0m')

    while player_score >= 1500 and player_score < 3500 and player_lives != 0:
        random_operator = operators[random.randint(0, 3)]
        random_operand3 = random.randint(0, 99)
        random_operand4 = random.randint(1, 20)  # This starts from 1 to avoid ZeroDivisionError.
        game_sec2 = 15

        # This ensures that the generated numbers are always divisible (that is, they don't give decimal numbers).
        if (random_operator == operators[1]) and (random_operand3 % random_operand4 != 0):
            continue

        # Calling the operation function.
        operation(random_operand3, random_operand4, game_sec2)

    # LEVEL: Medium
    if player_score > 3500:
        player_lives = player_lives + 2
        print('\033[0;31;40m' + 'Life: ' + str(player_lives) + '\033[0;0m')

    while player_score >= 3500 and player_score < 10000 and player_lives != 0:
        random_operator = operators[random.randint(0, 3)]
        random_operand5 = random.randint(0, 99)
        random_operand6 = random.randint(1, 50)  # This starts from 1 to avoid ZeroDivisionError.
        game_sec3 = 20

        # This ensures that the generated numbers are always divisible (that is, they don't give decimal numbers).
        if (random_operator == operators[1]) and (random_operand5 % random_operand6 != 0):
            continue

        # Calling the operation function.
        operation(random_operand5, random_operand6, game_sec3)

    # LEVEL: Hard
    if player_score > 10000:
        player_lives = player_lives + 3
        print('\033[0;31;40m' + 'Life: ' + str(player_lives) + '\033[0;0m')

    while player_score >= 10000 and player_score < 25000 and player_lives != 0:
        random_operator = operators[random.randint(0, 3)]
        random_operand7 = random.randint(0, 999)
        random_operand8 = random.randint(1, 100)  # This starts from 1 to avoid ZeroDivisionError.
        game_sec4 = 25

        # This ensures that the generated numbers are always divisible (that is, they don't give decimal numbers).
        if (random_operator == operators[1]) and (random_operand7 % random_operand8 != 0):
            continue

        # Calling the operation function
        operation(random_operand7, random_operand8, game_sec4)

    # LEVEL: Very Hard
    if player_score > 25000:
        player_lives = player_lives + 5
        print('\033[0;31;40m' + 'Life: ' + str(player_lives) + '\033[0;0m')

    while player_score > 25000 and player_lives != 0:
        random_operator = operators[random.randint(0, 3)]
        random_operand9 = random.randint(0, 999)
        random_operand10 = random.randint(1, 400)  # This starts from 1 to avoid ZeroDivisionError.
        game_sec5 = 30

        # This ensures that the generated numbers are always divisible (that is, they don't give decimal numbers).
        if (random_operator == operators[1]) and (random_operand9 % random_operand10 != 0):
            continue

        # Calling the operation function.
        operation(random_operand9, random_operand10, game_sec5)

if player_lives == 0:
    print('Game Over!\n')
    print('\033[0;33;40m' + 'Your score: ' + str(player_score) + '\033[0;0m')

# Check if the player’s score is greater than the high score
if player_score > high_score['Score']:
    high_score['Score'] = player_score
    high_score['Player Name'] = player_name

    with open(filename, 'w') as highest_score:
        json.dump(high_score, highest_score)

    highest_score.close()

    print('\033[30;32;40m' + 'New High Score!' + '\033[0;0m')

# TODO: Break out of the question if the time elapses before the player enters an answer.

Importing Modules

First, we import the modules which we would use in the code. The random module is used to generate operands and shuffle the operators. The json module is used to save the highest score and the highest scorer’s name, and also to load these data at the start of each game. The time module is used to know the number of seconds it took the player to answer the question.

import random
import json
import time

Display High Score

We would create a dictionary with a dummy score, dummy_score, which would be displayed if the script is being run for the first time. If the game had already been played, we open the file where the high score has been saved and load it using high_score = json.load(highest_score), and we print it on the screen. The try except block is used to handle FileNotFoundError, which would occur if the code is just being run for the first time.

dummy_score = {'Player Name': 'Anonymous', 'Score': 0}

filename = 'highest_score.json'

try:
    with open(filename) as highest_score:
        high_score = json.load(highest_score)

    highest_score.close()

except FileNotFoundError:
    high_score = dummy_score

print('\033[7;32;40m' + 'High score' '\033[0;0m\n' + high_score['Player Name'] + ': ' + str(high_score['Score']) + '\n')

Game Data

We would create variables, which would store the player’s name, assign the number of lives the player is to start with, and store the player’s score. We also create a list containing operators as strings which we would shuffle.

player_name = input("Name: ").title()

operators = ['*', '/', '+', '-']
random_operator = operators[random.randint(0, 3)]

player_lives = 3
player_score = 0

print('\033[0;31;40m' + 'Life: ' + str(player_lives) + '\033[0;0m')

Operation Function

We would define the function, operation(), which we would use to check if a player answers the questions correctly and on time. We make use of the global keyword to change the values of the global variables (player_lives and player_score). If the global keyword is not used, once the function ends, the changes it made to the global variables disappear. We create a variable, start_time, which saves the time the question was asked. We also create another variable which would have an empty string, which we would use to exit a while loop when the player answers the question. We then print the number of seconds the player has for each question at that level.

def operation(random_op1, random_op2, game_seconds):
    """Checks if the player's answer is correct within the given time."""

    # To change the global variable, the 'global' keyword is used.
    global player_lives
    global player_score
    start_time = time.time()
    player_answer = ""

    print('You have ' + str(game_seconds) + ' seconds')

Calculating Number of Seconds Used in Answering Questions

We print the question on the screen, and the player is prompted to answer it. Once the player answers the question, we determine the number of seconds the player used to answer the question from which we determine if any seconds remained from the specified number of seconds the player was meant to use to answer the question. If there are no seconds remaining, we notify that player of the excess time spent and one of his/her lives is deducted. A try except block is used to handle ValueError when the user inputs a value other than an integer.

    while player_answer == "":
        # try ... except to catch ValueError
        try:
            # This prints out the algebraic operation to the screen
            print(str(random_op1) + str(random_operator) + str(random_op2))
            player_answer = int(input("Answer: "))
        except ValueError:
            print('\033[0;31;40m' + 'Invalid input! Input an integer.' + '\033[0;0m')
            continue

    # Check if the question was answered before the time elapsed.
    time_used = int(time.time() - start_time)
    remaining_sec = game_seconds - time_used

    if remaining_sec <= 0:
        print('Time up!\n')
        player_lives = player_lives - 1
        print('\033[0;31;40m' + 'Life: ' + str(player_lives) + '\033[0;0m')

Check if the Player's Answer is Correct

If the player answers the question within the specified time, we check if the player’s answer is correct based on the operation carried out. If the answer is correct, we increase the player’s score by (10 x the remaining seconds); else, we prompt the player of the correct answer and deduct his/her life.

    # MULTIPLICATION
    elif random_operator == operators[0]:
        if player_answer == random_op1 * random_op2:
            player_score = player_score + (remaining_sec * 10)
            print('Player score:', player_score, '\n')

        else:
            print('Wrong! The answer is ' + str(random_op1 * random_op2) + '\n')
            player_lives = player_lives - 1
            print('\033[0;31;40m' + 'Life: ' + str(player_lives) + '\033[0;0m')

    # DIVISION
    elif random_operator == operators[1]:
        if player_answer == random_op1 / random_op2:
            player_score = player_score + (remaining_sec * 10)
            print('Player score:', player_score, '\n')

        else:
            print('Wrong! The answer is ' + str(int(random_op1 / random_op2)) + '\n')
            player_lives = player_lives - 1
            print('\033[0;31;40m' + 'Life: ' + str(player_lives) + '\033[0;0m')

    # ADDITION
    elif random_operator == operators[2]:
        if player_answer == random_op1 + random_op2:
            player_score = player_score + (remaining_sec * 10)
            print('Player score:', player_score, '\n')

        else:
            print('Wrong! The answer is ' + str(random_op1 + random_op2) + '\n')
            player_lives = player_lives - 1
            print('\033[0;31;40m' + 'Life: ' + str(player_lives) + '\033[0;0m')

    # SUBTRACTION
    elif random_operator == operators[3]:
        if player_answer == random_op1 - random_op2:
            player_score = player_score + (remaining_sec * 10)
            print('Player score:', player_score, '\n')

        else:
            print('Wrong! The answer is ' + str(random_op1 - random_op2) + '\n')
            player_lives = player_lives - 1
            print('\033[0;31;40m' + 'Life: ' + str(player_lives) + '\033[0;0m')

Gameplay and Game Levels

The game starts with 10 seconds per question for the Very Easy level, which increases by 5 seconds for each level. We would generate a random operator and operands for the game, which we would pass to the operation() function. Using if (random_operator == operators[1]) and (random_operand1 % random_operand2 != 0):, we ensure that every division operation is gives an integer answer. When the player is equal to or greater than 1500, he/she transitions to the Easy level and the player’s life increase by 2. When the player’s score is 3500, 10000, and 25000, he/she transitions to the Medium, Hard, and Very Hard levels respectively. At each level, we change the range at which the random operands are generated. We ensure that one operand range starts from 1 to prevent ZeroDivisionError.

while player_lives > 0:

    # LEVEL: Very Easy
    game_sec1 = 10

    while player_score < 1500 and player_lives != 0:
        random_operator = operators[random.randint(0, 3)]
        random_operand1 = random.randint(0, 9)
        random_operand2 = random.randint(1, 9) 

        if (random_operator == operators[1]) and (random_operand1 % random_operand2 != 0):
            continue

        operation(random_operand1, random_operand2, game_sec1)

    # LEVEL: Easy
    if player_score > 1500:
        player_lives = player_lives + 2
        print('\033[0;31;40m' + 'Life: ' + str(player_lives) + '\033[0;0m')

    while player_score >= 1500 and player_score < 3500 and player_lives != 0:
        random_operator = operators[random.randint(0, 3)]
        random_operand3 = random.randint(0, 99)
        random_operand4 = random.randint(1, 20) 
        game_sec2 = 15

        if (random_operator == operators[1]) and (random_operand3 % random_operand4 != 0):
            continue

        operation(random_operand3, random_operand4, game_sec2)

    # LEVEL: Medium
    if player_score > 3500:
        player_lives = player_lives + 2
        print('\033[0;31;40m' + 'Life: ' + str(player_lives) + '\033[0;0m')

    while player_score >= 3500 and player_score < 10000 and player_lives != 0:
        random_operator = operators[random.randint(0, 3)]
        random_operand5 = random.randint(0, 99)
        random_operand6 = random.randint(1, 50) 
        game_sec3 = 20

        if (random_operator == operators[1]) and (random_operand5 % random_operand6 != 0):
            continue

        operation(random_operand5, random_operand6, game_sec3)

    # LEVEL: Hard
    if player_score > 10000:
        player_lives = player_lives + 3
        print('\033[0;31;40m' + 'Life: ' + str(player_lives) + '\033[0;0m')

    while player_score >= 10000 and player_score < 25000 and player_lives != 0:
        random_operator = operators[random.randint(0, 3)]
        random_operand7 = random.randint(0, 999)
        random_operand8 = random.randint(1, 100)  # This starts from 1 to avoid ZeroDivisionError.
        game_sec4 = 25

        if (random_operator == operators[1]) and (random_operand7 % random_operand8 != 0):
            continue

        operation(random_operand7, random_operand8, game_sec4)

    # LEVEL: Very Hard
    if player_score > 25000:
        player_lives = player_lives + 5
        print('\033[0;31;40m' + 'Life: ' + str(player_lives) + '\033[0;0m')

    while player_score > 25000 and player_lives != 0:
        random_operator = operators[random.randint(0, 3)]
        random_operand9 = random.randint(0, 999)
        random_operand10 = random.randint(1, 400) 
        game_sec5 = 30

        if (random_operator == operators[1]) and (random_operand9 % random_operand10 != 0):
            continue

        operation(random_operand9, random_operand10, game_sec5)

Game Over

When the player’s lives have depleted to zero, the game ends and we print his/her score.

if player_lives == 0:
    print('Game Over!\n')
    print('\033[0;33;40m' + 'Your score: ' + str(player_score) + '\033[0;0m')

New High Score?

We check the player’s score against the score saved in the high score file; if it is greater, we open the high score file and we overwrite the file with the new high score and the player’s name. We close the file and prompt the player of their achievement.

if player_score > high_score['Score']:
    high_score['Score'] = player_score
    high_score['Player Name'] = player_name

    with open(filename, 'w') as highest_score:
        json.dump(high_score, highest_score)

    highest_score.close()

    print('\033[30;32;40m' + 'New High Score!' + '\033[0;0m')

Room For Improvement

At this stage of my python journey, I find it difficult to break out of the while loop (requiring the player to answer the question before it breaks) using a timeout. I would prefer if I could break out of the loop once the time allocated to the question elapses. If you have any idea, please let me know in the comment section. Thank you.

# TODO: Break out of the question if the time elapses before the player enters an answer.

Special thanks to Edwin who gave me the idea for this project.

Note: You are not to use a calculator to play the game.

Play the game on Replit.