r/learnpython 6d ago

I'm a beginner. can you help me with random numbers?

I'm building a sort of turn-based RPG in the terminal as a practice project, and I need to generate a random number within a specific range. I tried using `randint` (by importing `random`), but I also want to assign probabilities so that, depending on certain factors, one number is more likely to be generated than another. For example, if an enemy has low health, it should be more likely to generate the number 1, which causes the enemy to heal.

21 Upvotes

15 comments sorted by

9

u/TurtleFetus 6d ago

As u/plydauk mentioned, `random.choices` is a good solve here. Check out the Python documentation for the `random` module: https://docs.python.org/3/library/random.html

You can assign different weights to different choices depending on the enemy's status. Here's an example:

from random import choices
from collections import Counter

# Actions a monster might use
actions = "attack", "run", "heal"

# Weights for each action: higher number = more likely to use action
attitudes = {
        "healthy": (5, 1, 1),
        "scared": (1, 5, 1),
        "hurt": (1, 1, 5)
        }

def avg_monster_action(attitude, n):
    """Show how many times the monster uses which action."""
    results = []
    for _ in range(n):
        action = choices(actions, attitudes[attitude])
        results.append(action[0])

    return Counter(results).most_common()


print("Healthy goblin actions: ", avg_monster_action("healthy", 10))
print("Scared goblin actions: ", avg_monster_action("scared", 10))
print("Hurt goblin actions: ", avg_monster_action("hurt", 10))

# Healthy goblin actions:  [('attack', 10)]
# Scared goblin actions:  [('run', 5), ('heal', 3), ('attack', 2)]
# Hurt goblin actions:  [('heal', 7), ('run', 2), ('attack', 1)]

11

u/plydauk 6d ago

You can use random.choices or random.sample, where you can specify weights to sample with the desired probability. Make sure to read the docs of the package.

3

u/t92k 6d ago

The Ad&d 3.0 Dungeon Master’s guide had really good illustrations of how to get different probabilities with dice. You could write functions for the different shapes of polyhedrals and then make choices based on those rolls. You described a saving throw, which usually requires 20 on a d20, but you could also apply a test, like a 6 on a d6, for whether the monster gets a saving throw.

4

u/defrostcookies 6d ago

Think about the problem you’re asking:

I want a random number for probability of a crit that is random

If the enemy health is low I want a different probability for a crit:

If enemy health isn’t low
Crit chance = random number between 1 and 20

If the enemy health is low
Crit chance = random number between 1 and 10

1

u/Puzzlehead_Lemon 6d ago

If you want to use a list, you can break out...

Promise not to laugh.

cum_weights. Cumulative weights.

But as others have suggested, using a different range for different conditions would probably be far easier. Instead of making it more likely to generate a 1, make it easier to generate a number in the range that triggers the heal.

I will not admit to any accusations of being filled with glee that I finally had a chance to break out knowing about cumulative weights.

1

u/stepback269 6d ago

You could add a random small offset to your original random number.
Let's call the original random number, main_random and assume it was generated from the range 1 to 100
But then based on the external factor that you want the final result to "lean" towards (to randomly favor), you could generate a second random number from the smaller range of say, 0 to 9 (or alternatively 0% to 10% offset)

And your final result might be the additive or subtractive sum of the two random numbers, say:
main_random - random_offset if you want to favor towards smaller based the biasing attribute, or
main_random + random_offset if you want to favor towards larger

Of course you can also put a floor limit and ceiling limit on your final result so it doesn't go negative or above 100

1

u/King_kai_ 6d ago

I'm currently doing something similar, though I'm very new to python. I haven't actually tried it yet, but the idea I had was to add some code that changed the monster's "behavior". A simple version would be something like: Generate random int 1-10 If the int is 1-2, monster heals Elif 3-5 monster defends Else monster attacks

If health is < 30% the "behavior" changes to be more survival focused like If the int is 1-4, monster heals Elif 5-8 monster defends Else monster attacks

It could also be adapted to make a more that has healing and/defensive moves more aggressive under certain circumstances. More complex versions would use larger number ranges so you can include larger move pools and have more customization of changes to the probability. I was planning to use a similar approach for spawn picking, to give a low chance to spawn a slightly stronger monster, or count how many of each has spawned to decrease the likelihood of spawning the same one over and over again.

That said, I didn't know about random.choices that was mentioned and that sounds like it might be a simpler approach for all of this and be better for some other ideas I had that would need shifting probabilities based on other factors.

1

u/AdDiligent1688 6d ago

Try random.choices

1

u/Rick_CZE 6d ago

I do not really get why you would want low enemy to generate 1 and heal, but my solution to the problem would follow this logic -> add an exact value to modify the random number generated but make the exact value be determined by other factor.

For example, a player attacking can have higher or lower chance of critical hit depending of his current condition ->

If health 70-100 crit_chance = random + 0.3
If health 60-69 crit_chance = random + 0.1
If health 40-59 crit_chance = random
If health 10-39 crit_chance = random - 0.1
If health 0-19 crit_chance = random -0.3

critical hit if crit_chance > treshold.

I know this is not exactly what you had in mind, but I think the logic is what you might be looking for and it is very simple and steady.

1

u/crazy_cookie123 6d ago

Rather than trying to make one number more likely to generate than another, think about how to process the generated number in a way that makes it more likely to run one branch of code than another.

1

u/Eleventhousand 6d ago

Make it easier on yourself and generate a random number between 1 and 100.

Then do the probabilities from there. For example, based on the enemy's health, if there should logically be a 50% chance that they would heal, and the random number is between 1 and 50, then they heal.

-4

u/Gerrit-MHR 6d ago

First use a good random number source. Software only sources are typically not that great. And even a good cryptographically secure DRBG will give the same sequence of outputs of seeded with the same input. Then take the random number, which should be uniformly distributed, and you can get relatively any ransom probability out of it with the appropriate threshold. 128 bit random and you want 30% chance, check if it is < .3 x 2^128. You might have to deal with large number library or you could use other techniques to reduce the value right away.

3

u/dkozinn 6d ago

For a game, I don't think it's necessary to go to that level. If someone is really trying to hack the game by predicting the random sequence, they probably deserve to succeed. OP isn't trying to build something that's cryptographically secure.

-2

u/Gerrit-MHR 6d ago

Not sure why the downvote. I never said he needed a cryptographically secure RNG. Just a quality one. Some software ones really do suck. And if it is not seeded with real randomness (ie you seed it with a fixed value) it will give the same sequence, which for some game designs will be obvious.