5
\$\begingroup\$

I am pretty new to Python and programming in general, since I go to a programming school I am learning a lot of things by the book, which personally it doesn't suit me and I am falling behind everyone. Currently I am trying to change that and show that I am capable of doing something with my "knowledge" of Python, currently I am working on a basic text game, you can choose which weapon to fight a wolf, who's health is driven from a dictionary, same goes for statistics of the weapons you can choose from. Now what I was trying to do is set it up so I do not need to repeat the same code code for the whole damage idea and I wanted to write it down in a function, so I can call back the function for each weapon type and save on space and have less code. It would mean a lot if someone could show me how to do it, and / or any suggestions to my code and how I can make it shorter without using a lot of object oriented programming would mean the world.

Thank you for anyone helping or just reading this and taking the time of their day.

import time
import random

weapons = {
    "melee": {

        "katana": {

            "speed": 6,
            "strength": 50,
            "range": 5,
            "critical_chance": 50,
            "critical_multiplier": 2,
            "damage": 100

        },

        "dull_sword": {

            "speed": 3,
            "strength": 15,
            "range": 4,
            "critical_chance": 10,
            "critical_multiplier": 1.2,
            "damage": 30

        },

        "sharp_sword": {

            "speed": 3,
            "strength": 25,
            "range": 4,
            "critical_chance": 25,
            "critical_multiplier": 1.5,
            "damage": 45

        },
        "broken_machete": {

            "speed": 4,
            "strength": 20,
            "range": 3,
            "critical_chance": 15,
            "critical_multiplier": 1.3,
            "damage": 30

        },

        "repaired_machete": {

            "speed": 5,
            "strength": 30,
            "range": 3,
            "critical_chance": 20,
            "critical_multiplier": 1.4,
            "damage": 60

        },

        "mace": {

            "speed": 2.1,
            "strength": 60,
            "range": 4,
            "critical_chance": 10,
            "critical_multiplier": 1.1,
            "damage": 40

        }

    },

    "long_range": {

        "longbow": {

            "speed": 3,
            "strength": 55,
            "range": 100,
            "critical_chance": 100,
            "critical_multiplier": 1.5,
            "damage": 120

        },

        "slingshot": {

            "speed": 5,
            "strength": 10,
            "range": 50,
            "critical_chance": 25,
            "critical_multiplier": 1.1,
            "damage": 20

        },

        "short_bow": {

            "speed": 4,
            "strength": 15,
            "range": 60,
            "critical_chance": 20,
            "critical_multiplier": 1.2,
            "damage": 40
        },

        "broken_longbow": {

            "speed": 2,
            "strength": 30,
            "range": 70,
            "critical_chance": 50,
            "critical_multiplier": 1.3,
            "damage": 60

        }


    },

    "throwable": {

        "small_rock": {

            "speed": 7,
            "strength": 7,
            "range": 20,
            "critical_chance": 10,
            "critical_multiplier": 1,
            "damage": 10

        },

        "dynamite": {

            "speed": 2.1,
            "strength": 100,
            "range": 20,
            "critical_chance": 20,
            "critical_multiplier": 1.7,
            "damage": 120
        },

        "makeshift_explosive": {

            "speed": 2.3,
            "strength": 60,
            "range": 15,
            "critical_chance": 15,
            "critical_multiplier": 1.5,
            "damage": 95

        }

    }


}

enemies_animals = {

    "dead": {

        "zombie": {

            "health": 150,
            "speed": 6,
            "damage": 20,
            "attack_speed": 4,
            "range": 3

        },

        "zombie_wolf": {

            "health": 250,
            "speed": 8,
            "damage": 25,
            "attack_speed": 4,
            "range": 3

        },

        "zombie_bear": {

            "health": 350,
            "speed": 4,
            "damage": 35,
            "attack_speed": 4,
            "range": 4

        }

    },

    "animals": {

        "chicken": {

            "health": 20,
            "speed": 6

        },

        "wolf": {

            "health": 100,
            "speed": 12,
            "damage": 20,
            "attack_speed": 3,
            "range": 3

        },

        "bear": {

            "health": 200,
            "speed": 6,
            "damage": 25,
            "attack_speed":  3,
            "range": 4

        },

        "cow": {

            "health": 50,
            "speed": 6

        },

        "large_bat": {

            "health": 100,
            "speed": 14,
            "damage": 15,
            "attack_speed": 5,
            "range": 2

        }

    },

    "regular_enemies": {

        "bandit": {

            "health": 100,
            "speed": 10,
            "damage": 10,
            "attack_speed": 5,
            "range": 3

        },

        "hoarder": {

            "health": 120,
            "speed": 10,
            "damage": 20,
            "attack_speed": 4,
            "range": 3

        },

        "mage": {

            "health": 90,
            "speed": 10,
            "damage": 25,
            "attack_speed": 3.3,
            "range": 15

        },

        "marksman": {

            "health": 75,
            "speed": 12,
            "damage": 50,
            "attack_speed": 2.2,
            "range": 50

        },

        "heavy_bandit": {

            "health": 150,
            "speed": 7,
            "damage": 15,
            "attack_speed": 3.4,
            "range": 4

        },

        "heavy_hoarder": {

            "health": 170,
            "speed": 7,
            "damage": 20,
            "attack_speed": 3.7,
            "range": 4

        }

    }

}

wolf = enemies_animals["animals"]["wolf"]["health"]


user = input("Write down your username: ")
time.sleep(0.5)

userInput = input("Welcome, for this test please choose either to attack, or to run: ")
time.sleep(0.5)

if userInput.lower() == "attack":

    time.sleep(0.5)
    weapon_type = input("Choose which type of weapon to use (melee/long_range/throwable): ")

    if weapon_type.lower() == "melee":

        time.sleep(0.5)
        weapon_list = []
        for key in weapons[weapon_type]:
            weapon_list.append(key)
        print(f'Choose from weapons: {weapon_list}')

        time.sleep(0.5)
        weapon = input("choose which weapon to use(seen from the list above): ")
        critical_chance = weapons[weapon_type][weapon]["critical_chance"]

        if random.randint(1, 100) == weapons[weapon_type][weapon]["critical_chance"]:

            total_damage = ((weapons[weapon_type][weapon]["damage"] + weapons[weapon_type][weapon]["strength"]) / 2) * 3 * weapons[weapon_type][weapon]["critical_multiplier"]
            attack = wolf - total_damage

            if attack <= 0:

                dead_wolf = wolf

                if random.randint(1, 5) == 5:

                    time.sleep(0.5)
                    print(f"{user} killed a wolf with a critical hit and got it's meat! ")
                else:

                    time.sleep(0.5)
                    print(f"{user} killed the wolf with a critical hit!")
            else:

                time.sleep(0.5)
                print(f"The wolf has {attack} health! ")

        else:

            total_damage = ((weapons[weapon_type][weapon]["damage"] + weapons[weapon_type][weapon]["strength"]) / 2) * 3
            attack = wolf - total_damage

            if attack <= 0:

                dead_wolf = wolf

                if random.randint(1, 5) == 5:

                    time.sleep(0.5)
                    print(f"{user} killed a wolf with a critical hit and got it's meat! ")

                else:

                    time.sleep(0.5)
                    print(f"{user} killed the wolf with a critical hit!")
            else:

                time.sleep(0.5)
                print(f"The wolf has {attack} health! ")

    elif weapon_type == "long_range":

        time.sleep(0.5)
        weapon_list = []
        for key in weapons[weapon_type]:
            weapon_list.append(key)
        print(f'Choose from weapons: {weapon_list}')

        time.sleep(0.5)
        weapon = input("choose which weapon to use(seen from the list above): ")
        critical_chance = weapons[weapon_type][weapon]["critical_chance"]

        if random.randint(1, 100) == weapons[weapon_type][weapon]["critical_chance"]:

            total_damage = ((weapons[weapon_type][weapon]["damage"] + weapons[weapon_type][weapon][
                "strength"]) / 2) * 3 * weapons[weapon_type][weapon]["critical_multiplier"]
            attack = wolf - total_damage

            if attack <= 0:

                dead_wolf = wolf

                if random.randint(1, 5) == 5:

                    time.sleep(0.5)
                    print(f"{user} killed a wolf with a critical hit and got it's meat! ")
                else:

                    time.sleep(0.5)
                    print(f"{user} killed the wolf with a critical hit!")
            else:

                time.sleep(0.5)
                print(f"The wolf has {attack} health! ")

        else:

            total_damage = ((weapons[weapon_type][weapon]["damage"] + weapons[weapon_type][weapon]["strength"]) / 2) * 3
            attack = wolf - total_damage

            if attack <= 0:

                dead_wolf = wolf

                if random.randint(1, 5) == 5:

                    time.sleep(0.5)
                    print(f"{user} killed a wolf with a critical hit and got it's meat! ")
                else:

                    time.sleep(0.5)
                    print(f"{user} killed the wolf with a critical hit!")
            else:

                time.sleep(0.5)
                print(f"The wolf has {attack} health! ")

    elif weapon_type == "throwable":

        time.sleep(0.5)
        weapon_list = []
        for key in weapons[weapon_type]:
            weapon_list.append(key)
        print(f'Choose from weapons: {weapon_list}')

        time.sleep(0.5)
        weapon = input("choose which weapon to use(seen from the list above): ")
        critical_chance = weapons[weapon_type][weapon]["critical_chance"]

        if random.randint(1, 100) == weapons[weapon_type][weapon]["critical_chance"]:

            total_damage = ((weapons[weapon_type][weapon]["damage"] + weapons[weapon_type][weapon][
                "strength"]) / 2) * 3 * weapons[weapon_type][weapon]["critical_multiplier"]
            attack = wolf - total_damage

            if attack <= 0:

                dead_wolf = wolf

                if random.randint(1, 5) == 5:

                    time.sleep(0.5)
                    print(f"{user} killed a wolf with a critical hit and got it's meat! ")

                else:

                    time.sleep(0.5)
                    print(f"{user} killed the wolf with a critical hit!")
            else:

                time.sleep(0.5)
                print(f"The wolf has {attack} health! ")

        else:

            total_damage = ((weapons[weapon_type][weapon]["damage"] + weapons[weapon_type][weapon]["strength"]) / 2) * 3
            attack = wolf - total_damage

            if attack <= 0:

                dead_wolf = wolf

                if random.randint(1, 5) == 5:

                    time.sleep(0.5)
                    print(f"{user} killed a wolf with a critical hit and got it's meat! ")

                else:

                    time.sleep(0.5)
                    print(f"{user} killed the wolf with a critical hit!")
            else:

                time.sleep(0.5)
                print(f"The wolf has {attack} health! ")

else:

    time.sleep(0.5)
    print(f"{user}, choose to run away!")
\$\endgroup\$
3
  • \$\begingroup\$ Does your code work? Your first two sentences make it sound like no, but the rest could maybe suggest that it does work, but you're looking to simplify the code. But the wall of text that sounds like "I don't know what I'm doing but I'm trying to learn" is leading people to think your question is off-topic. \$\endgroup\$
    – Teepeemm
    Dec 11, 2021 at 22:30
  • \$\begingroup\$ Welcome to Code Review@SE. Reinderien (commendably!) tagged beginner. As you always may comment your own posts, I suggest turning parts of the introduction without notable impact on the code into comments. Shorter sentences might be better, too. \$\endgroup\$
    – greybeard
    Dec 12, 2021 at 7:22
  • \$\begingroup\$ The code does in fact work, since I am new to this I still need to learn how to ask questions... well better. \$\endgroup\$
    – Avalanche
    Dec 12, 2021 at 16:08

1 Answer 1

4
\$\begingroup\$

learning a lot of things by the book personally doesn't suit me

I get that; I'm the same way. Doing projects like this will really help you. Keep this up - it's a very important habit to learning how to program.

Good things about your code - your weapon and enemy database structure is not completely crazy; and your program works! That's not nothing, particularly for a beginner.

I'm going to keep these suggestions mostly at a beginner-to-intermediate level.

Your code will be much cleaner if you cut out the database and put it in a separate file. JSON is the simplest format and is nearly a cut-and-paste from your current dictionary literal. You currently include a hierarchy where there are sub-dictionaries for each weapon and enemy type. This is a form of indexing, but there are many forms of indexing possible, and I'm going to suggest that for your purposes it may be easier to just have a flatter database format without this hierarchy, and to include a type field for each weapon and enemy. Indexing by type can happen in runtime.

You can simplify the name enemies_animals to just enemies.

Your weapon and enemy names can use normal spaces instead of underscores, and spaces will be nicer for your user interface.

Rather than assigning the enemy object to a wolf variable, assign it to a variable called enemy. The variable, and the code using it, should not care that it's a wolf - you should generalise.

Delete your sleeps - they're not helping anyone.

Don't hard-code your weapon type names into your prompt string. Instead, form this string dynamically from the weapon types in your database.

attack is a misnomer and should be called health.

Your use of randint is better-represented by randrange.

Your critical chance check is incorrect. Instead of equality, you should be checking for lesser-than <. Otherwise, your effective critical hit chance will always be 1%.

Work on variable reuse and identifying common sections of code. The golden rule is DRY - don't repeat yourself.

For the run-away check, do the simple thing first and early-return.

Suggested

game-database.json

{
  "weapons": {
    "katana": {
      "type": "melee",
      "speed": 6,
      "strength": 50,
      "range": 5,
      "critical_chance": 50,
      "critical_multiplier": 2,
      "damage": 100
    },
    "dull sword": {
      "type": "melee",
      "speed": 3,
      "strength": 15,
      "range": 4,
      "critical_chance": 10,
      "critical_multiplier": 1.2,
      "damage": 30
    },
    "sharp sword": {
      "type": "melee",
      "speed": 3,
      "strength": 25,
      "range": 4,
      "critical_chance": 25,
      "critical_multiplier": 1.5,
      "damage": 45
    },
    "broken machete": {
      "type": "melee",
      "speed": 4,
      "strength": 20,
      "range": 3,
      "critical_chance": 15,
      "critical_multiplier": 1.3,
      "damage": 30
    },
    "repaired machete": {
      "type": "melee",
      "speed": 5,
      "strength": 30,
      "range": 3,
      "critical_chance": 20,
      "critical_multiplier": 1.4,
      "damage": 60
    },
    "mace": {
      "type": "melee",
      "speed": 2.1,
      "strength": 60,
      "range": 4,
      "critical_chance": 10,
      "critical_multiplier": 1.1,
      "damage": 40
    },
    "longbow": {
      "type": "long range",
      "speed": 3,
      "strength": 55,
      "range": 100,
      "critical_chance": 100,
      "critical_multiplier": 1.5,
      "damage": 120
    },
    "slingshot": {
      "type": "long range",
      "speed": 5,
      "strength": 10,
      "range": 50,
      "critical_chance": 25,
      "critical_multiplier": 1.1,
      "damage": 20
    },
    "short bow": {
      "type": "long range",
      "speed": 4,
      "strength": 15,
      "range": 60,
      "critical_chance": 20,
      "critical_multiplier": 1.2,
      "damage": 40
    },
    "broken longbow": {
      "type": "long range",
      "speed": 2,
      "strength": 30,
      "range": 70,
      "critical_chance": 50,
      "critical_multiplier": 1.3,
      "damage": 60
    },
    "small rock": {
      "type": "throwable",
      "speed": 7,
      "strength": 7,
      "range": 20,
      "critical_chance": 10,
      "critical_multiplier": 1,
      "damage": 10
    },
    "dynamite": {
      "type": "throwable",
      "speed": 2.1,
      "strength": 100,
      "range": 20,
      "critical_chance": 20,
      "critical_multiplier": 1.7,
      "damage": 120
    },
    "makeshift explosive": {
      "type": "throwable",
      "speed": 2.3,
      "strength": 60,
      "range": 15,
      "critical_chance": 15,
      "critical_multiplier": 1.5,
      "damage": 95
    }
  },
  "enemies": {
    "zombie": {
      "type": "dead",
      "health": 150,
      "speed": 6,
      "damage": 20,
      "attack_speed": 4,
      "range": 3
    },
    "zombie wolf": {
      "type": "dead",
      "health": 250,
      "speed": 8,
      "damage": 25,
      "attack_speed": 4,
      "range": 3
    },
    "zombie bear": {
      "type": "dead",
      "health": 350,
      "speed": 4,
      "damage": 35,
      "attack_speed": 4,
      "range": 4
    },
    "chicken": {
      "type": "animal",
      "health": 20,
      "speed": 6
    },
    "wolf": {
      "type": "animal",
      "health": 100,
      "speed": 12,
      "damage": 20,
      "attack_speed": 3,
      "range": 3
    },
    "bear": {
      "type": "animal",
      "health": 200,
      "speed": 6,
      "damage": 25,
      "attack_speed": 3,
      "range": 4
    },
    "cow": {
      "type": "animal",
      "health": 50,
      "speed": 6
    },
    "large bat": {
      "type": "animal",
      "health": 100,
      "speed": 14,
      "damage": 15,
      "attack_speed": 5,
      "range": 2
    },
    "bandit": {
      "type": "regular",
      "health": 100,
      "speed": 10,
      "damage": 10,
      "attack_speed": 5,
      "range": 3
    },
    "hoarder": {
      "type": "regular",
      "health": 120,
      "speed": 10,
      "damage": 20,
      "attack_speed": 4,
      "range": 3
    },
    "mage": {
      "type": "regular",
      "health": 90,
      "speed": 10,
      "damage": 25,
      "attack_speed": 3.3,
      "range": 15
    },
    "marksman": {
      "type": "regular",
      "health": 75,
      "speed": 12,
      "damage": 50,
      "attack_speed": 2.2,
      "range": 50
    },
    "heavy bandit": {
      "type": "regular",
      "health": 150,
      "speed": 7,
      "damage": 15,
      "attack_speed": 3.4,
      "range": 4
    },
    "heavy hoarder": {
      "type": "regular",
      "health": 170,
      "speed": 7,
      "damage": 20,
      "attack_speed": 3.7,
      "range": 4
    }
  }
}

Python code

import json
import random
from collections import defaultdict


def load_database():
    with open('game-database.json') as f:
        data = json.load(f)

    return data['weapons'], data['enemies']


def index_weapons(weapons):
    indexed = defaultdict(list)
    for weapon_name, weapon in weapons.items():
        weapon['name'] = weapon_name
        indexed[weapon['type']].append(weapon)
    return indexed


def choose_weapon(weapons):
    weapons_by_type = index_weapons(weapons)
    types = '/'.join(weapons_by_type.keys())
    weapon_type = input(f'Choose which type of weapon to use ({types}): ')

    names = '/'.join(
        weapon['name']
        for weapon in weapons_by_type[weapon_type]
    )
    weapon_name = input(f'Choose from weapons ({names}): ')
    return weapons[weapon_name]


def use_weapon(weapon):
    damage = (weapon['damage'] + weapon['strength']) * 3 / 2
    critical = random.randrange(100) < weapon['critical_chance']
    harvest = False
    if critical:
        damage *= weapon['critical_multiplier']
        harvest = random.randrange(5) == 0

    return damage, critical, harvest


def attack_enemy(enemy, damage):
    health = enemy['health'] - damage
    alive = health > 0
    return health, alive


def run_game():
    weapons, enemies = load_database()

    user_name = input('Write down your username: ')

    choice = input('Welcome. For this test please choose either to attack, or to run: ')
    if choice.lower() != 'attack':
        print(f'{user_name} chose to run away!')
        return

    weapon = choose_weapon(weapons)

    damage, critical, harvest = use_weapon(weapon)
    harvest_suffix = ' and got its meat' if harvest else ''
    victory_suffix = ' with a critical hit' if critical else ''

    enemy_name = 'wolf'
    enemy = enemies[enemy_name]
    health, alive = attack_enemy(enemy, damage)
    if alive:
        print(f'The {enemy_name} has {health} health!')
    else:
        print(f'{user_name} killed the {enemy_name}{victory_suffix}{harvest_suffix}!')


if __name__ == '__main__':
    run_game()
\$\endgroup\$
1
  • \$\begingroup\$ Thank you very much! I will look into the code, and save it for later, I wish to learn how to use json and everything to a decent degree before implementing it in this code, the critical chance I realized it doesn't work and I fixed that issue. \$\endgroup\$
    – Avalanche
    Dec 12, 2021 at 16:10

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.