r/learnpython • u/yetanotherdud • 1d ago
how do i dymanically generate large numbers of objects in a class/dictionary?
my goal is to create an army manager for the tabletop game mythras, where each soldier has their own hp, attack value, etc, that i can use to add detail to the large scale battles. i want to be able to direct commands to it (eg, 'damage 3 soldiers') and have it randomly determine which soldier is attacked, how much damage it does, and whether it kills them (as well as how it heals up, following the standard healing rules). I don't have a lot at the moment at all, just a dictionary that contains a nested dictionary for each soldier's hp and attack value, randomly determined:
from random import randint
Soldier1 = {
"HP" : randint(1,10),
"ATK" : randint(1,10),
}
Soldier2 = {
"HP" : randint(1,10),
"ATK" : randint(1,10),
}
Soldier3 = {
"HP" : randint(1,10),
"ATK" : randint(1,10),
}
myArmy = {
"Soldier 1" : Soldier1,
"Soldier 2" : Soldier2,
"Soldier 3" : Soldier3
}
print(myArmy)
from random import randint
Soldier1 = {
"HP" : randint(1,10),
"ATK" : randint(1,10),
}
Soldier2 = {
"HP" : randint(1,10),
"ATK" : randint(1,10),
}
Soldier3 = {
"HP" : randint(1,10),
"ATK" : randint(1,10),
}
myArmy = {
"Soldier 1" : Soldier1,
"Soldier 2" : Soldier2,
"Soldier 3" : Soldier3
}
print(myArmy)
now, that works fine for the three soldiers listed there, but i don't need 3 soldiers, i need 300. how can i input a number and have it create that many soldiers on the fly?
and, further to that, are dictionaries the best choice for this? i know the technical difference between dictionaries and classes, but i don't have any real-world context to either to know which is best for what i need.
2
u/PvtRoom 1d ago
you're gonna want to use classes. - allows you to easily distinguish different types of soldier (archer, infantry, cavalry, knight, heavy cavalry, siege engines, captain, general, etc.) who will have different rules (speed, attack speed, defence, attack power, position, attack range, hit chance) and different capabilities (infantry can try the Roman testudo, cavalry can jump 6 foot wide gorges)
1
u/DrShocker 1d ago
Honestly, that's an implementation detail that I'm not sure I'd personally breakdown the same way. I'd probably compose an Archer as being a soldier with components like ranged attack rather than having a distinct class for every variation.
1
u/PvtRoom 1d ago
whetether it's a mega class with 30 variations or a parent class with 30 children:
soldier.createinfantry(....)
or
soldier.infantry.create(....)
not sure I care.
1
u/DrShocker 1d ago
That's not the difference I'd see, it's more like how annoying it is to add new kinds of units. If it's all in a class heirarchy you end up creating new classes for new situations which you end up defining everything there again and again like health or movementspeed or whatever. And if you rely on inheritance for only defining some things then you end up coupling the implementation to the inheritance tree which can lead to confusion about which numbers affect which others.
I'd much rather take a base solider, set max health lower, add a magic attack, etc and then you've got a mage without putting the logic to define that all over the place in different classes. That also means you are already set up to do something like swap the skills or stats or whatever with an opponent when a skill is used. With inheritance that behavior is embedded into the class itself so messing with it is harder.
In general "prefer composition over inheritance" because it enables you to put together behaviors in more interesting ways.
1
u/PvtRoom 1d ago
I'm assuming you're optimising for easier development.
that may be a bad goal. - a bit like optimizing a dining chair for sleeping on.
1
u/DrShocker 1d ago
Let's say that's true, what does the alternative you proposed solve better?
If I wanted to optimize runtime speed there's some more fundamental changes I'm making so it's not that. If it's correctness then there's an argument to be made that falls under "easier development" since that's a broad category.
1
u/PvtRoom 1d ago
like I said, I don't particularly care which solution - he's still gonna have an Army class containing a soldiers class or a family of soldier like classes, of which some might have a child class of familiars or trained animals or mounts
it's op who needs to address that, knowing what he wants to do.
4
u/MicrotubularMushroom 1d ago
Create a list or dict to hold the objects, then create the objects in a for loop, and add them to said list or dict.
3
u/Excellent-Practice 1d ago
Notice that your soldiers have the same attributes. Also consider that you may want to track multiple armies and/or split those armies into subordinate units of which you may have several instances. Further, managing all those dictionaries will get cumbersome if you have to specify which of several variables you want to update.
You can address all of those issues if you rework your concept using classes. I would suggest building a soldier class along with a class for each echelon of unit you want to work with and include methods that deal damage using random.choices().
Your soldier class might look like:
class Soldier:
def __init__(self):
self.hp=randint(1,10)
self.atk=randint(1,10)
def injure(self):
self.hp-=1
And an army class:
class Army:
def __init__(self, size):
self.roster=[]
for i in range(size):
self.roster.append(Soldier())
def damage(self, n):
victims = random.coice(self.roster, k=n)
for victim in victims:
victim.injure()
Those are just very simple classes which you can elaborate on. After writing those templates, all you have to do is call the Army class and pass an integer. That will build an arbitrary large list of Soldier objects, each of which has a randomized HP and ATK score. You can deal damage to a randomized set of soldiers within your army and you can iterate through your roster if you want to check on each soldier's status. The next step you probably want to implement is a method to remove soldier's or mark their status as casualties if their HP hits zero. Maybe you also want an attribute in your Army class that reports overall health or average strength for the force as a whole
2
u/UlisKore 1d ago
Did you try making an object out of your soldiers? Several things you say point in that direction :
- you want a quick way to create several
- they will have individual attributes
- they will have a common set of skills
My only doubt is about the killing part, I don't think the state of the victim should be solely determined by a rule that the attacker bears.
1
u/Usernamenotta 1d ago
If your choice of implementation is coding each soldier manually, you are going to have a bad time.
It is true that the fastest way possible (complexity wise) is to assign each user manually. However, if you want to expand the scale and/or scope of project, you will be pulling hairs.
I would suggest that the first thing you do is take a pen and some paper and sketch your ideas, then split them into classes, actions and relationships. Then set out the mechanics of your game. Again, break those apart into who does what and to whom.
Based on the very limited description you've provided, you can structure your code in 3 sections.
- Individual character model: Create a python class which describes an individual in your army. I will give you a hint of how you can make one class for a uniform army:
class Soldier:
def __init__(self, soldier_id, HP, ATK, parameter_3, parameter_4 etc.):
self.soldier_id=soldier_id
self.HP_MAX=HP
self.current_HP=HP
self.ATK=ATK
def display_soldier(self):
print("Soldier number ", self.soldier_id, " has ", self.current_HP, " HP left and deals", self.ATK, " points of damage")
2. Create an army generator. It's a bit faster to generate a vector the size of our army for each parameter and assigning a soldier a value from that parameter
def generate_army(army_size):
ids=np.arrray((1,army_size))+1 #I always confuse the numpy synthax with the normal synthax. Please check numpy doc for proper synthax. What this assignment should do is create a vector (dimensions 1 row, n collumns), then add 1 so the ids start at 1.
hps=(Highest_HP_that_you_want_a_soldier_to_Have-lowest_hp_that_you_want)*np.random.rand((1,army_size))+lowest_hp_that_you_want
same thing for attack
army=[]
for i in ids:
army.append(Soldier(i,hps[i-1],atks[i-1]):
return army
player_1_army=generate_army(player_1_army_size)
player_2_army=generate_army(player_2_army_size)
armies={-1: player_1_army,
1: player_2_army}
3. Create game loop logic. I am going to write a very simple loop focusing on ask for input from player 1, execute attack
current_player=-1
while True:
enemy=current_player*-1
selected_soldier=input(f"Player {current_player} Type the id of the soldier you want to initiate attack ')
selected_enemy_1=random.choice(armies[enemy])
selected_enemy_3=random.choice(armies[enemy])
selected_enemy_2=random.choice(armies[enemy])
selected_enemy_1.current_HP=selected_enemy_1.current_HP-armies[current_player][selected_soldier).ATK
if selected_enemy_1.current_HP<=0:
armies[enemy].pop(sel_enemy_1)
selected_enemy_2.current_HP=selected_enemy_2.current_HP-armies[current_player][selected_soldier).ATK
if selected_enemy_2.current_HP<=0:
armies[enemy].pop(sel_enemy_2)
selected_enemy_3.current_HP=selected_enemy_3.current_HP-armies[current_player][selected_soldier).ATK
if selected_enemy_3.current_HP<=0:
armies[enemy].pop(sel_enemy_3)
print (" Army of player 1 is" armies[-1])
print (" Army of player 2 is)" armies[1])
current_player=enemy
1
u/kilkil 19h ago edited 19h ago
python (and programming languages in general) have this thing called loops, where you can execute code repetitively. for example, to print all the numbers from 1 to 10, you can use a loop like so:
py
for n in range(1, 11):
print(n)
here is a link to a tutorial for how to use loops: https://www.learnpython.org/en/Loops
python (and other languages) also have this thing called functions, where you can take some chunk of code, and instead of having to copy-paste it, you can just define the chunk once, then reuse it. here is a tutorial for functions: https://www.learnpython.org/en/Functions
in your case, each soldier has a well-defined shape. instead of always having to write the same dict, you can use a function:
```py def make_soldier(): hp = randint(1, 10) atk = randint(1, 10) return { "HP": hp, "ATK": atk }
my_army = dict() for i in range(300): soldier_name = f"Soldier {i}" soldier = make_soldier() my_army[soldier_name] = soldier
print(my_army) ```
some more notes:
if you want to make this input dynamic, there are a few different ways to do it. since you are new to coding, probably the easiest thing will be to use Python's builtin input() function, which allows whoever is executing the program to enter some input text.
you are correct in your intuition that dictionairies are not really the nicest tool for what you are trying to do. for the army (the list of soldiers), it is probably better to use a list. as for each individual soldier... if this were a larger / more complex program, I would recommend using a class instead of a dictionary. but since this is a basic script I don't really think it matters much. if anything a class would just add more unnecessary lines of code (unless you use a namedtuple, up to you if you want to explore more on that side).
a simple script using these concepts (minus the namedtuple bit) might look like this:
```py from random import randint
def make_soldier(): hp = randint(1, 10) atk = randint(1, 10) return {"HP": hp, "ATK: atk}
count = int(input("enter # of soldiers: "))
soldiers = [] for _ in range(count): s = make_soldier() soldiers.append(s)
print(soldiers) ```
of course, this script doesn't have input validation — what if the user enters a bad value, like "banana", instead of a number?
also, it might be a bit confusing which soldier has which stats, so you probably want each soldier to have a name.
another consideration is, you probably want your script to output something more convenient. for example if you save the output to a CSV file (CSV = "comma separated values"), you can import that directly into Google Sheets or Excel, and get your soldier values in a convenient to look at table. or maybe you can think of something even more convenient for you and your friends, idk.
here is an example of what such a script might look like, if I wrote it. feel free to ignore this and write it yourself, only peek at the solution if you want:
```py from random import randint from collections import namedtuple import csv
Soldier = namedtuple("Soldier", ["name", "hp", "atk"])
def soldier(n): name = f"Soldier {n}" hp = randint(1, 10) atk = randint(1, 10) return Soldier(name, hp, atk)
user_input = input("enter # of soldiers:")
assert user_input.isdigit(), f"expected {user_input=} to be a whole number"
count = int(user_input)
assert count > 0, f"expected {count=} to be greater than 0"
soldier_list = [soldier(n) for n in range(1, count+1)]
with open("soldiers.csv", "w", newline="") as f: w = csv.writer(f) w.writerow(["NAME", "HP", "ATK"]) w.writerows(soldier_list) ```
this script:
- asks the user to enter a soldier count
- checks that the count is a positive whole number
- creates that many soldier objects (each one is a basic object created using the builtin namedtuple class
- gives each soldier a name based on its position in the list
- writes the soldier objects to a CSV file called "soldiers.csv"
this script also uses a weird bit of syntax near the end called a list comprehension, which is a python shorthand for creating a list using a loop (you can see it has the "for ... in ..." keywords, which the language designers intentionally chose to remind you of the original for loop syntax).
as a final note, if you are running this script on the command line (in a terminal), you may also consider making the input an argument, so that the script can be invoked as e.g. python3 myscript.py 300. but that's up to you.
EDIT: just saw your post has additional future requirements. will follow-up with a part 2 comment.
1
u/kilkil 19h ago
i want to be able to direct commands to it (eg, 'damage 3 soldiers') and have it randomly determine which soldier is attacked, how much damage it does, and whether it kills them (as well as how it heals up, following the standard healing rules)
this is pretty ambitious! in order to achieve this, you are going to need to design something more than a simple script. your program is going to need a few pieces:
1) structured data
you will need to figure out how you want all the info about your soldiers to be organized within your program. you already have some intuitions for this, as you were thinking about class vs dictionaries. in addition to that, you need to make a list of all the things you need to know for each soldier, aside from HP and ATK. and you will need to figure out how any given soldier will be selected (is it by name? by number? by their current location?). once you know that, you have some clues that will lead you (or help us lead you) towards the right data structure(s).
2) storage
you will need some form of persistent storage, to save your program data. you will need this so your program can "remember" the state of all the soldiers.
technically you can use any file format, e.g. CSV. however, it sounds like you will need to store a large amount of structured data (which soldier is located where on the map, how much hp do they have), which CSV is not great for. it's probably doable, but CSV is a very simple/basic storage format and if you try to use it for this, you will find yourself running into a lot of issues / edge cases / bugs / etc.
the most "proper" solution would be to use a SQLite database — they are saved as files, but the storage format is very structured, and you can query it using SQL ("structured query language"), which as you can tell from the name was designed for this exact situation. however, the catch is that you have to learn SQL, and learn how to use sqlite with python. however, it is what I would recommend.
if you don't want to learn SQL, you can try saving your data as JSON instead. however you might run into similar downsides to using CSV (though I would say using JSON is generally less painful than using CSV).
3) command parsing
if you want your program to be able to interpret various user given commands (e.g. "damage 3 soldiers"), you need to come up with some rules this input should follow (you can write them down in some notes/documentation so you don't forget them). then you need to parse the input, that is write some code that will look through the input, figure out if it is valid, and if it is, figure out what to do based on the input.
all 3 of these are pretty hefty lifts! but I guarantee you, by the end of this project, you will have learned a lot about software development. good luck and have fun
1
u/danielroseman 1d ago
Why do you need the separate Soldier1 etc variables in the first place? You don't, you can just generate them directly in the army dict.
my_amy = {}
for i in range(100):
my_army[f"Soldier {I}"] = {
"HP" : randint(1,10),
"ATK" : randint(1,10),
}
`
which can be shortened to a dict comprehension:
my_army = {f"Soldier {I}": {
"HP" : randint(1,10),
"ATK" : randint(1,10),
} for i in range(100)}
Note, you probably don't want a dict though; why do the soldiers need names? Better to use a list and access them via their index.
1
u/yetanotherdud 1d ago
the soldier1, soldier2 stuff is just placeholder, i want to be able to do it on the fly. your code is exactly what i was looking for though! the whole fstring stuff and bracketing tripped me up.
what do you mean by using a list and index? can i store multiple values (HP, atk, etc) in a list?
3
u/danielroseman 1d ago
I mean a list for my_army - so a list of dicts rather than a dict of dicts.
Then it's just:
my_army = [{ "HP" : randint(1,10), "ATK" : randint(1,10), } for i in range(100)]and you can access an individual soldier via
my_army[99]or whatever.-1
u/Electrical_Toe8997 1d ago
You are very correct from a code standpoint, but this comment shows the horrors of war: living, breathing, named individuals are reduced to their utility and to their number in the system. Their essential humanity is wiped out forever, while war rages on...
1
u/LayotFctor 1d ago edited 1d ago
Dictionaries and classes are similar, but classes allow adding behavior (methods) while dictionaries don't. If your soldiers need methods too, which I believe is the case for you, you should use classes. Dictionaries are pure data containers only, but you're right they're extremely similar with similar performance too.
Create 300 soldiers with a for loop, storing them in another array. It might have slight bit delay, so ideally do this type of tedious tasks at the beginning of a round before anything imoortant has started yet. The classic loading screen basically.
0
u/jmooremcc 1d ago
Why are you using a dictionary instead of creating a custom object (OOP) to represent a soldier? An object is a collection of data and the methods that work with that data.
So if you create a Soldier class with the appropriate attributes plus methods that implement the Soldier object’s behavior, you can easily and dynamically create multiple instances of the Soldier object, each with their own set of attributes and a common set of behaviors.
0
u/DuckSaxaphone 1d ago
You really want a class. That can have the attributes of a soldier that you're putting in a dictionary but it can also contain the functions that handle the damage and death logic.
In general, if you're making lots of identical dictionaries, you want a class.
As for creating many, you can do a loop to create a list of as many objects as you need.
12
u/Altruistic_Sky1866 1d ago
as u/UlisKore mentioned, why don't you use class? I personally feel using classes is best way to implement what you want which will allow you to have different types of soldiers as well