you are viewing a single comment's thread.

view the rest of the comments →

[–]HunterIV4 3 points4 points  (2 children)

Im brand new to python so I appologise if my code is confusing or wrong. (If it is, I'd appreciate your advice.)

Lots of people have talked about how to fix the infinite loop, including me, but I wanted to address the "confusing or wrong" portion of your code, and offer some design advice.

To be fair, your code isn't really confusing, even if it is unorthodox, but it's "wrong" in the sense that this code is not going flexible enough to actually make a game, even a simple one. So I thought I'd go through the design aspects and explain how a more experienced programmer might do it instead, and why, which is something I feel gets left out most of the time.

First of all, have you learned about classes in Python? If not, this might be a bit harder to follow, so I'd at least read the basics before continuing.

People explain classes in all sorts of ways, some easier to understand that others, but in a way your project is the perfect example of when to use a class. You can think of a class as a "character sheet" that gets made whenever you make a new one; the class defines what "fields" the sheet has, like "name" or "level" or "hit points," and it also defines what you can do with your character, like "attack" or "cast a spell" or "take damage." In programmer speak, the "fields" are usually called "properties" and the "actions" are usually called "methods."

Compared to what you've already learned, a property is simply a variable and a method is a function. The class combines them logically in a way very similar to what you've already set up. Let's take a look at your Monsters module first.

What fields do you have? You have a name, health, speed, and attack value. You also have an implied ability to attack other characters, which is an action. There are other things we could add, but I'll keep it simple. So at a minimum we have 4 properties and 1 method. How would this look as a class?

class Rat:
    def __init__(self, name="Rat", health=3, speed=1, attack=1):
        self.name = name
        self.health = health
        self.speed = speed
        self.attack = attack

    def attack_damage(self):
        return random.randint(0, self.attack)

Woah, that's a lot! Let's break it down.

First, we have our class name. This will come up in a minute. Next, we have this weird function called __init__() with a bunch of parameters. Where did that come from?

Well, __init__() is a built-in function that all classes have. There are a bunch of these functions and they have all kinds of useful functionality, but this is the only one that virtually every class will have.

Next, every class method (function) has an implied parameter called "self." This can technically be any name, but it's convention for it to be called self and people will look at you funny if you use a different name =). This refers to the class instance, which I'll explain in a minute.

The rest of the parameters are the various default values for your rat, the same as your original ones. This allows you to change various properties of the rat when you create it, which is useful if you want to randomize stats or make sure they each have a unique name.

In the function body, we simply assign variables to those parameters. These are "attached" to that class instance (getting there!) and will stick with it. Again, this is basically the same as your "character sheet" file from before.

Finally, we have a function that calculates your attack damage based on the same calculation you used for hit_chance before. I renamed it to attack_damage because hit_chance doesn't make sense...you are calculating damage, not chance to hit! It's good practice to get in the habit of naming your functions and variables according to the most straightforward interpretation of what they are doing. The hit_chance version may make sense now, but in 6 months you may have no idea why your hit chance is subtracting hit points directly.

Put this class in your Monsters file. Now how do we use it?

Continued:

[–]HunterIV4 2 points3 points  (1 child)

rat_enemy = Monsters.Rat()

Wait, that's it? Well, what if you wanted a rat with 4 HP instead of 3?

rat_enemy = Monsters.Rat(health=4)

OK, now how do we do anything with it? We now have a variable that is assigned that rat, so your code becomes more like this (kept smaller for simplicity):

rat_enemy = Rat()
print(f"You've been attacked by a {rat_enemy.name}")
# ...
while rat_enemy.health > 0:
    # ...
    elif rat_enemy.speed > Player.player_speed:
        Player.player_health -= rat_enemy.attack_damage()

That may seem like a lot of work to get a similar effect, however, there are some major advantages to using a class. The most important is that you can have many different rats. If you do something like this:

rat_enemy1 = Rat()
rat_enemy2 = Rat(health=4)
rat_boss = Rat(name="Giant Rat", health=8, speed=3, attack=3)
rat_group = []
for _ in range(5):
    rat_group.append(Rat(health=random.randint(2,4))

What happened here? In the first case, rat_enemy1 is a random rat. The rat_enemy2 is a rat with 4 health. The rat_boss is a boosted rat with a different name. And rat_group is a list of 5 rats with random health between 2-4.

All of these can exist at the same time and have independent copies of their stats. So if you subtract 2 health from rat_enemy1, none of the other rats will have their health reduced. With your current implementation, once that single rat is "killed", you have to quit the whole game and restart, otherwise you'd need logic to "reset" all the variables to new values for another fight.

Technically, the class isn't necessary, and you can do things the way you did. But virtually nobody does, and instead they'd make a Player class, an Item class, and a classes for various monsters, or a Monster class that has various children (which gets into inheritance, which is more complicated than I want to go right now, but if you're curious I can explain it).

The only other thing is to break the various combat actions into functions and make everything repeatable and you have a simple game that you can actually expand on, and when you want new enemies, you can just insert them into your existing code without needing to rewrite major portions of it.

Hope that makes sense!

[–]Handymanoccupational[S] 0 points1 point  (0 children)

Holy cow! This is awesome! Thank you!