4
\$\begingroup\$

I created this game in Python as a learning experience and was hoping for advice to make it better. I also used global and was hoping for good alternatives.

import random
import pickle
import os
enemy_count = 0
health_pot_count = 5
level_up_counter = 1

class Player:

    def __init__(self, name):
        self.name = name
        self.level = 1
        self.health = 20
        self.max_attack = 6
        self.min_attack = 2
        self.armour = 1

    def __repr__(self):
        return('Player({})'.format(self.name))

    def __str__(self):
        return('Name: {}, Level: {}, Health: {}, Max Attack: {}, Min Attack {}, Armour: {}'.format(self.name, self.level, self.health, self.max_attack, self.min_attack, self.armour))

    def level_up(self):
        global level_up_counter
        if level_up_counter == self.level:
            level_up_counter = 1
            self.level += 1
            self.max_attack += 3
            self.min_attack += 3
            self.armour += 2
        else: level_up_counter += 1

    def armour_up(self):
        self.armour += 1

    def weapon_up(self):
        self.max_attack += 1

class World:
    def __init__(self):
            self.isenemy = random.choice([True, True, False])

    def enter_room(self):
        if self.isenemy:
            fight()

        else:
            if random.choice([True, True, False]):
                treasure()

            else:
                print('The room is empty')


class Enemy:
    def __init__(self):
        attack_buff = random.randint(-2,4)
        self.health = 5 * pc.level + random.randint(-5,5)
        self.max_attack = 3 * pc.level + attack_buff
        self.min_attack = 2 * pc.level + attack_buff
        self.armour = pc.level * random.randint(1,2)

    def __str__(self):
        return(f'Enemy Health {self.health}')            

def save():
    save_var = [current_room, rooms, pc, enemy_count, health_pot_count, level_up_counter, pc.name, pc.level, pc.health, pc.max_attack, pc.min_attack, pc.armour]
    with open('save.pkl', 'wb') as save_pickle:
        pickle.dump(save_var, save_pickle)


def get_save():
    global current_room, rooms, pc, enemy_count, health_pot_count, level_up_counter
    with open('save.pkl', 'rb') as save_pickle:
        current_room, rooms, pc, enemy_count, health_pot_count, level_up_counter, pc.name, pc.level, pc.health, pc.max_attack, pc.min_attack, pc.armour = pickle.load(save_pickle)



def save_clear():
    try:
        os.remove(os.path.abspath('save.pkl'))
    except Exception:
        pass

def yn(promt):
    loop = True
    print (promt)
    while loop:
        yn_output = input("Y/N: ").lower()

        if yn_output == 'y':
            loop = False
            return True

        elif yn_output == 'n':
            loop = False
            return False

        else: print('Invalid input!')

def new_game():
    global current_room, rooms, pc, enemy_count, health_pot_count, level_up_counter 
    enemy_count = 0
    health_pot_count = 5
    level_up_counter = 1
    save_clear()
    name = input("What is the name of are brave adventurer: ")
    #pc stands for player character.
    pc = Player(name)
    current_room = 0
    rooms = ['start_room']
    start_room = World()


def start_up():
    print('Welcome to text dungeon')
    start_loop = True
    while start_loop:
        new_game_test = yn('Is this a new game.')

        if new_game_test == True:
            if yn('Are you sure, this will clear all saves.'):
                start_loop = False
                new_game()


        elif new_game_test == False:
            start_loop = False
            get_save()

def new_room(room, current_room):
    room.append(str(current_room) + '_room')
    room[current_room] = World()    

def fight():
    increment_enemy_count()
    print('Starting combat!')
    enemys = []
    enemys.append('{}_enemy'.format(enemy_count))
    enemys[0] = Enemy()
    combat = True
    while combat:
        space()
        print(pc)
        print('Enemy Stats- Health: {}, Max Attack: {}, Min Attack {}, Armour: {}'.format(enemys[0].health, enemys[0].max_attack, enemys[0].min_attack, enemys[0].armour))
        print('What is your move.')

        action_wait = True
        while action_wait:
            action = input('1 = attack, 2 = Use health potion: ')
            if action == '1':
                attack_damage = random.randint(pc.min_attack, pc.max_attack)
                if attack_damage < enemys[0].armour:
                    print(f'You did 0 damage.')
                else:
                    enemys[0].health = (int(enemys[0].health) + int(enemys[0].armour)) - attack_damage
                    print(f'You did {int(attack_damage) - int(enemys[0].armour)} damage.')

                if enemys[0].health < 1:
                    combat = False
                action_wait = False

            elif action == '2':
                if health_pot_count > 0:
                    health_pot_use()
                    action_wait = False
                else: 
                    print('You have none.')

            else:
                print('Try again')

            attack_damage = random.randint(enemys[0].min_attack, enemys[0].max_attack)
            if attack_damage < pc.armour:
                print(f'You took 0 damage.')
            else:
                pc.health = int((pc.health) + int(pc.armour) - attack_damage)
                print(f'You took {int(attack_damage) - int(pc.armour)} damage.')

            if (pc.health < 1):
                combat = False
                death()

    print('You win')
    pc.level_up()
    treasure() 

def treasure():
    global health_pot_count
    loot = random.randint(1, 5)

    if loot == 1:
        print('You got a health potion!')
        health_pot_count += 1

    elif loot == 2:
        print('You got a better weapon!')
        pc.weapon_up()

    elif loot == 3:
        print('You got better armour!')
        pc.armour_up()

    else:
        print('No Loot')



def death():
    print('You lose')
    save_clear()
    main()


def increment_enemy_count():
    global enemy_count 
    enemy_count += 1

def health_pot_use():
    global health_pot_count
    health_pot_count -= 1
    health_back = pc.level * 2 + random.randint(-2, 6)
    pc.health  += health_back
    print(f'You got {health_back} health.')

def space():
    print()
    print()

def main():
    global pc, current_room, play
    start_up()
    save()
    play = True
    while play:
        wait_for_action = True
        while wait_for_action:
            save_clear()
            save()
            space()
            print(str(pc))
            print('What do you want to do.')
            action = input("1 = continue, 2 = Use health potion: ")

            if action == '1':
                play = False
                current_room += 1
                new_room(rooms, current_room)
                rooms[current_room].enter_room()

            elif action == '2':
                play = False
                if health_pot_count > 0:
                    health_pot_use()
                else: print('You have none.')


            else: print('Invalid action!')


if __name__ == '__main__':
    main()
\$\endgroup\$
3
\$\begingroup\$

Decision loops

Your implementation of a decision loop is... interesting to say the least. If you're using python-3.8, you can utilize the walrus operator. It's a way to assign variables within an expression. In this case, the while loop. Have a look:

def choice(prompt: str) -> bool: # replaced "yn" with "choice" #
    print(prompt)
    while decision := input("Y/N: ").lower():
        if decision in "yn":
            return decision == "y"
        print("Invalid input!")

Instead of just returning True or False, you can return the boolean expression that evaluates to a boolean. It's the same thing you're doing, but a lot simpler.

Type Hints

Using type hints help you and other people reading your code know what types of parameters are accepted and what types are returned by functions. If you look at the function above, you can see that prompt is a str, and the function returns a bool value.

Dealing with exceptions

def save_clear():
    try:
        os.remove(os.path.abspath('save.pkl'))
    except Exception:
        pass

Passing on an exception isn't a good idea. An Exception is raised, and you're essentially ignoring it. I would print an error message to the console, such as "ERROR: File not found!" or something related. Just to tell you whats wrong, instead of having an exception and not knowing what specifically is wrong. You should also try to catch specific exceptions if you can.

Consistency

You use three different ways to concatenate strings in your program. .format, f"" and +. Use one method and stick to it. I would recommend f"" because it allows you to directly include variables in your strings, rather than having to call a function (.format) to add them.

Globals

It's not recommended to use globals. They can have unseen consequences, increase the complexity of your program, and can lead to spaghetti code. I would try to find a way to write this program without using globals.

Boolean Comparison

Instead of

if new_game_test == True:

do this

if new_game_test:

new_game_test is a boolean value in itself, so you can just check that value.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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