[SOLVED] Having problems with classes, trying to create turn based combat with random enemies

Discuss how to use the Ren'Py engine to create visual novels and story-based games. New releases are announced in this section.
Forum rules
This is the right place for Ren'Py help. Please ask one question per thread, use a descriptive subject like 'NotFound error in option.rpy' , and include all the relevant information - especially any relevant code and traceback messages. Use the code tag to format scripts.
Post Reply
Message
Author
Kocker
Newbie
Posts: 5
Joined: Thu Jul 06, 2023 9:57 am
Contact:

[SOLVED] Having problems with classes, trying to create turn based combat with random enemies

#1 Post by Kocker »

So, with my very limited knowledge of coding, I bumped into a problem that I have 0 clue how to tackle.
I want to create a turn based combat focused game, where you could battle random opponents. The problem comes from the random part. At the beginning of this project, I started things small and simple to give myself time to understand the parts of the code that didn't seem too frightening.
Unfortunately I realized pretty early that I need some more complex line of codes, to able to do some things, and also, to avoid writing the same code 100s of times.

Here's the code with some comments on a few things that have problems/don't work. (there might be some typos/older version of code, cuz I tried to solve things a few different way, so please ignore those if there is any, and only focus on the class/random enemy related stuff. But I tried my best to find all of them, by going through everything a few times)
I'm not asking for help to solve everything, in fact my intuition suggests that most of, if not all problems comes from me not understanding a key part of classes. So, if someone could enlighten me what I'm doing wrong, I would really appreciate it. (unless what I want to do is not possible)

Code: Select all


# python block for classes

init python:   # basic stats for the characters, keeping things simple for now
    class chara:
        def __init__(self, name, max_HP, HP, ATK, crit_chance):
            self.name = name
            self.max_HP = max_HP
            self.HP = HP
            self.ATK = ATK
            self.crit_chance = crit_chance

    def incombat(self):                 # here, im trying to make something that would help me set up the random enemy
        if current_enemy == self.name:  # for the battles (there are probably major problems with it, that i dont understand/see)
            return True
        else:
            return False

    Creatures = []
    exists = 0

    while exists < 100:
        Creatures.append(chara((""), 0, 0, 0, 0))

        exists += 1

##################################
# placeholder images

image player_pic = "player.png"
image enemy_pic = "enemy.png"
image goblin_pic = "goblin.png"
image orc_pic = "orc.png"

##############################
# game starts here

label start:

    call variables

    menu:
        "Enter battle":
            $ current_enemy = renpy.random.choice(['Enemy', 'Goblin', 'Orc'])  ## the random enemy would be based on this
 
            python:                       # this, idk, i currently dont know enough to fully understand what is going on here
                for q in Creatures:       # but what i want, is to use the stats based on the random enemy chosen
                    if q.incombat(""):    # and apply it to the one in battle (i think it should be possible, but im not that smart)
                        combat_enemy = q.name
                        combat_enemy.max_HP = q.max_HP
                        combat_enemy.HP = q.HP
                        combat_enemy.ATK = q.ATK
                        comabt_enemy.crit_chance = q.crit_chance

            show screen HP_bars
            call p_vs_enemy

        "Do nothing":
            "Nothing happened."
            jump start

#########################
# some variables

label variables:
    $ Creatures[0] = chara("Player", 50, 50, 5, 50)
    $ Creatures[1] = chara("Enemy", 15, 15, 2, 0)
    $ Creatures[2] = chara("Goblin", 30, 30, 5, 25)
    $ Creatures[3] = chara("Orc", 50, 50, 10, 75)
    $ Creatures[4] = chara("combat_enemy", 0, 0, 0, 0) # i dont know if this is correct or needed at all, its here because i thought it would help/needed
    $ current_enemy = "none"    # i want to use this with "renpy.random.choice" ,and i was hoping that, i would be able to use that as something 
                                # that would help me set up battles with random enemies, if there is an enemy with that name
 
#############################################
# random number generator for battle actions, nothing here is final

label RNG:
    $ p_normal_attack = renpy.random.randint(1, 4)
    $ p_big_attack = renpy.random.randint(8, 12)
    $ e_normal_attack = renpy.random.randint(2, 8)
    $ e_big_attack = renpy.random.randint(10, 16)
    $ e_attack_type = renpy.random.randint(1, 2)
    $ crit = renpy.random.randint(1, 100)   # i use this for calculating crit chance, but it seems to be way too simple, so idk if
                                            # its working as intended (i mean, i think it should work, it seemed to work, until i 
    return                                  # tried to go for the random enemy set up)

###############################################
# placeholder images, eventually want to set up moving images so the battle feels more alive

label p_vs_enemy:
    show player_pic at left
    show enemy_pic at right
    jump combat

#############################
# screen for hp bars, right now its pretty simple, would like to implement something that displays the number as well later
# also, doesnt fully work, same problem as everywhere else

screen HP_bars:

    vbox:
        spacing 20
        xalign 0.1
        yalign 0.0
        xmaximum 600
        text "Player"
        bar value [Player.HP] range [Player.max_HP]
    vbox:
        spacing 20
        xalign 0.9
        yalign 0.0
        xmaximum 600
        text "[combat_enemy]"
        bar value [combat_enemy.HP] range [combat_enemy.max_HP]

#################################
# here, i would like to use this for every battle, instead of creating copies for every possible matchup
# right now its just plain text to keep things simple (setting up skills scares me xd)

label combat:

    while True:   ### here i wanted to set up Player.HP > 0 and combat_enemy.HP > 0: ,but it didnt work

        call RNG

        menu:  ### player turn starts here
            "Normal attack":
                if Player.crit_chance >= crit:
                    $ Player.ATK = p_normal_attack*2
                    $ combat_enemy.HP -= Player.ATK
                    "Player's noraml attack crits for [Player.ATK] damage!"
                else:
                    $ Player.ATK = p_normal_attack
                    $ combat_enemy.HP -= Player.ATK
                    "Player's normal attack does [Player.ATK] damage."

            "Hard attack":
                if Player.crit_chance >= crit:
                    $ Player.ATK = p_big_attack*2
                    $ combat_enemy.HP -= Player.ATK
                    "Player's hard attack crits for [Player.ATK] damage!"
                else:
                    $ Player.ATK = p_big_attack
                    $ combat_enemy.HP -= Player.ATK
                    "Player hard attack does [Player.ATK] damage."
            "Do nothing":
                "Nothing happened."

        if combat_enemy.HP <= 0: # winning the battle
            "Player wins."
            hide screen HP_bars
            hide player_pic
            hide enemy_pic
            jump start

        call RNG # enemy turn starts here

        if e_attack_type == 1: # this adds some randomness to the enemy (only 2 options for now)

            if combat_enemy.crit_chance >= crit:
                $ combat_enemy.ATK = e_normal_attack*2
                $ Player.HP -= combat_enemy.ATK
                "normal ATK crits for [combat_enemy.ATK] damage"
            else:
                $ Player.HP -= e_normal_attack
                "normal ATK does [e_normal_attack] damage"

        else:

            if combat_enemy.crit_chance >= crit:
                $ combat_enemy.ATK = e_big_attack*2
                $ Player.HP -= combat_enemy.ATK
                "combat_enemy big ATK crits for [combat_enemy.ATK] damage"
            else:
                $ Player.HP -= e_big_attack
                "combat_enemy big ATK does [e_big_attack] damage"

        if Player.HP <= 0:   # losing the battle
            "you lose"
            hide screen HP_bars
            hide player_pic
            hide enemy_pic
            jump start
            
 
Last edited by Kocker on Thu Mar 28, 2024 11:44 am, edited 1 time in total.

philat
Eileen-Class Veteran
Posts: 1912
Joined: Wed Dec 04, 2013 12:33 pm
Contact:

Re: Having problems with classes, trying to create turn based combat with random enemies

#2 Post by philat »

Honestly, I would simply put the game down and go study a python OOP course. Barring that, at least another working example. (e.g., viewtopic.php?t=59747 )

jeffster
Veteran
Posts: 409
Joined: Wed Feb 03, 2021 9:55 pm
Contact:

Re: Having problems with classes, trying to create turn based combat with random enemies

#3 Post by jeffster »

1. By usual convention, start class names with capital letters, and variable names with lowercase:

Code: Select all

class Chara:
#...
creatures = []
2. More importantly, you should keep correct indentation levels.

To have a method "incombat" in class "Chara", the definition of "incombat" must be indented as a part of "class Chara" block:

Code: Select all

    class Chara:
        #...
        def incombat(self):
In your code, "class chara" and "def incombat" are at the same indentation level:

Code: Select all

    class chara:
    #...
    def incombat(self):
which means "incombat" does not belong to that class definition.

3. If you want to set initial values for variables, instead of setting them in "init python" blocks, better use Ren'Py statements
* "define" for the values that wouldn't be changed in the game,
* and "default" for those which would be changed.
https://renpy.org/doc/html/python.html

Code: Select all

default creatures = []
default exists = 0
4. You don't need this:

Code: Select all

    while exists < 100:
        Creatures.append(chara((""), 0, 0, 0, 0))

        exists += 1
To add elements to a list, use .append():

Code: Select all

default creatures = []
#...
label variables:
    python:
        creatures.append(chara("Player", 50, 50, 5, 50))
        creatures.append(chara("Enemy", 15, 15, 2, 0))
        creatures.append(chara("Goblin", 30, 30, 5, 25))
        creatures.append(chara("Orc", 50, 50, 10, 75))
        current_enemy = ""   # or None. Both are "falsy" when checked in "if"
    return
5. Note that "return" above. It should be there if you call label "variables".

6. This part is questionable:

Code: Select all

        "Enter battle":
            $ current_enemy = renpy.random.choice(['Enemy', 'Goblin', 'Orc'])
            python:
                for q in Creatures:
                    if q.incombat(""):
Trying to loop through all the list of 100 creatures is hardly efficient. At least you should use "break" statement when you found the creature you need:

Code: Select all

    menu:
        "Enter battle":
            python:
                current_enemy = renpy.random.choice(['Enemy', 'Goblin', 'Orc'])
                for q in creatures:
                    if q.incombat():
                        combat_enemy = q.name
                        combat_enemy.max_HP = q.max_HP
                        combat_enemy.HP = q.HP
                        combat_enemy.ATK = q.ATK
                        combat_enemy.crit_chance = q.crit_chance
                        break
Note that "q.incombat()" shouldn't be called with parameters (like empty string "" in your code), because the only parameter defined for "incombat" function is "self". Read some tutorial about "classes in Python" to understand. E.g.
https://realpython.com/python-classes/# ... -with-self

Or the official docs:
https://docs.python.org/3/tutorial/classes.html

7. Actually Python has more convenient way to load the enemy characteristics there. Instead of a list, use a dict:

Code: Select all

default creatures = {
    "Player": chara("Player", 50, 50, 5, 50),
    "Enemy": chara("Enemy", 15, 15, 2, 0)),
    "Goblin": chara("Goblin", 30, 30, 5, 25)),
    "Orc": chara("Orc", 50, 50, 10, 75)),
    }
default current_enemy = None
#...

    menu:
        "Enter battle":
            python:
                current_enemy = renpy.random.choice(['Enemy', 'Goblin', 'Orc'])
                combat_enemy = creatures[current_enemy].copy()
With this code, you will copy characteristics of a random enemy ('Enemy', 'Goblin' or 'Orc') into "combat_enemy" variable.

I didn't check your code further, but hopefully this all will point in the right direction.

Kocker
Newbie
Posts: 5
Joined: Thu Jul 06, 2023 9:57 am
Contact:

Re: Having problems with classes, trying to create turn based combat with random enemies

#4 Post by Kocker »

First of all, thanks for the quick reply, I just woke up like an hour ago. The day starts off good!
Second of all, I forgot to mention that I'm quite a bad learner when it comes to new things that I don't know. What I mean by that is, unless I connect to the topic/problem on a somewhat personal level, I struggle very hard. That's why I started things on my own (kind of), instead of looking at already existing RPG/combat codes, because those are full off complex lines and such.

1.
By usual convention, start class names with capital letters, and variable names with lowercase
I'll try to remember this going forward.

2.
In your code, "class chara" and "def incombat" are at the same indentation level
I didn't even notice that, I feel silly. Makes total sense!

3.
If you want to set initial values for variables, instead of setting them in "init python" blocks, better use Ren'Py statements
* "define" for the values that wouldn't be changed in the game,
* and "default" for those which would be changed.
Oh, so that's why some of my early-early code didn't work, I mixed up these two. I WILL NOTE THIS DOWN

4.
To add elements to a list, use .append()
Mhm, I think I understand.

6.
Trying to loop through all the list of 100 creatures is hardly efficient. At least you should use "break" statement when you found the creature you need
Noted!
Note that "q.incombat()" shouldn't be called with parameters (like empty string "" in your code), because the only parameter defined for "incombat" function is "self".
Makes sense. That one got left there probably, cuz I think I originally had (self, chara) when defining the incombat.

7.
Actually Python has more convenient way to load the enemy characteristics there. Instead of a list, use a dict
OMG, I didn't even know this existed, I'm gonna look it up! I most likely need this for a lot of other things in the future, so I better be prepared.
With this code, you will copy characteristics of a random enemy ('Enemy', 'Goblin' or 'Orc') into "combat_enemy" variable
Yeah, this seems to be a great way to achieve what I want, but unlike the previous codes, this one has a part that troubles me, the .copy()
How exactly does it work? Do I leave it as is or do I need to put something there? Because:
Up until this point I corrected/deleted the codes based on your feedback, but there is an error saying: 'Chara' object has no attribute 'copy'
Do I need to define something in that class or am I just using the .copy wrong?

Appreciating the help, makes learning/understanding things less of a headache. (even though I get really nervous when using forums)

jeffster
Veteran
Posts: 409
Joined: Wed Feb 03, 2021 9:55 pm
Contact:

Re: Having problems with classes, trying to create turn based combat with random enemies

#5 Post by jeffster »

Kocker wrote: Thu Mar 28, 2024 2:56 am
With this code, you will copy characteristics of a random enemy ('Enemy', 'Goblin' or 'Orc') into "combat_enemy" variable
Yeah, this seems to be a great way to achieve what I want, but unlike the previous codes, this one has a part that troubles me, the .copy()
How exactly does it work? Do I leave it as is or do I need to put something there? Because:
Up until this point I corrected/deleted the codes based on your feedback, but there is an error saying: 'Chara' object has no attribute 'copy'
Do I need to define something in that class or am I just using the .copy wrong?

Appreciating the help, makes learning/understanding things less of a headache. (even though I get really nervous when using forums)
I was incorrect. .copy() exists for lists or dicts. Let's imagine you had

Code: Select all

creatures["Ogre"] = ["Ogre", 50, 50, 5, 50]
Then you don't just assign

Code: Select all

current_enemy = creatures["Ogre"]
because that would assign "a reference": current_enemy would point at the same object as creatures["Ogre"].
Meaning when you e.g. decrease HP of current_enemy, it would change creatures["Ogre"].
Therefore we would use instead

Code: Select all

current_enemy = creatures["Ogre"].copy()
But as you use a custom object instead of a list, then indeed instead of .copy() you just assign (copy) properties one by one.
(Or you define function "copy" for the Chara class, returning the object with properties copied from "self"). Something like

Code: Select all

    class Chara:
        #...
        def copy(self):
            return Chara(self.name, self.max_HP, self.HP, self.ATK, self.crit_chance)

Kocker
Newbie
Posts: 5
Joined: Thu Jul 06, 2023 9:57 am
Contact:

Re: Having problems with classes, trying to create turn based combat with random enemies

#6 Post by Kocker »

I really appreciate all the help, but I realized that as of now, I clearly don't have enough knowledge about classes and lists. So I'm gonna spend some time trying to learn more about them. At least I actually learned a few things that I didn't know , so it wasn't for nothing. (mainly the existence of dictionary) Many thanks for that!

So, for the time being, I'm gonna pause this "more complex" way of writing the code for random encounters, and I'm just gonna go back to my old way and just copy paste everything for each encounters and just decide which one triggers with a simple renpy.random.choice.
And when I have more knowledge/feel more comfortable with coding I come back to this topic (and hopefully shrink the size of the code xd)

Thanks again and have a good day!

jeffster
Veteran
Posts: 409
Joined: Wed Feb 03, 2021 9:55 pm
Contact:

Re: Having problems with classes, trying to create turn based combat with random enemies

#7 Post by jeffster »

Kocker wrote: Thu Mar 28, 2024 11:42 am I really appreciate all the help, but I realized that as of now, I clearly don't have enough knowledge about classes and lists. So I'm gonna spend some time trying to learn more about them. At least I actually learned a few things that I didn't know , so it wasn't for nothing. (mainly the existence of dictionary) Many thanks for that!

So, for the time being, I'm gonna pause this "more complex" way of writing the code for random encounters, and I'm just gonna go back to my old way and just copy paste everything for each encounters and just decide which one triggers with a simple renpy.random.choice.
And when I have more knowledge/feel more comfortable with coding I come back to this topic (and hopefully shrink the size of the code xd)

Thanks again and have a good day!
Happy learning! :-)

Post Reply

Who is online

Users browsing this forum: Bing [Bot], GetOutOfMyLab, Ocelot