4
\$\begingroup\$

I have just started to learn to code as it always been a pipe dream of mine but I found it hard to understand, however, with the rise of LLMs I have found the experience not nearly as daunting as before. Using ChatGPT and Phind.com I finally began my programming journey and decided a good place to start would be with Python.

As I'm sure is the case with most newcomers, I decided that I would start to learn by creating a Text based game as this would hold my interest more. I have spent nearly the past month learning new ideas and techniques that might seem simple for some, but for someone with no experience in coding or education in computer science were quite new to me.

With all this said I have come to point where I would really like some feedback on my first project, which is still in the works, to help guide my learning further. I am aiming to take this game and create a website in HTML, to help me learn HTML, and host the game online as a project to point to for future employers.

I would greatly appreciate any feedback on my code in terms of do's and don'ts, any things that I should learn to make my code more streamlined, and even any advice on how to structure the code would be great!

Please follow the link here to the public Github I have created to host the code. Feel free to download and play the game if you want. It is not very long at all as I have been spending most of my time learning the language and implementing what I have learned.

Github link - https://tinyurl.com/3hb5bpy5

import random as r
from colorama import Fore, Style
import colorama as c
import time as t
import os
import msvcrt
import time
import customtkinter as ctk

green = Fore.GREEN
red = Fore.RED
rcolour = Style.RESET_ALL

 
"""This class represents a message box with a title and a journal of entries. It uses the CTk library for creating the GUI.
    Attributes:
    system_input (str): The message to be displayed in the message area.
    title (str): The title of the message box.
    journal_entries (list): A list of journal entries to be displayed in the buttons.
    Methods:
    init(self, system_input, title, journal_entries): Initializes the message box and creates the GUI.
    display_entry(self, entry): Displays the selected entry in the message area.
    close_window(self): Destroys the message box GUI.
    Usage:
    To use this class, create an instance of it with the necessary parameters, and then call the mainloop() method on it to display the message box.
    You can then select journal entries to display by clicking on the corresponding buttons.
"""
class Message_box:
    def __init__(self, system_input, title, journal_entries):
        self.system_input = system_input
        self.title = title
        self.journal_entries = journal_entries

        # Set the appearance mode and color theme for the CTk library
        ctk.set_appearance_mode('dark')
        ctk.set_default_color_theme('dark-blue')

        # Create a new CTk window and configure its properties
        self.root = ctk.CTk()
        self.root.geometry('800x400')
        self.root.title(self.title)
        self.root.wm_attributes("-topmost", 1)

        # Create a frame within the window and configure its properties
        self.frame = ctk.CTkFrame(master=self.root)
        self.frame.columnconfigure(0, weight=1)
        self.frame.rowconfigure(0, weight=1)
        self.frame.pack(pady=20, padx=20, fill='both', expand=True, side='top')

        # Create a text box for displaying messages and configure its properties
        self.message_area = ctk.CTkTextbox(master=self.frame)
        self.message_area.grid(row=0, column=0, sticky='nsew')

        # Create a frame for the journal entry buttons and configure its properties
        self.button_frame = ctk.CTkFrame(master=self.frame)
        self.button_frame.grid(row=1, column=0, pady=10)

        # Create buttons for each journal entry and add them to the button frame
        self.buttons = []
        # Create a button with the entry's text and a command to display that entry
        for i, entry in enumerate(self.journal_entries):
            button_text = f"Entry {i+1}"
            button = ctk.CTkButton(master=self.button_frame, text=button_text, command=lambda e=entry: self.display_entry(e))
            button.grid(row=0, column=i, padx=5)
            self.buttons.append(button)
            
        # Insert the system input message into the message area
        self.message_area.insert('0.0', self.system_input)
        self.message_area.configure(state='disabled', wrap='word')

        # Create a close button and add it to the frame
        self.close_button = ctk.CTkButton(master=self.frame, text='Close', command=self.close_window)
        self.close_button.grid(row=2, column=0, pady=10)

        # Start the main loop for the CTk window
        self.root.mainloop()

    # Display the selected journal entry in the message area
    def display_entry(self, entry):
        self.message_area.configure(state='normal')
        self.message_area.delete('1.0', 'end')
        self.message_area.insert('0.0', entry)
        self.message_area.configure(state='disabled', wrap='word')

    # Destroy the CTk window
    def close_window(self):
        self.root.destroy()


def print_debug_message(message):
    print(green + 'DEBUG:' + message + rcolour)


def print_error_message(message):
    if message.startswith("ERROR:"):
        print(Fore.RED + message + Style.RESET_ALL)
    else:
        print(message)


def print_slow(message):
    """
    Print a message to the terminal one character at a time, with a delay of
    0.05 seconds between each character. The function wraps long lines to fit
    the terminal width, and clears the screen before printing. If the user
    presses a key during the printing, the function stops and waits for the user
    to press another key.
    Parameters   : message (str): The message to be printed.
    Returns: None
    """
    # Print message
    # Check if the message is a dictionary
    if isinstance(message, dict):
        # If it is, join the dictionary keys into a single string
        message = ''.join(message)

    # Split the message into sentences using the period as the delimiter
    sentences = message.split('.')

    # Initialize the stop flag and time delay
    stop = False
    t = 0.05

    # Iterate through each sentence in the list of sentences
    for sentence in sentences:
        # Iterate through each character in the current sentence
        for char in sentence:
            # Print the character without a newline and flush the output buffer
            print(char, end='', flush=True)
            # Increment the printed characters counter
            printed_chars += 1
            # Sleep for the specified time delay
            time.sleep(t)
            # Check if a key was pressed
            if msvcrt.kbhit():
                # If a key was pressed, set the stop flag to True
                stop = True
                # Retrieve the pressed key
                msvcrt.getch()
                # Set the time delay to 0
                t = 0
        # Print a newline character after printing all characters in the sentence
        else:
            print()


class Player_character:
    def __init__(self, player_name, player_health, player_class, player_strength, player_endurance, player_dexterity, player_intelligence, player_wisdom, player_charisma, player_gold, player_level, player_xp, player_location, player_inv):
        self.player_name = player_name
        self.player_health = player_health
        self.player_class = player_class
        self.player_strength = player_strength
        self.player_endurance = player_endurance
        self.player_dexterity = player_dexterity
        self.player_intelligence = player_intelligence
        self.player_wisom = player_wisdom
        self.player_charisma = player_charisma
        self.player_gold = player_gold
        self.player_level = player_level
        self.player_xp = player_xp
        self.player_inv = {}
        self.player_weapons = player_weapon_type
        self.player_location = 'start'

   # def weapon_skill_add_exp(self):

    def weapon_skill_level(self):
        levels = self.player_weapon_skills['weapon skill levels']
        experiance = self.player_weapon_skills['weapon skill experience']
        while True:
            for skill in experiance:
                for weapon, exp_range in experiance[skill].items():
                    if exp_range[0] > exp_range[1]:
                        levels[skill][weapon] += 1
                        exp_range[0] = 0
                        exp_range[1] += 100
                    else:
                        experiance[skill][weapon] = (
                            exp_range[0]+1, exp_range[1]+1)
            break

    def weapon_attack(self):
        # print_debug_message('DEBUG:', str(list(player_weapon_type.keys())))
        weapon_choice = None
        while weapon_choice == None:
            print_slow(
                f'Please select one of your weapons to attack with\n{list(player_weapon_type.keys())}')
            weapon_choice = input('\n>')
            if weapon_choice in player_weapon_type:
                weapon_choice = list(player_weapon_type[weapon_choice])
                # print_debug_message(f'DEBUG: weapon_choice = {weapon_choice}')
                weapon_selected = Weapon(*weapon_choice)
                weapon_type = weapon_selected.weapon_type
                weapon_skill = weapon_selected.weapon_skill
                # Asign weapon skill stats to variables
                pws = player_weapon_skills["weapon skill experience"][weapon_type][weapon_skill]
                pwsd = player_weapon_skills["weapon skill levels"][weapon_type][weapon_skill]
                print_debug_message(
                    f'DEBUG: weapon selected sweapon skill = {player_weapon_skills["weapon skill experience"][weapon_type][weapon_skill]}')
                pws[0] += 20
                if pws[0] > pws[1]:
                    self.weapon_skill_level()
                print_debug_message(
                    f'DEBUG: weapon selected sweapon skill = {player_weapon_skills["weapon skill experience"][weapon_type][weapon_skill]}')
                # print_debug_message(f'DEBUG: weapon_selected = {weapon_selected}')
                weapon_damage = r.randint(
                    weapon_selected.weapon_min, weapon_selected.weapon_max)
                # print_debug_message(f'DEBUG: {weapon_selected.getatt()}')
                damage_mod = weapon_selected.weapon_stat_mod
                damage_mod = 'player_'+damage_mod
                damage_mod = getattr(self, damage_mod)
                # print_debug_message(f'DEBUG: damage mod = {damage_mod}')
                weapon_damage = (weapon_damage + damage_mod + pwsd)
                return weapon_damage, weapon_selected.weapon_name
            else:
                weapon_choice = None

    def spell_attack(self):
        spell_choice = None
        while spell_choice == None:
            # print_debug_message(f"player_spells =  {list(player_spells.keys())}")
            spell_choice = input(
                f"You open your book of spells:\n{str(list(player_spells.keys()))}\nPlease select a spell to use!\n>")
            # print_debug_message(f'spell_choice = {spell_choice}')
            if spell_choice in player_spells:
                spell_choice = list(player_spells[spell_choice].values())
                # print_debug_message(f'spell_choice = {spell_choice}')
                spell_choice = Spell(*spell_choice)
                # print_debug_message(f'spell_choice = {spell_choice}')
                spell_damage = r.randint(*spell_choice.spell_damage)
                # print_debug_message(f'spell_damage = {spell_damage}')
                health_damage = r.randint(*spell_choice.health_cost)
                # print_debug_message(f'health_damage = {health_damage}')
                return spell_damage, health_damage
            else:
                print('select another spell dumbass')
                spell_choice = None

    # def skill_level(self):


class Enemy:
    """
    A class to represent an enemy in a role-playing game.
    Attributes:
    -----------
    enemy_name : str
        The name of the enemy.
    enemy_description : str
        A description of the enemy.
    enemy_health : int
        The amount of health the enemy has.
    enemy_strength : int
        The enemy's strength attribute.
    enemy_endurance : int
        The enemy's endurance attribute.
    enemy_dexterity : int
        The enemy's dexterity attribute.
    enemy_intelligence : int
        The enemy's intelligence attribute.
    enemy_wisdom : int
        The enemy's wisdom attribute.
    enemy_charisma : int
        The enemy's charisma attribute.
    enemy_weapon : str
        The weapon the enemy is using.
    enemy_weapon_type : str
        The type of weapon the enemy is using (e.g. "goblin_weapons", "orc_weapons", etc.).
    enemy_gold : int
        The amount of gold the enemy is carrying.
    enemy_xp : int
        The amount of experience points the enemy is worth.
    Methods:
    --------
    gen_random_enemy(enemy):
        Generates a random enemy based on the given enemy type.
        Parameters:
        -----------
        enemy : str
            The type of enemy to generate. Valid options are "cave goblin", "forrest goblin", "Orc Warrior",
            "Orc Shaman", "Orc Archer", "Orc Berserker", "Zombie", and "Skeleton".
        Returns:
        --------
        enemy : Enemy
            A new instance of the Enemy class representing the generated enemy.
    """
    def __init__(self, enemy_name, enemy_description, enemy_health, enemy_strength, enemy_endurance, enemy_dexterity, enemy_intelligence, enemy_wisdom, enemy_charisma, enemy_weapon, enemy_weapon_type, enemy_gold, enemy_xp):
        self.enemy_name = enemy_name
        self.enemy_description = enemy_description
        self.enemy_health = enemy_health
        self.enemy_strength = enemy_strength
        self.enemy_endurance = enemy_endurance
        self.enemy_dexterity = enemy_dexterity
        self.enemy_intelligence = enemy_intelligence
        self.enemy_wisdom = enemy_wisdom
        self.enemy_charisma = enemy_charisma
        self.enemy_weapon = enemy_weapon
        self.enemy_weapon_type = enemy_weapon_type
        self.enemy_gold = enemy_gold
        self.enemy_xp = enemy_xp

    def gen_random_enemy(enemy):
        enemy_info = {
            'goblin types': {
                "cave goblin": ("cave goblin", 'A small greyish creature, half the height of the average man. It could be mistaken for child if it wasnt for the large pointed ears and sharp fang like teeth', r.randint(4, 6), r.randint(0, 1), r.randint(0, 1), r.randint(0, 1), r.randint(-1, 0), r.randint(0, 1), r.randint(-2, 0), r.choice(list(enemy_weapon_type["goblin_weapons"].keys())), 'goblin_weapons', r.randint(0, 3), 50),
                "forrest goblin": ("forrest goblin", 'A dark green creature, hlaf the height of the average man. It could be mistaken for a child it wasnt for the large pointed ears and sharp fang like teeth', r.randint(4, 6), r.randint(0, 1), r.randint(0, 1), r.randint(0, 1), r.randint(-1, 0), r.randint(0, 1), r.randint(-2, 0), r.choice(list(enemy_weapon_type["goblin_weapons"].keys())), 'goblin_weapons', r.randint(0, 3), 50),
            },
            'orc types': {
                "Orc Warrior": ("Orc Warrior", "A tall and muscular creature, with green skin and a mean scowl. Known for their brutal strength and fierce loyalty to their clans.", r.randint(8, 12), r.randint(1, 2), r.randint(1, 2), r.randint(1, 2), r.randint(0, 1), r.randint(0, 1), r.randint(-2, 0), r.choice(list(enemy_weapon_type["orc_weapons"].keys())), 'orc_weapons', r.randint(0, 5), 100),
                "Orc Shaman": ("Orc Shaman", "A wizened orc with deep knowledge of dark magic. They are often found leading orc raiding parties, using their spells to bolster their warriors and weaken their enemies.", r.randint(8, 12), r.randint(1, 2), r.randint(1, 2), r.randint(1, 2), r.randint(0, 1), r.randint(0, 1), r.randint(-2, 0), r.choice(list(enemy_weapon_type["orc_weapons"].keys())), 'orc_weapons', r.randint(0, 5), 100),
                "Orc Archer": ("Orc Archer", "A lean orc with excellent aim. They are often found providing ranged support for orc raiding parties, raining arrows down on their enemies from a safe distance.", r.randint(8, 12), r.randint(1, 2), r.randint(1, 2), r.randint(1, 2), r.randint(0, 1), r.randint(0, 1), r.randint(-2, 0), r.choice(list(enemy_weapon_type["orc_weapons"].keys())), 'orc_weapons', r.randint(0, 5), 100),
                "Orc Berserker": ("Orc Berserker", "A massive orc with an uncontrollable rage. They are often used as shock troops, charging headlong into enemy lines and wreaking havoc.", r.randint(8, 12), r.randint(1, 2), r.randint(1, 2), r.randint(1, 2), r.randint(0, 1), r.randint(0, 1), r.randint(-2, 0), r.choice(list(enemy_weapon_type["orc_weapons"].keys())), 'orc_weapons', r.randint(0, 5), 100),
            },
            'undead types': {
                "Zombie": ("Zombie", "A shambling corpse with rotting flesh and an insatiable hunger for brains. These undead are slow, but persistent, and can overwhelm even the most skilled warrior with their sheer numbers.", r.randint(4, 6), r.randint(0, 1), r.randint(0, 1), r.randint(0, 1), r.randint(-1, 0), r.randint(-1, 0), r.randint(-3, -1), r.choice(list(enemy_weapon_type["undead_weapons"].keys())), 'undead_weapons', r.randint(0, 3), 75),
                "Skeleton": ("Skeleton", "A walking pile of bones, animated by dark magic. These undead are fragile, but can be difficult to hit due to their bony frames. They often carry weapons and wear armor, making them more dangerous than they first appear.", r.randint(4, 6), r.randint(0, 1), r.randint(0, 1), r.randint(0, 1), r.randint(-1, 0), r.randint(-1, 0), r.randint(-3, -1), r.choice(list(enemy_weapon_type["undead_weapons"].keys())), 'undead_weapons', r.randint(0, 3), 75),
                "Wraith": ("Wraith", "A ghostly apparition with the power to drain the life force of its enemies. These undead are difficult to hit with physical attacks, but can be vulnerable to magic. They are often found haunting old ruins and graveyards.", r.randint(6, 8), r.randint(0, 1), r.randint(0, 1), r.randint(1, 2), r.randint(-2, 0), r.randint(-2, 0), r.randint(-4, -2), r.choice(list(enemy_weapon_type["undead_weapons"].keys())), 'undead_weapons', r.randint(0, 3), 150),
            },
            "Troll Berserker": ("Troll Berserker", "A massive creature with grey-green skin and razor-sharp claws. Known for their incredible strength and ability to enter a berserker rage during combat.", r.randint(15, 20), r.randint(2, 3), r.randint(2, 3), r.randint(2, 3), r.randint(-1, 1), r.randint(-1, 1), r.randint(-3, -1), r.choice(list(enemy_weapon_type["troll_weapons"].keys())), 'toll_weapons', r.randint(0, 8), 200),
            "Necromancer": ("Necromancer", "A dark and twisted individual with the power to control the undead. They are often found in dark and foreboding places, performing their dark rituals.", r.randint(6, 8), r.randint(0, 1), r.randint(0, 1), r.randint(0, 2), r.randint(-2, 0), r.randint(-2, 0), r.randint(-4, -2), r.choice(list(enemy_weapon_type["magic_weapons"].keys())), 'troll_weapons', r.randint(0, 3), 150),
            "Giant Spider": ("Giant Spider", "A massive arachnid with venomous fangs and a deadly bite. Often found in dark and damp places, they are known for their ability to spin webs and lay traps for their prey.", r.randint(4, 6), r.randint(0, 1), r.randint(0, 1), r.randint(0, 1), r.randint(-1, 0), r.randint(-1, 0), r.randint(-3, -1), r.choice(list(enemy_weapon_type["beast_weapons"].keys())), 'troll_weapons', r.randint(0, 3), 75),
            "Demon Hunter": ("Demon Hunter", "A skilled warrior with a deep hatred for all things demonic. They are often found hunting demons and other evil creatures, using their knowledge of the occult to aid them in battle.", r.randint(8, 10), r.randint(1, 2), r.randint(1, 2), r.randint(1, 2), r.randint(0, 1), r.randint(0, 1), r.randint(-2, 0), r.choice(list(enemy_weapon_type["holy_weapons"].keys())), 'troll_weapons', r.randint(0, 5), 175),
        }
        enemy = r.choice(list(enemy_info[enemy].values()))
        enemy = Enemy(*enemy)
        return enemy

    def spell_attack(self):
        # print_debug_message(str(list(enemy_spells.keys())))
        spell_choice = r.choice(list(enemy_spells.values()))
        spell_chosen = Spell(*spell_choice.values())
        spell_damage = r.randint(*spell_chosen.spell_damage)
        health_cost = r.randint(*spell_chosen.health_cost)
        # print_debug_message(f'spell_damage={spell_damage}. health_cost={health_cost}')
        spell_mod = spell_chosen.spell_stat_mod
        if spell_mod == "wisdom":
            spell_mod = self.enemy_wisdom
        elif spell_mod == 'intelligence':
            spell_mod = self.enemy_intelligence
        # print_debug_message(f'spell_stat_mod={spell_chosen.spell_stat_mod}. spell_mod={spell_mod}')
        spell_damage += spell_mod
        health_cost -= spell_mod
        spell_name = spell_chosen.spell_name
        return spell_damage, health_cost, spell_name

    def weapon_attack(self):
        weapon_choice = list((enemy_weapon_type)[
                             self.enemy_weapon_type][self.enemy_weapon])
        weapon_chosen = Weapon(*weapon_choice)
        damage = r.randint(weapon_chosen.weapon_min, weapon_chosen.weapon_max)
        weapon_mod = weapon_chosen.weapon_stat_mod
        if weapon_mod == "strength":
            weapon_mod = self.enemy_strength
        if weapon_mod == 'dexterity':
            weapon_mod = self.enemy_dexterity
        if weapon_mod == 'charisma':
            weapon_mod = self.enemy_charisma
        if weapon_mod == 'intelligence':
            weapon_mod = self.enemy_intelligence
        damage += weapon_mod
        return damage, self.enemy_weapon

    # def __str__(self):
    #     return f'{green}enemy name:{self.enemy_name}, enemy description: {self.enemy_description}, enemy health:{self.enemy_health}, enemy strength: {self.enemy_strength}, enemy endurance: {self.enemy_endurance}, enemy dexterity: {self.enemy_dexterity}, enemy intelligence: {self.enemy_intelligence}, enemy wisdom: {self.enemy_wisdom}, enemy charisma: {self.enemy_charisma}, enemy weapon: {self.enemy_weapon}, enemy gold: {self.enemy_gold}, enemy xp: {self.enemy_xp}{rcolour}'


class Weapon:
    """This is a Python class called Weapon that represents a weapon object in a game. Here is a breakdown of its attributes and methods:
        Attributes:
        weapon_name: a string representing the name of the weapon
        weapon_description: a string representing a brief description of the weapon
        weapon_min: an integer representing the minimum damage the weapon can inflict
        weapon_max: an integer representing the maximum damage the weapon can inflict
        weapon_stat_mod: an integer representing a modifier to the damage based on a player's stats
        weapon_value: a float representing the value of the weapon
        weapon_type: a string representing the type of weapon (e.g. sword, bow, etc.)
        weapon_skill: a string representing the skill required to use the weapon
        
        Methods:
        __init__(self, weapon_name, weapon_description, weapon_min, weapon_max, weapon_stat_mod, weapon_value, weapon_type, weapon_skill): the constructor method for the class, initializes the attributes of the object
        __str__(self): a method that returns a string representation of the object
        getatt(self): a method that prints out some of the attributes of the object
        Overall, this class provides a way to create and store information about different types of weapons in a game.
    """

    def __init__(self, weapon_name, weapon_description, weapon_min, weapon_max, weapon_stat_mod, weapon_value, weapon_type, weapon_skill):
        self.weapon_name = weapon_name
        self.weapon_description = weapon_description
        self.weapon_min = weapon_min
        self.weapon_max = weapon_max
        self.weapon_stat_mod = weapon_stat_mod
        self.weapon_value = weapon_value
        self.weapon_type = weapon_type
        self.weapon_skill = weapon_skill

    def __str__(self):
        return f"{self.weapon_name}: {self.weapon_min}-{self.weapon_max} damage"

    def getatt(self):
        weapon_name = self.weapon_name
        weapon_min = self.weapon_min
        weapon_max = self.weapon_max
        weapon_stat_mod = self.weapon_stat_mod
        print(weapon_name, weapon_min, weapon_max, weapon_stat_mod)

class Spell:
    """
    Represents a magical spell that can be used to deal damage to enemies.
    Attributes:
    -----------
    spell_name : str
        The name of the spell.
    spell_description : str
        A brief description of the spell.
    spell_damage : int
        The amount of damage that the spell can deal.
    spell_stat_mod : str
        The statistic that the spell's damage is based on (e.g. "intelligence").
    health_cost : int
        The amount of health that the caster must sacrifice to cast the spell.
    Methods:
    --------
    __str__() -> str
        Returns a string representation of the spell, including its name, damage, and health cost.
    """
    def __init__(self, spell_name, spell_description, spell_damage, spell_stat_mod, health_cost):
        self.spell_name = spell_name
        self.spell_description = spell_description
        self.spell_damage = spell_damage
        self.spell_stat_mod = spell_stat_mod
        self.health_cost = health_cost

    def __str__(self):
        return f"{self.spell_name}: {self.spell_damage}- spell_damage : {self.health_cost}-health_damage"


class Combat_loop:
    """
    This class represents a combat loop in a game. It takes a player character and an enemy as inputs and initializes their
    attributes for combat. The class has a method called start_combat that begins the combat between the player and the enemy.
    Attributes:
    ----------
    player_character : object
        The player character object that is participating in combat.
    enemy : object
        The enemy object that is participating in combat.
    Methods:
    -------
    start_combat():
        Begins the combat loop between the player and the enemy.
    """
    def __init__(self, player_character, enemy):
        self.player_character = player_character
        self.enemy = enemy

    def start_combat(self):
        player_character_init = r.randint(
            1, 20) + player_character.player_dexterity
        enemy_init = r.randint(1, 20) + self.enemy.enemy_dexterity
        round = 1
        player_attc_choice = None
        print_slow(f'You are entering combat with a {self.enemy.enemy_name}')
        print_slow(
            f'You roll a {player_character_init} and the {self.enemy.enemy_name} has rolled a {enemy_init}')
        if player_character_init > enemy_init:
            print_slow(
                f'You are faster to react than the {self.enemy.enemy_name}, you get to go first!')
            while True:
                print_slow(f'\nRound {round}')
                print_slow(
                    f'{player_character.player_name} has {player_character.player_health} health and the {self.enemy.enemy_name} has {self.enemy.enemy_health} health')
                if player_character_init > enemy_init:
                    while player_attc_choice not in ['spell', 'weapon']:
                        player_attc_choice = input(
                            f'\nPlease chose how you want to attack!\nWith a "spell" or with a "weapon"?\n>').lower()
                        if player_attc_choice == "spell":
                            enemy_damage_player_health_cost = player_character.spell_attack()
                            player_character.player_health -= enemy_damage_player_health_cost[1]
                            self.enemy.enemy_health -= enemy_damage_player_health_cost[0]
                            print_slow(
                                f'{green}You deal {enemy_damage_player_health_cost[0]} damage to the {self.enemy.enemy_name} but take {enemy_damage_player_health_cost[1]} damage as a result of using the spell{rcolour}')

                            if self.enemy.enemy_health <= 0:
                                print(
                                    f'{green}You have defeated the {self.enemy.enemy_name} and won the fight!{rcolour}')
                                return
                        if player_attc_choice == "weapon":
                            player_attc_damage = player_character.weapon_attack()
                            print_slow(
                                f'{green}You have done {player_attc_damage[0]} with your {player_attc_damage[1]} to the {self.enemy.enemy_name}{rcolour}')
                            self.enemy.enemy_health -= player_attc_damage[0]
                            if self.enemy.enemy_health <= 0:
                                print(
                                    f'{green}You have defeated the {self.enemy.enemy_name} and won the fight!{rcolour}')
                                return
                    player_attc_choice = None
                    attack_choice_spell = r.randint(
                        1, 20) + self.enemy.enemy_intelligence
                    attack_choice_weapon = r.randint(
                        1, 20) + self.enemy.enemy_strength
                    if attack_choice_spell > attack_choice_weapon:
                        damage = self.enemy.spell_attack()
                        print_slow(
                            f'{red}The {self.enemy.enemy_name} attacked you with {damage[2]} dealing {damage[0]} to you!\nThey also take {damage[1]} by using the spell!{rcolour}')
                        player_character.player_health -= damage[0]
                        self.enemy.enemy_health -= damage[1]
                        round += 1
                        if player_character.player_health <= 0:
                            print_slow(
                                f'{red}You have been defeated by the {self.enemy.enemy_name}, better luck next time!{rcolour}')
                            if self.enemy.enemy_health <= 0:
                                print_slow(
                                    f'{green}You have defeated the {self.enemy.enemy_name} and won the fight!{rcolour}')
                                return
                    if attack_choice_weapon > attack_choice_spell:
                        damage = self.enemy.weapon_attack()
                        print_slow(
                            f'{red}The {self.enemy.enemy_name} is attacking with a {damage[1]}{rcolour}')
                        print_slow(
                            f'{red}The {self.enemy.enemy_name} did {damage[0]} damage to you!{rcolour}.')
                        player_character.player_health -= damage[0]
                        round += 1
                        if player_character.player_health <= 0:
                            print_slow(
                                f'{red}You have been defeated by the {self.enemy.enemy_name}, better luck next time!{rcolour}')
                            exit
        if enemy_init > player_character_init:
            print_slow(
                    f'{red}The {self.enemy.enemy_name} has reacted quicker than you, they go first!{rcolour}')
            while True:
                print_slow(f'Round {round}')
                print_slow(
                    f'{player_character.player_name} has {player_character.player_health} health and the {self.enemy.enemy_name} has {self.enemy.enemy_health} health')
                attack_choice_spell = r.randint(
                    1, 20) + self.enemy.enemy_intelligence
                attack_choice_weapon = r.randint(
                    1, 20) + self.enemy.enemy_strength
                if attack_choice_spell > attack_choice_weapon:
                    damage = self.enemy.spell_attack()
                    print_slow(
                        f'{red}The {self.enemy.enemy_name} attacked you with {damage[2]} dealing {damage[0]} to you!\nThey also take {damage[1]} by using the spell!{rcolour}')
                    player_character.player_health -= damage[0]
                    self.enemy.enemy_health -= damage[1]
                    round += 1
                    if player_character.player_health <= 0:
                        print_slow(
                            f'{red}You have been defeated by the {self.enemy.enemy_name}, better luck next time!{rcolour}')
                        exit
                if attack_choice_weapon > attack_choice_spell:
                    damage = self.enemy.weapon_attack()
                    print_slow(
                        f'{red}The {self.enemy.enemy_name} attacked you with a {damage[1]} and did {damage[0]} damage!{rcolour}')
                    player_character.player_health -= damage[0]
                    if player_character.player_health <= 0:
                        print_slow(
                            f'{red}You have been defeated by the {self.enemy.enemy_name}, better luck next time!{rcolour}')
                        exit
                player_attc_choice = None
                while player_attc_choice is not 'spell' or not 'weapon':
                    player_attc_choice = input(
                        f'Please chose how you want to attack!\nWith a "spell" or with a "weapon"?\n>').lower()
                    if player_attc_choice == "spell":
                        enemy_damage_player_health_cost = player_character.spell_attack()
                        player_character.player_health -= enemy_damage_player_health_cost[1]
                        self.enemy.enemy_health -= enemy_damage_player_health_cost[0]
                        print_slow(
                            f'{green}You deal {enemy_damage_player_health_cost[0]} damage to the {self.enemy.enemy_name} but take {enemy_damage_player_health_cost[1]} damage as a result of using the spell{rcolour}')
                        round += 1
                        if self.enemy.enemy_health <= 0:
                            print(
                                f'{green}You have defeated the {self.enemy.enemy_name} and won the fight!{rcolour}')
                            return
                    if player_attc_choice == "weapon":
                        player_attc_damage = player_character.weapon_attack()
                        print_slow(
                            f'{green}You have done {player_attc_damage[0]} with your {player_attc_damage[1]} to the {self.enemy.enemy_name}{rcolour}')
                        self.enemy.enemy_health -= player_attc_damage[0]
                        round += 1
                        if self.enemy.enemy_health <= 0:
                            print(
                                f'{green}You have defeated the {self.enemy.enemy_name} and won the fight!{rcolour}')
                            return
                    break


class Location:
    def __init__(self):
        self.exit_choices = locations[player_character.player_location]['exits']
        self.player_location = locations[player_character.player_location]
        self.enemies = locations[player_character.player_location]['enemies']

    def location_change(self, player_location):
        exit_choice = None
        exit_locations = locations[player_location]['exits']
        print_slow('Please choose a location:')
        i = 0
        for choice in exit_locations.values():
            i += 1
            print(i, choice)
        while exit_choice == None:
            exit_choice = input('>')
            if exit_choice in exit_locations:
                exit_choice = exit_locations[exit_choice]
                player_character.player_location = exit_choice
                # print_debug_message(f'player_location = {exit_choice}')
                return player_character.player_location, self.player_location,self.location_choices()
            else:
                rnum = r.randint(1, 10)
                if rnum <= 9:
                    print_slow("That is not a valid exit choice")
                    exit_choice=None
                else:
                    print_slow(
                        "Stop trying to make new locations!.What do you think you know better than me or something?.Is my story not interesting enough for you?.")
                    exit_choice=None

    def location_choices(self):
        ldescription = locations[player_character.player_location]['description']
        print_slow(f"{ldescription}\n")
        if 'enemies' in locations[player_character.player_location]:
            if locations[player_character.player_location]['enemies'] != {}:
                lenemies = locations[player_character.player_location]['enemies'].values(
                )
                lenemies = ', '.join(lenemies)
                print_slow(
                    f'{red}You have been attacked by {lenemies}.Prepare to fight.{rcolour}')
                fightnum = 1
                for enemy in list(locations[player_character.player_location]['enemies'].keys()):
                    print_slow(f'Fight {fightnum}')
                    enemy_created = Enemy.gen_random_enemy(enemy)
                    fight = Combat_loop(player_character, enemy_created)
                    fight.start_combat()
                    del locations[player_character.player_location]['enemies'][enemy]
                    fightnum += 1
                    if locations[player_character.player_location]['enemies'] == {}:
                        print_slow(
                            'You have defeated all the enemies in the area!.')
                        lchoice = None
        
        if 'player journal entry' in  locations[player_character.player_location]:
            new_journal_entry = locations[player_character.player_location]['player journal entry']
            entry_num = len(player_journal) + 1
            player_journal.append(f'Entry {entry_num}: {new_journal_entry}')
        
        lchoice = None
        while lchoice == None:
            print_slow(
                f'What do you want to do?.1 : Move to new location.2 : Explore current location.3 : Talk.4 : Fight enemies.5 : Open your Journal')
            
            lchoice = input('>')

            if lchoice == '1':
                self.location_change(player_character.player_location)
            elif lchoice == '2':
                if 'explore' in locations[player_character.player_location]:
                    self.location_explore()
                else:
                    print_slow(
                        'You look around but do not see anything to investigate')
                    lchoice = None
            elif lchoice == '3':
                if 'NPCs' in self.player_location:
                    self.location_conversation()
                else:
                    print_slow(
                        "Talking to yourself is fine.It's when you start talking back that you should worry!")
                    lchoice = None
            elif lchoice == '4':
                if locations[player_character.player_location]['enemies'] != {}:
                    fightnum = 1
                    for enemy in list(self.enemies.keys()):
                        print_slow(f'Fight {fightnum}')
                        enemy_created = Enemy.gen_random_enemy(enemy)
                        fight = Combat_loop(player_character, enemy_created)
                        fight.start_combat()
                        del self.enemies[enemy]
                        fightnum += 1
                        if locations[player_character.player_location]['enemies'] == {}:
                            print_slow(
                                'You have defeated all the enemies in the area!.')
                            lchoice = None
                else:
                    print_slow('There are no enemies for you to fight!.')
                    lchoice = None
            elif lchoice == '5':
                journal_info = ''
                for entry in player_journal:
                    journal_info += f'{entry}\n\n'
                journal_window = Message_box(journal_info, f'{player_character.player_name} Journal', player_journal)
                lchoice = None
            else:
                lchoice = None


    def location_explore(self):
        edescription = locations[player_character.player_location]['explore']['description']
        print_slow(edescription)
        eloot = locations[player_character.player_location]['explore']['loot'].values(
        )
        for value in eloot:
            if isinstance(value, str):
                print_slow(value)
        self.location_choices()


enemy_spells = {
    "fireball": {
        "spell_name": "Inferno",
        "spell_description": "A spell that creates a massive explosion of fire and engulfs the target.",
        "spell_damage": (5, 8),  # random integer between 5 and 8
        "spell_stat_mod": "wisdom",  # enemy's attribute to decrease health cost
        "spell_health_cost": (2, 5)  # random integer between 7 and 12
    },
    "poison": {
        "spell_name": "Venom",
        "spell_description": "A spell that poisons the target and deals damage over time.",
        "spell_damage": (2, 4),  # random integer between 2 and 4
        "spell_stat_mod": "intelligence",
        "spell_health_cost": (1, 3)
    },
    "frost_bolt": {
        "spell_name": "Frost Bolt",
        "spell_description": "A spell that shoots a bolt of freezing ice at the target.",
        "spell_damage": (3, 6),
        "spell_stat_mod": "intelligence",
        "spell_health_cost": (1, 4)
    }
}

player_spells = {
    "embers": {
        "spell_name": "Embers",
        "spell_description": "A powerful spell that creates a ball of fire and hurls it at the target.",
        "spell_damage": (3, 6),  # random integer between 10 and 20
        "spell_stat_mod": "intelligence",  # player's attribute to decrease health cost
        "spell_health_cost": (1, 4)  # random integer between 5 and 10
    },
    "heal": {
        "spell_name": "Heal",
        "spell_description": "A spell that restores health to the user.",
        "spell_damage": (-5, 0),  # spell has a chance to heal target as well
        "spell_stat_mod": "wisdom",
        # random integer between -6 and -1 (i.e. heals instead of dealing damage)
        "spell_health_cost": (-6, -1)
    },
    "ice_blast": {
        "spell_name": "Ice Blast",
        "Spell_description": "A spell that creates a blast of ice and hurls it at the target.",
        "Spell_damage": (3, 6),
        "spell_stat_mod": "intelligence",
        "spell_health_cost": (1, 4)
    }
}

player_weapon_type = {
    "rusty sword": ("rusty sword", "A rusty old sword, probably been in the family for generations", 1, 4, "dexterity", 10, 'malee weapon skills', 'one handed weapons'),
    "rusty axe": ("rusty axe", "A rusty old axe, probably been in the family for generations", 1, 4, "strength", 10, 'malee weapon skills', 'one handed weapons'),
    "splintered bow": ("splintered bow", "A old bow showing signs of splintering, nearly at its breaing point", 1, 4, "dexterity", 10, 'ranged weapon skills', 'bows'),
    "cracked wand": ("cracked wand", "An old family heirloom, damaged from excessive use", 1, 4, "intelligence", 10, 'magic weapon skills', 'wands')
}

""" Player weapon skill dictionary
This is a dictionary that stores information about a player's weapon skills. It has two main keys:
'weapon skill levels': which contains the player's skill level for each type of weapon.
'weapon skill experience': which contains the player's experience for each type of weapon.
Each of these keys has subkeys for different types of weapons:
'melee weapon skills': which contains subkeys for one-handed weapons, two-handed weapons, and daggers.
'ranged weapon skills': which contains subkeys for bows, crossbows, and guns.
'magic weapon skills': which contains subkeys for wands, staffs, and holy symbols.
Each subkey contains a value that represents the player's skill level or experience for that particular weapon.
This dictionary is used to keep track of a player's progress in developing their weapon skills, and is likely to be updated as the player gains experience and becomes more proficient with different types of weapons.
"""
player_weapon_skills = {
    'weapon skill levels': {
        'malee weapon skills': {
            'one handed weapons': 0,
            'two handed weapons': 0,
            'dagger': 0,
        },
        'ranged weapon skills': {
            'bows': 0,
            'cross bows': 0,
            'guns': 0,
        },
        'magic weapon skills': {
            'wands': 0,
            'staffs': 0,
            'holy symbols': 0,
        }
    },
    'weapon skill experience': {
        'malee weapon skills': {
            'one handed weapons': [0, 100],
            'two handed weapons': [0, 100],
            'dagger': [0, 100],
        },
        'ranged weapon skills': {
            'bows': [0, 100],
            'cross bows': [0, 100],
            'guns': [0, 100],
        },
        'magic weapon skills': {
            'wands': [0, 100],
            'staffs': [0, 100],
            'holy symbols': [0, 100],
        },
    },
}

enemy_weapon_type = {
    'goblin_weapons': {
        'goblin dagger': ('goblin dagger', 'A crude attempt at a makeshift weapon. A sharpened stone with ragged cloth tied at the base for a handle', 1,  3, 'dexterity', 0, 'None', 'None',),
        'goblin club': ('goblin club', 'A crude makeshift weapon. A large stone forced into a chunck of wood', 1, 3, 'strength', 0, 'None', 'None',),
        'sack of rocks': ('sack of rocks', 'A collection of smaller rocks, some covered in blood.', 1, 3, 'dexterity', 0, 'None', 'None',)
    },
    'orc_weapons': {
        'orc axe': ('orc axe', 'A large and heavy axe, made for crushing bones and splitting shields.', 2, 6, 'strength', 50, 'None', 'None'),
        'orc mace': ('orc mace', 'A brutal weapon made of iron spikes attached to a heavy wooden handle.', 2, 6, 'strength', 50, 'None', 'None'),
        'orc spear': ('orc spear', 'A long and deadly weapon, designed for piercing through armor and enemies alike.', 2, 6, 'dexterity', 50, 'None', 'None')
    },
    'undead_weapons': {
        'bone sword': ('bone sword', 'A sword made from the bones of the undead. Its jagged edges can cause severe damage to living foes.', 2, 6, 'strength', 50, 'None', 'None'),
        'death scythe': ('death scythe', 'A grim weapon that can instantly reap the souls of those it strikes down. It is said that the scythe itself is alive.', 3, 6, 'strength', 75, 'None', 'None'),
        'necrotic staff': ('necrotic staff', 'A staff that channels the power of the undead. It can weaken the lifeforce of enemies and drain their vitality.', 2, 8, 'intelligence', 50, 'None', 'None')
    },
    'troll_weapons': {
        'troll hammer': ('troll hammer', 'A massive hammer, capable of smashing through solid stone with ease.', 3, 9, 'strength', 100, 'None', 'None'),
        'troll claws': ('troll claws', 'Long and razor-sharp claws, designed to shred through armor and flesh alike.', 3, 9, 'dexterity', 100, 'None', 'None'),
        'troll javelin': ('troll javelin', 'A long and heavy javelin, capable of impaling multiple enemies at once.', 3, 9, 'strength', 100, 'None', 'None')
    },
    'magic_weapons': {
        'fireball staff': ('fireball staff', 'A powerful staff imbued with the power of fire, capable of unleashing devastating spells.', 1, 3, 'intelligence', 75, 'None', 'None'),
        'ice shard wand': ('ice shard wand', 'A wand that creates sharp shards of ice, piercing through enemies and leaving them frozen in place.', 1, 3, 'intelligence', 75, 'None', 'None'),
        'necrotic scythe': ('necrotic scythe', 'A scythe imbued with dark magic, capable of draining the life force from enemies with each strike.', 1, 3, 'intelligence', 75, 'None', 'None')
    },
    'beast_weapons': {
        'poisonous fangs': ('poisonous fangs', 'Long and deadly fangs, capable of injecting venom into enemies and causing them to weaken.', 2, 5, 'dexterity', 25, 'None', 'None'),
        'acidic spines': ('acidic spines', 'Spiny protrusions from a beast, coated in a powerful acid that can burn through armor and flesh.', 2, 5, 'dexterity', 25, 'None', 'None'),
        'crushing tail': ('crushing tail', 'A massive and powerful tail, used by beasts to crush and maim enemies.', 2, 5, 'strength', 25, 'None', 'None')
    },
    'holy_weapons': {
        'divine sword': ('divine sword', 'A beautiful and powerful sword, imbued with the power of divine magic.', 2, 7, 'strength', 150, 'None', 'None'),
        'holy mace': ('holy mace', 'A weapon of pure light and energy, capable of striking down evil and undead enemies with ease.', 2, 7, 'strength', 150, 'None', 'None'),
        'holy bow': ('holy bow', 'A bow imbued with holy magic, capable of firing arrows of pure energy that smite enemies on contact.', 2, 7, 'dexterity', 150, 'None', 'None')
    }
}

locations = {
    "start": {
        "description": "You awake in the pitch black, the sound of dripping water echos around you.The cold hard ground underneath you feels like stone.You reach into your bad and pull out a tourch to light.The flame flickers to life and reveals that you are in a cave.You can see two passageways out of the area, do you take the one on the right or the left?",
        "exits": {
            "1": "left passageway",
            "2": "right passageway"
        },
        'enemies': {},
        "explore": {
            "description": "The cave is damp and cold, with jagged rocks protruding from the walls and ceiling. The ground is uneven and covered in a layer of dust and dirt. The air is thick with the smell of damp earth and mold.",
            "loot": {
                'bones': 'Scattered around the floor are various remains of creatures',
                'rusty broken sword': 'A rust covered broken sword, not worth taking'
            },
        },
    },
    "left passageway": {
        "description": "As you enter the passageway you notice a cool breeze blowing against your face. The walls around you are slick with water and the ground begins to become coated in more and more soil the further along you go. Do you continue or turn around?",
        "exits": {
            "1": "forest",
            "2": "starting room"
        }
    },
    "right passageway": {
        "description": "You begin to walk down the passageway and notice that the air is becoming more and more stale the further you go. In the distance you swear you can hear the sound of something moving. Do you continue or turn around?",
        "exits": {
            "1": "main cave hub",
            "2": "starting room"
        }
    },
    "forest": {
        "description": "You see the blinding light of day growing brighter and brighter as you move quickly up the passageway until you finally emerge from the cave. Taking a second to drinkn in your surroundings you find yourself in a dense thick forrest, the sound of wild life can be heard all around. You notice an opening in the tree line straight a head and a climbable tree to the right of you. What do you do?",
        "exits": {
            "1": "forest opening",
            "2": "tree climb",
            "3": "cave return"
        }
    },
    "main cave hub": {
        "description": "You step into a gaint canverness room. Water dripping from the ceiling splashes around you as you examine the area. Out of the corner of your eye you spot a green flash dart into the darkness. What do you do?",
        "exits": {
            "1": "goblin chase",
            "2": "starting room"
        },
        "player journal entry" :""" Gaint cave hub\n 
As I stepped into the cavernous room, I couldn't help but feel a sense of awe. The room was massive, and the sound of water dripping from the ceiling echoed off the walls. As I took a moment to examine the area, I couldn't help but feel a sense of unease. Something didn't feel right.
Out of the corner of my eye, I spotted a flash of green dart into the darkness. I knew I had to investigate. I drew my sword and shield, ready for whatever lay ahead. As I moved deeper into the cavern, the sound of my footsteps echoed loudly.
As I drew closer to the spot where I saw the green flash, I couldn't help but feel a sense of apprehension. I knew that danger lurked in these caverns, and I needed to be cautious. My hand tightened around the hilt of my sword as I stepped closer to the darkness.
Suddenly, I heard a rustling sound and a low growl. I was not alone in this cavern. I gripped my sword tightly and prepared to face whatever lay ahead."""
    },
    "goblin chase": {
        "description": "Not wanting to let the mystery figure escape you begin to chase after it.Running fast through the cave, your foot steps echoing as you go, you begin to gain ground on it.Your blood is thumping in your ears as you turn a corner and finally see the figures cornered in a deadend.You are now face to face with 3 Cave Goblins.What do you do?",
        "exits": {
            "1": "goblin talk",
            "2": "running away",
        },
        "enemies": {
            'goblin types': 'cave goblin 1',
        },
        "end_of_fight": {"goblin fight": "goblin fight"},
        "fight_loot": {},
        "combat": True,
        "player journal entry" :""" First kill\n 
Today I faced my first battle against a cave goblin, and I emerged victorious. I cannot help but feel a sense of pride mixed with a tinge of sadness. It was the first time I have ever killed such a large creature, and I cannot help but reflect on the experience.
As I was exploring the caverns, I heard the sound of footsteps approaching. I knew that danger was near, so I drew my sword and shield, ready for whatever lay ahead.
Suddenly, a cave goblin appeared before me. It was small, but fierce, with razor-sharp teeth and beady eyes that glinted in the dim light. It lunged at me, but I was ready for it.
We engaged in a quick but deadly battle. The goblin was fast, but I was able to parry its attacks with my shield and strike back with my sword. After a few minutes of intense fighting, I finally landed a fatal blow, and the goblin fell to the ground, dead.
As I caught my breath and looked at the lifeless body of the goblin, I couldn't help but feel a mix of emotions. On the one hand, I was proud of myself for defeating such a formidable foe. On the other hand, I couldn't help but feel a sense of sadness at taking another creature's life.
But such is the life of an adventurer. I knew that I would face many more battles like this in the future, and that I needed to be prepared to defend myself against whatever lay ahead.
I gathered my wits about me and continued on my journey, knowing that I had overcome this challenge and was ready for whatever lay ahead.
Until next time, Journal.""",

    },
    "goblin talk": {
        "description": "The goblin looks afriad and confused. 'Why are you chasing me? leave me alone!'. You are surprised by this. Do you go back to the main room?",
        "exits": {
            "1": "main cave hub return"
        }
    },
    "goblin fight": {
        "description": "As the goblin snarls you instenctivly reach down and grasb the hilt of your sword. Seeing the danager the goblin lunges at you and manages to plung a dagger into your leg. Feeling a rush of pain you quickly swing your sword with all your might, cleaving the goblins head off. Your leg is badly damaged, you have lost 20 health points. With the goblin defeated what do you do?",
        "exits": {
            "1": "goblin search",
            "2": "leg bandage",
            "3": "main cave hub return"
        }
    },
    "goblin search": {
        "description": "You look through the pockets of the goblin and only find 2 gold pieces and a few small bones from the goblins last meal. You stand back up and trun around to face the passageway back to big room, you glance down at your bleeding leg. What do you do?",
        "exits": {
            "1": "leg bandage",
            "2": "main cave hub return"
        }
    },
    "leg bandage": {
        "description": "You take your bag off and look for a bandage. Finding one in the front pocket of the bag you take it out and wrap it tightly around your leg. Happy that youve stopped the bleeding you look at the goblin on the floor and passageway back to the main hub. What do you want to do?",
        "exits": {
            "1": "goblin search",
            "2": "main cave hub return"
        }
    },
    "main cave hub return": {
        "description": "You manage to make your way back to the large room in the cave. You notice a cool breeze coming from the passageway back to where you woke up. With your back to the passageway that is now the goblins tomb, what do you do?",
        "exits": {
            "1": "goblin corpse",
            "2": "starting room"
        }
    },
    "goblin corpse": {
        "description": "You go back down the passageway to the goblin's corpse. What do you do?",
        "exits": {
            "1": "goblin search",
            "2": "main cave hub return"
        }
    },
    "starting room": {
        "description": "You enter the room where you woke up. You can see two passage ways out of the area, do you take the one on the right or the left?",
        "exits": {
            "1": "main cave hub return",
            "2": "left passageway"
        }
    },
    "testing hall": {
        "description": "You have entered the dev testing hall"
    }
}

player_journal= [
"""
Entry 1: The worst headache\n
I'm not entirely sure what happened to me, but I seem to have found myself in quite a predicament.The last thing I remember was going to bed, and now I'm lying on the hard, cold ground of a cave. The darkness is all-encompassing, and the only sound is that of dripping water echoing around me. 
    
I fumble around in my bag, hoping to find something useful. My hand brushes against a flashlight, and I breathe a sigh of relief. I turn it on, and the flame flickers to life, casting a dim light around me. The cave is vast and seems to go on forever. I try to remember how I got here, but my mind draws a blank. Was I kidnapped? Did I wander here on my own? I simply cannot recall. All I know is that I need to find my way out of this cave and back to civilization.
I will keep you updated, Journal, as I continue to navigate this unknown terrain. For now, I'll keep moving forward, one step at a time.
"""
]


player_character = Player_character(
    'Ed', 100, None, 5, 5, 5, 5, 5, 5, 5, 5, 5, None, None)
# print(str(player_character))
# print(player_character.weapon_attack())
# print(player_character.spell_attack())

enemy_info = {
    "cave goblin": ("cave goblin", 'A small greyish creature, half the height of the average man. It could be mistaken for child if it wasnt for the large pointed ears and sharp fang like teeth', r.randint(4, 6), r.randint(0, 1), r.randint(0, 1), r.randint(0, 1), r.randint(-1, 0), r.randint(0, 1), r.randint(-2, 0), r.choice(list(enemy_weapon_type["goblin_weapons"].keys())), r.randint(0, 3), 50),
    "forrest goblin": ("forrest goblin", 'A dark green creature, hlaf the height of the average man. It could be mistaken for a child it wasnt for the large pointed ears and sharp fang like teeth', r.randint(4, 6), r.randint(0, 1), r.randint(0, 1), r.randint(0, 1), r.randint(-1, 0), r.randint(0, 1), r.randint(-2, 0), r.choice(list(enemy_weapon_type["goblin_weapons"].keys())), r.randint(0, 3), 50),
}

os.system('cls')
movement = Location()
movement.location_choices()
   
\$\endgroup\$
3
  • \$\begingroup\$ Welcome to Code Review! I changed the title so that it describes what the code does per site goals: "State what your code does in your title, not your main concerns about it.". Feel free to edit and give it a different title if there is something more appropriate. \$\endgroup\$ Apr 20 at 18:18
  • 1
    \$\begingroup\$ Also - how much of the code has been modified from AI generated code? \$\endgroup\$ Apr 20 at 18:19
  • \$\begingroup\$ I used AI to generate the idea of starting with a text-based game and to give me the bases to go off in terms of what functions would be helpful and so on. The majority of the code is a combination of suggestions from AI about what methods or classes might work and then I would take those suggestions try to write code that would work and then used the AI to trouble shoot when I couldn't understand it. I used the AI as sort of private personal coder to help guide me and correct me when I went wrong more than having it create everything for me. \$\endgroup\$ Apr 20 at 18:39

1 Answer 1

2
+50
\$\begingroup\$

PEP-8

PEP-8 is the standard style recommendation for Python. In it, it includes things like how many blank lines, how much indentation, recommendations for variable naming and casing.

There are linters (Flake8, Pylint and others) which check your code against PEP-8 and issue useful warnings. My linters show over 300 warnings (mostly line too long, and many duplicates, but over 100 unique)

Issues

There are several bugs apparently in your code:

  • printed_chars is not defined in print_slow but subsequently incremented, which will fail.

  • Player_character does not have the player_weapon_skills attribute, likewise Location doesn't have a location_conversation attribute, but the code attempts to access them.

  • get_random_enemy is not a @staticmethod but doesn't have self passed as the first arg, which means it tries to get its own instance (in the arg enemy) from the enemy_info dict which isn't a valid key.


You have numerous variables which are assigned/imported but unused

  • colorama as c
  • time as t
  • printed_chars
  • stop
  • journal_window

You declare methods such as Weapon.getatt which are never used and don't seem to have any value beyond what __str__ already provides


In print_error_message you use Style.RESET_ALL rather than your declared constant rcolour


In Spell.__str__ it looks like you have some f-string variables which aren't properly bracketed

def __str__(self):
        return f"{self.spell_name}: {self.spell_damage}- spell_damage : {self.health_cost}-health_damage"

In print_slow you subsequently "shadow" t making the imported module inaccessible in that routine by assigning something to the same name. This is bad practice and can cause hard to debug errors.


You have several instances of writing exit, which does nothing. It is a built-in function to exit the Python interpreter, but you are calling it without parens, so it is a no-operation (nop) statement.

Style

You have lines which stretch over 400 characters. It's usual to keep lines to 80-120 characters long for legibility. In contrast to this, you also have seemingly random linebreaks:

weapon_choice = list((enemy_weapon_type)[
                     self.enemy_weapon_type][self.enemy_weapon])

attack_choice_spell = r.randint(
    1, 20) + self.enemy.enemy_intelligence

which makes your code hard to read.


Try to avoid single character variable (and particularly library) names. They make save you some typing but it is effort to remember what's what if you come back to it later.


You have comparisons like:

while lchoice == None:

Python best practice says you should use is with None, in this case you might even want:

while not lchoice:

in fact what you probably want here is

while True:
    ...
    if lchoice == '1':
        ...
        break

You also have the construct:

        while True:
            for skill in experiance:
                for weapon, exp_range in experiance[skill].items():
                    if exp_range[0] > exp_range[1]:
                        levels[skill][weapon] += 1
                        exp_range[0] = 0
                        exp_range[1] += 100
                    else:
                        experiance[skill][weapon] = (
                            exp_range[0]+1, exp_range[1]+1)
            break

The while True and break here, mean this block is only ever executed once and as such are doing nothing.


Classes

For some reason you have Combat_loop as a class, which has one method and takes two arguments. This would usually be a function. Especially since it is always called as:

fight = Combat_loop(player_character, enemy_created)
fight.start_combat()

The function Combat_loop.start_combat also does a lot, with a lot of copy-pasting and should probably be broken down into multiple functions such as player_turn, enemy_turn, deal_damage, etc.


In Weapon and Enemy you have dictionaries of dictionaries of monsters/weapons with a standard form which are only instantiated into classes through a strange and confusing

weapon_choice = list((enemy_weapon_type)[
                     self.enemy_weapon_type][self.enemy_weapon])
weapon_chosen = Weapon(*weapon_choice)

Instead of building the big dict[dict[tuple[...]]] in Enemy.get_random_enemy (note this shadows the one declared at the top-level)

You should probably have a constant dict[list[Enemy]] which would do the same thing and be easier to read and generate, because you wouldn't be generating a this dict every time you call the function.

The same goes for locations, weapons, journals and everything else at the end. If it has a repeated structure it can be a class to link relevant data in a standard way.


Documentation

Docstrings go inside definitions. Your docs for Message_box are outside the definition and thus inaccessible through help.


Your documentation of Enemy (and probably others) is incomplete and does not mention all of the methods.


Besides your classes, nothing else is documented. None of your methods have documentation


In print_slow the comments add nothing which is not said by the code itself and are just clutter. There is no reasoning given as to why these things are being done.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.