I decided to build a quiz type of game for improving chemistry linguistics using python. Currently there is not much variety in terms of questions, but before expanding the question base, I would like my code to be reviewed.
I have used various modules including colorama, pyfiglet and inquirer to improve the UX. Any better alternatives and approaches are welcomed.
Any improvements regarding performance, code readability, UX are highly appreciated
Here is the directory structure:
iChemistry -
           - data
                 - elements.json
                 - compounds.json
           - compound.py
           - element.py
           - questions.py
           - main.py
elements.json
{
    "Hydrogen": {
        "symbol": "H",
        "atomic_no": 1,
        "mass_no": 1
    },
    "Helium": {
        "symbol": "He",
        "atomic_no": 2,
        "mass_no": 4
    },
    "Lithium": {
        "symbol": "Li",
        "atomic_no": 3,
        "mass_no": 7
    },
    "Beryllium": {
        "symbol": "Be",
        "atomic_no": 4,
        "mass_no": 9
    },
    "Boron": {
        "symbol": "B",
        "atomic_no": 5,
        "mass_no": 11
    },
    "Carbon": {
        "symbol": "C",
        "atomic_no": 6,
        "mass_no": 12
    },
    "Nitrogen": {
        "symbol": "N",
        "atomic_no": 7,
        "mass_no": 14
    },
    "Oxygen": {
        "symbol": "O",
        "atomic_no": 8,
        "mass_no": 16
    },
    "Fluorine": {
        "symbol": "F",
        "atomic_no": 9,
        "mass_no": 19
    },
    "Neon": {
        "symbol": "Ne",
        "atomic_no": 10,
        "mass_no": 20
    },
    "Sodium": {
        "symbol": "Na",
        "atomic_no": 11,
        "mass_no": 23
    },
    "Magnesium": {
        "symbol": "Mg",
        "atomic_no": 12,
        "mass_no": 24
    },
    "Aluminium": {
        "symbol": "Al",
        "atomic_no": 13,
        "mass_no": 27
    },
    "Silicon": {
        "symbol": "Si",
        "atomic_no": 14,
        "mass_no": 28
    },
    "Phosphorus": {
        "symbol": "P",
        "atomic_no": 15,
        "mass_no": 31
    },
    "Sulphur": {
        "symbol": "S",
        "atomic_no": 16,
        "mass_no": 32
    },
    "Chlorine": {
        "symbol": "Cl",
        "atomic_no": 17,
        "mass_no": 35.5
    },
    "Argon": {
        "symbol": "Ar",
        "atomic_no": 18,
        "mass_no": 40
    }
}
compounds.json
{
    "Hydrogen monoxide": "H2O",
    "Hydrogen peroxide": "H2O2",
    "Hydrogen sulphate": "H2SO4",
    "Hydrogen chloride": "HCl",
    "Hydrogen sulphide": "H2S",
    "Carbon dioxide": "CO2",
    "Carbon tetrachloride": "CCl4",
    "Methane": "CH4",
    "Ethanol": "C2H5OH",
    "Ammonia": "NH3",
    "Sulphur dioxide": "SO2"
}
element.py
import json
class Element:
    def __init__(self, name, symbol, atomic_no, mass_no):
        self.name = name
        self.symbol = symbol
        self.atomic_no = atomic_no
        self.mass_no = mass_no
    def get_no_of_electrons(self):
        return self.atomic_no
    def get_no_of_protons(self):
        return self.atomic_no
    def get_no_of_neutrons(self):
        return self.mass_no - self.atomic_no
    def get_electronic_configuration(self):
        i = 1
        electrons_left = self.atomic_no
        configuration = []
        while electrons_left > 0:
            capacity = 2 * (i ** 2)
            if electrons_left >= capacity:
                configuration.append(capacity)
                electrons_left -= capacity
            else:
                configuration.append(electrons_left)
                electrons_left = 0
            i += 1
        return configuration
    def __repr__(self):
        return self.name
ELEMENTS = []
with open("./data/elements.json", "r") as file:
    for name, data in json.load(file).items():
        ELEMENTS.append(Element(name, *data.values()))
compound.py
import json
class Compound:
    def __init__(self, name, formula):
        self.name = name
        self.formula = formula
        
COMPOUNDS = []
with open("./data/compounds.json", "r") as file:
    for name, formula in json.load(file).items():
        COMPOUNDS.append(Compound(name, formula))
questions.py
from element import Element, ELEMENTS
from compound import Compound, COMPOUNDS
import inquirer
import random
from colorama import Fore, Style
def ask_symbol() -> tuple[str, bool]:
    element = random.choice(ELEMENTS)
    answer = inquirer.prompt(
        [inquirer.Text("name", Fore.BLUE + f"What is the symbol of {element.name}" + Style.RESET_ALL)]
    )["name"].strip()
    return element.symbol, answer == element.symbol
def ask_name() -> tuple[str, bool]:
    element = random.choice(ELEMENTS)
    answer = inquirer.prompt(
        [inquirer.Text("symbol", Fore.BLUE + f"What is the name of {element.symbol}?" + Style.RESET_ALL)]
    )["symbol"].strip()
    return element.name, answer == element.name
def ask_atomic_no() -> tuple[int, bool]:
    element = random.choice(ELEMENTS)
    try:
        answer = inquirer.prompt(
            [inquirer.Text("atomic_no", Fore.BLUE + f"What is the atomic number of {get_name_or_symbol(element)}?" + Style.RESET_ALL)]
        )["atomic_no"]
        answer = int(answer.strip())
    except ValueError:
        answer = None
    return element.atomic_no, answer == element.atomic_no
def ask_mass_no() -> tuple[float, bool]:
    element = random.choice(ELEMENTS)
    try:
        answer = inquirer.prompt(
            [inquirer.Text("mass_no", Fore.BLUE + f"What is the mass number of {get_name_or_symbol(element)}?" + Style.RESET_ALL)]
        )["mass_no"]
        answer = float(answer.strip())
    except ValueError:
        answer = None
    return element.mass_no, answer == element.mass_no
def ask_electronic_configuration() -> tuple[list[int], bool]:
    element = random.choice(ELEMENTS)
    try:
        answer = inquirer.prompt(
            [inquirer.Text("electronic_config", Fore.BLUE + f"What is the electronic configuration of {get_name_or_symbol(element)}?" + Style.RESET_ALL)]
        )["electronic_config"]
        answer = [int(i) for i in answer.strip().replace(" ", "").split(",")]
    except ValueError:
        answer = None
    correct_configuration = element.get_electronic_configuration()
    return correct_configuration, answer == correct_configuration
def ask_chemical_formula():
    compound = random.choice(COMPOUNDS)
    answer = inquirer.prompt(
        [inquirer.Text("formula", Fore.BLUE + f"What is the chemical formula of {compound.name}")]
    )["formula"].strip()
    correct_formula = compound.formula
    return correct_formula, answer == correct_formula
def get_name_or_symbol(element: Element) -> str:
    return random.choice([element.name, element.symbol])
QUESTIONS = [
    ask_symbol, ask_name,
    ask_atomic_no, ask_mass_no,
    ask_electronic_configuration, ask_chemical_formula
]
main.py
import time
import random
import inquirer
from colorama import Fore, Style
from pyfiglet import Figlet
from questions import QUESTIONS
class iChemistry:
    def __init__(self):
        self.name = None
    def intro(self):
        figlet = Figlet()
        text = "iChemistry"
        print(Style.BRIGHT + Fore.BLUE + figlet.renderText(text) + "\t- An interactive way to learn chemistry..." + Style.RESET_ALL)
        self.name = input("What is your name? ").strip()
        print(f"Welcome {self.name}!")
    def start(self):
        self.intro()
        print("Answer the questions, as quickly as possible!")
        input("Ready? ")
        while True:
            question = random.choice(QUESTIONS)
            correct_answer, has_answered = question()
            if has_answered:
                print(Fore.GREEN + "Correct! Keep up the confidence!" + Style.RESET_ALL)
            else:
                print(Fore.RED + f"Oops! The correct answer is {correct_answer}" + Style.RESET_ALL)
            time.sleep(0.5)
if __name__ == "__main__":
    interface = iChemistry()
    interface.start()
Thank you and happy coding!!