Last active
April 11, 2022 16:34
-
-
Save tclancy/a3f9b9b123b0fcdcebee0ac92f3591f2 to your computer and use it in GitHub Desktop.
Simple math problems for practice
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
#-*- coding: utf-8 -*- | |
from functools import reduce | |
import operator | |
from random import randrange | |
from typing import Sequence | |
PROMPT = "What's the answer? " | |
GOODBYE = "\n\n *** Thanks for playing! ***" | |
# colors - stolen from https://stackoverflow.com/a/287944/7376 | |
class bcolors: | |
HEADER = '\033[95m' | |
OKBLUE = '\033[94m' | |
OKCYAN = '\033[96m' | |
OKGREEN = '\033[92m' | |
WARNING = '\033[93m' | |
FAIL = '\033[91m' | |
ENDC = '\033[0m' | |
BOLD = '\033[1m' | |
UNDERLINE = '\033[4m' | |
def random_problem(minimum: int, maximum: int, | |
number_of_terms: int, sign: str) -> None: | |
""" | |
Generate a series (of length number_of_terms) of numbers between | |
minimum and maximum for the given problem type and check the work | |
""" | |
if number_of_terms < 2: | |
raise SystemExit(f"Looks like someone made a mistake here! " | |
f"Please fix the difficulty: terms for {sign}") | |
terms = sorted( | |
[randrange(minimum, maximum) for _ in range(number_of_terms)], | |
key=int, reverse=True | |
) | |
math_operation = { | |
"+": operator.add, | |
"x": operator.mul, | |
"/": operator.truediv, | |
"-": operator.sub | |
}[sign] | |
format_problem(sign, *terms) | |
answer = input(PROMPT) | |
attempt = 1 | |
correct_answer = reduce(lambda x, y: math_operation(x, y), terms) | |
while not get_result(answer, correct_answer): | |
answer = input(PROMPT) | |
attempt += 1 | |
if attempt % 3 == 0: | |
get_hint(sign, terms, correct_answer) | |
def get_result(attempt: str, answer: int) -> bool: | |
""" | |
See if the user got the correct answer or if they want to quit or skip | |
""" | |
try: | |
if int(attempt) == answer: | |
print(f"{bcolors.OKGREEN}Correct! Let's try another!\n{bcolors.ENDC}") | |
return True | |
except ValueError: | |
inp = attempt.lower() | |
if inp == "q": | |
raise SystemExit(GOODBYE) | |
if inp == "s": | |
print("No problem, on to the next one!") | |
return True | |
print(f"{bcolors.FAIL}Please type a number!{bcolors.ENDC}") | |
return False | |
print(f"{bcolors.WARNING}Try again{bcolors.ENDC}") | |
return False | |
def get_hint(sign: str, terms: Sequence[int], correct_answer: int) -> None: | |
""" | |
In the most horribly sloppy fashion, provide hints for the problem type. | |
If the answer is less than 1 (because the author made 0 attempt to prevent | |
it) give the poor dummy an out. | |
""" | |
if correct_answer < 1: | |
print(f"{bcolors.WARNING}Hmm, this looks like a hard one. " | |
f"Type {bcolors.OKCYAN}s {bcolors.WARNING}to skip{bcolors.ENDC}") | |
return | |
if sign == "+": | |
if max(terms) > 10: | |
print("Slow down and write it out on your whiteboard or paper") | |
else: | |
print("If I have " + terms[0] * "๐" + " and someone gives me") | |
for t in terms[1:]: | |
print(t * "๐") | |
print("How many apples do I have?") | |
if sign == "-": | |
print("The answer to subtraction is the difference between the numbers. Maybe this will help") | |
for i, t in enumerate(terms): | |
color = bcolors.OKBLUE if i % 2 else bcolors.OKGREEN | |
print(color + "." * t + bcolors.ENDC) | |
if sign == "/": | |
print("You don't know how to do this yet! Stop guessing :)") | |
if sign == "x": | |
x, y = terms[:2] | |
print(f"Think of this as {y} groups of {x}") | |
for _ in range(y): | |
print("." * x) | |
def format_problem(sign: str, *args): | |
# TODO: stop cheating and handle right-padding | |
max_width = len(str(max(args))) | |
for a in args[:-1]: | |
print(f" {a}") | |
print(sign + str(args[-1])) | |
print("-" * (max_width + 1)) | |
""" | |
Set the level of difficulty per type of problem | |
""" | |
difficulty = { | |
"+": { | |
"minimum": 10, | |
"maximum": 90, | |
"number_of_terms": 2 | |
}, | |
"-": { | |
"minimum": 10, | |
"maximum": 90, | |
"number_of_terms": 2 | |
}, | |
"x": { | |
"minimum": 1, | |
"maximum": 9, | |
"number_of_terms": 2 | |
}, | |
"/": { | |
"minimum": 1, | |
"maximum": 9, | |
"number_of_terms": 2 | |
} | |
} | |
if __name__ == '__main__': | |
print("Welcome to the Mathalympics, let's see how you're doing") | |
print("Press q to quit and you can always press s to skip if you're stuck") | |
print("Do you want to do Addition (+), Subtraction (-), Multiplication (x) or Division (/)?") | |
sign = None | |
while not sign: | |
choices = difficulty.keys() | |
choice = input(f"Enter {' or '.join(choices)} ") | |
if choice in choices: | |
sign = choice | |
try: | |
while True: | |
kwargs = difficulty[sign] | |
kwargs["sign"] = sign | |
random_problem(**kwargs) | |
except KeyboardInterrupt: | |
print(GOODBYE) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Should make
reverse=True
on the sorting an optional parameter by operation type as things like fractions will want to be sorted in ascending order.