6
\$\begingroup\$

This program is the basis for a bigger game I'm working on. I want to make sure I did this correctly before moving on. Please give pointers to anything I can improve. I'm learning on my own, so I doubt my structure is the best it can be.

enemy.py module

class enemy:
    '''
    This is how to define users and items
    '''
    def __init__(self, name, hp, damage):
        self.name = name
        self.hp = hp
        self.damage = damage

class item:
    def __init__(self, name, hp_bonus, damage_bonus, shield):
        self.name = name
        self.hp_bonus = hp_bonus
        self.damage_bonus = damage_bonus
        self.shield = shield

game.py/ main module

import random
import time
from enemy import enemy
from enemy import item

Level = 1
Level_Bonus = [2.0, 5.0, 8.0, 11.0, 13.0, 15.0, 18.0]
Level_Max = 20
ITEM = ""
Enemy = ""
Potion = random.randint(3, 7)
Enemies_Fought = 0


Player = enemy("Player", 100, random.randint(5, 8))
Dog = enemy("DOG", 40, random.randint(1, 8))
Cat = enemy("Cat", 20, random.randint(1, 5))
Monkey = enemy("Monkey", 55, random.randint(5, 8))
Ogre = enemy("Ogre", 100, random.randint(10,120))


Ring = item("RING", 10, 0, 0)
Sword = item("SWORD", 0, 10, 0)
Shield = item("SHIELD", 0, 0, 3)
No_item = item("NO ITEM", 0, 0, 0)


def ITEM_CHANCE():
    global ITEM
    item_chance = random.randint(1,1000)
    if item_chance > 1 and item_chance < 80:
        ITEM = Ring
        Player.hp += ITEM.hp_bonus
        print('''
        Your health has increased by 10!''')
        print(f'''
        You found a {ITEM.name}!!!''')
    elif item_chance > 140 and item_chance < 175:
        ITEM = Sword
        Player.damage += ITEM.damage_bonus
        print('''
        Your damage has increased by 10!''')
        print(f'''
        You found a {ITEM.name}!!''')
    elif item_chance > 500 and item_chance < 600:
        ITEM = Shield
        Enemy.damage -= ITEM.shield
        print('''
        Enemy damage reduced by 3!!''')
        print(f'''
        You found a {ITEM.name}!!!''')
    else:
        ITEM = No_item
        print('''
        No items found''')


def which_enemy():
    global Enemy
    Enemy_choose = random.randint(1, 100)
    if Enemy_choose < 30:
        Enemy = Cat
    elif  Enemy_choose > 30 and Enemy_choose < 70:
        Enemy = Dog
    elif Enemy_choose > 70 and Enemy_choose < 95:
        Enemy = Monkey
    else:
        Enemy = Ogre



def fight():
    global Enemy
    global Player
    which_enemy()
    print(f'''
    You have come across a {Enemy.name}''')
    keep_going = input(f'''
    Do you wish to fight it, this will be a battle to the death and
    your hp is "{Player.hp}".
    1) Yes.
    2) No.
    Enter option: ''')
    if keep_going == "2":
        print(f'''
        The enemy hit you for {Enemy.damage} as you escaped.
        ''')
        Player.hp = Player.hp - Enemy.damage
        print(f'''
        Your health is now {Player.hp}
        ''')
        return False
    elif keep_going == "1":
        while Player.hp > 0 and Enemy.hp > 0:
            print(f'''
            The enemy hit you for {Enemy.damage} damage''')
            Player.hp = Player.hp - Enemy.damage
            if Player.hp < 0:
                Player.hp = 0
            print(f'''
            Your health is now {Player.hp}''')
            time.sleep(.2)
            print(f'''
            You hit the enemy for {Player.damage} damage''')
            Enemy.hp = Enemy.hp - Player.damage
            if Enemy.hp < 0:
                Enemy.hp = 0
            print(f'''
            The enemy's health is now {Enemy.hp}''')
            time.sleep(.2)
    else:
        return False
    if Enemy.hp == 0:      # Reset all enemy health for next round
        if Enemy == Dog:
            Enemy.hp = 40
        elif Enemy == Cat:
            Enemy.hp = 20
        elif Enemy == Monkey:
            Enemy.hp = 55



def main():
    while True:
        global Enemies_Fought
        global Level
        PLAY = input('''Do you wish to fight a creature?"
                1) Yes
                2) No''')
        if PLAY == "2":
            print('''
            You gave up. You loose.''')
            break
        elif PLAY == "1":
            ITEM_CHANCE()
            Player.hp = Player.hp
            fight()
            Enemies_Fought += 1
            Level += .5
            print(f'''
            Your level is {Level}''')
            if Level in Level_Bonus:
                Player.hp = 100
                Player.damage = Player.damage + 5
                print(f'''
                Your damage is {Player.damage}, and your hp has been        restored.''')
            if Player.hp <= 0:
                print(f'''

                You reached level: {Level}
                You killed: {Enemies_Fought} enemies
                ''')
                print('''
                You have died. Thanks for playing''')
                break
        else:
            print('''
            Enter valid option.''')


main()
\$\endgroup\$
1
  • \$\begingroup\$ Welcome to CodeReview! I'm sure you'll get some good feedback, but can you go ahead and include the enemy module as well? And edit the existing code so it's all one big "file", including any parts you may have left out? \$\endgroup\$
    – aghast
    Apr 27 '19 at 23:26
3
\$\begingroup\$

Gameplay

So I played the game:

Do you wish to fight a creature?"
                1) Yes
                2) No1

        No items found

    You have come across a DOG

    Do you wish to fight it, this will be a battle to the death and
    your hp is "100".
    1) Yes.
    2) No.
    Enter option: 1

            The enemy hit you for 7 damage

            Your health is now 93

            ... many lines elided ...

            Your level is 1.5
Do you wish to fight a creature?"
                1) Yes
                2) No2

            You gave up. You loose.

There are some glaring problems here. First, the extra quotation mark. Next, for some reason you are not printing a newline or a prompt after the Yes/No menu, so the user's choice is echoed after "No". Third, I don't know what that "No items found" message is for, but it's not appropriate at that point in the game. Fourth, all the hit/counter-hit turns are spaced evenly. It would probably be better if the lines were grouped into paragraphs. Finally, "loose" means "released" or "not tight". You want to say "You lose" (1 'o') which is the opposite of "You win".

Coding Style

Your coding style is pretty jarring. The official Python coding style guide is in PEP-8, which I recommend you follow slavishly until you develop strong opinions about a different style.

Organization

You have an enemy module, but I don't see a very good reason to keep that code separate. I suggest you just bundle it into the same file as the rest of your game source code. Python is not Java, and you don't need one file per class or anything. Putting related things together is the right thing to do.

Orthography

The simple rules for PEP-8 naming:

  • Use snake_case names for functions, methods, module, variables, and attributes.

  • Use PascalCase for class names.

  • Use ALL_CAPS for module- or class-level "constant" values. (Even though the standard library uses lower case. Do as they say, not as they do.)

Organization

Use this layout until you have to use a different one, and can justify the need.

#!/usr/bin/env python
""" Docblock describing the program or module.
"""
import from __future__  # Usually not present

__dundernames__         # Usually not present

import standard libary modules

import 3rd party modules

import project local modules

GLOBAL_CONSTANTS

Global_variables    # Location here is PEP8. Cap is my thing.

class ClassName:
    pass

def some_function():
    pass

def main():
    pass

if __name__ == '__main__':
    main()

Design

You have two classes, but they don't have methods. So I suspect you aren't comfortable with deep OO designs or anything just yet. That's fine, but I'm going to pitch my ideas in that direction. As you learn more OO, you'll know different ways to approach this same problem.

Create a TextInterface class

You are sending a lot of messages to the screen. And in those messages, you are working to try to insert newlines and manage the appearance of the message in some kind of consistent manner.

Instead of trying to manage this using triple-quoted strings with embedded newlines, why not create a dedicated class with methods to manage that for you? Create a TextInterface class with methods that reflect the kind of output you are sending:

class TextInterface:
    def stat_change(self, msg):
        print(f"\n{msg}")

    def found_item(self, msg):
        print(f"\n{msg}")

Then you can consistently change the formatting of all messages of a particular "type" by editing one function. All you have to do is pick the right type for each message you print.

Use parameters/return instead of globals

If you are treating a global variable as read-only, don't access it as a global (even if it exists at global scope). Instead, pass that value as an argument to the functions that need it.

Similarly, if you are setting a global variable inside a function, consider if you could just return the value from the function instead:

def ITEM_CHANCE():
    global ITEM
    item_chance = random.randint(1,1000)
    if item_chance > 1 and item_chance < 80:
        ITEM = Ring
        Player.hp += ITEM.hp_bonus
        print('''
        Your health has increased by 10!''')
        print(f'''
        You found a {ITEM.name}!!!''')

This could be:

def maybe_drop_item():
    drop_probabilities = (
        (Ring, 80),
        (Sword, 35),
        (Shield, 100),
    )
    drop_items, weights = zip(*drop_probabilities)
    cum_dist = list(itertools.accumulate(weights))

    # Chance of no drop
    if cum_dist[-1] < 1000:
        cum_dist.append(1000)
        drop_items.append(None)

    randval = random.random() * cum_dist[-1]
    item = drop_items[bisect.bisect(cum_dist, randval)]
    return item

Once you randomly choose an item, you can describe it while using it (I notice you have the Player as type Enemy, so make this a method on that class):

class Enemy:
    def use_item(self, item):
        if item is None:
            return

        TextInferface.found_item("You found a {item.name}!!!")

        if item.hp_bonus:
            self.hp += item.hp_bonus
            TextInterface.stat_bonus(f"Your health has increased by {item.hp_bonus}!")
        if item.damage_bonus:
            self.damage += item.damage_bonus
            TextInterface.stat_bonus(f"Your damaged has increased by {item.damage_bonus}!")

        if item.shield:
            self.shield += item.shield:
            TextInterface.stat_bonus(f"Enemy damages reduced by {item.shield}!")

(Note: this last function treats shields as permanent items, while your code applies them only to the current enemy. If you don't like that change, feel free to adjust it.)

\$\endgroup\$
1
  • \$\begingroup\$ Thanks for your time, and in depth response. I will work on what you have pointed out and start over again. \$\endgroup\$
    – William
    Apr 28 '19 at 11:16

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.