Python dice class that returns individual rolls - python

I'm trying to make a Die() class, with a DiceShaker() subclass. The DiceShaker() subclass is supposed to take multiple copies of the Die() class, roll them, and then return the individual rolls. Here is my code for Die().
class Die(object):
'a class representing a Die'
def __init__(self, sides=6):
'initialize a die, default 6 sides'
self.sides = sides
def roll(self):
'roll the dice'
self.dieRoll = random.randint(1, self.sides)
def get(self):
'get the current value of the die'
return self.dieRoll
Here is my code for DiceShaker().
class DiceShaker(Die):
'DiceShaker class'
def __init__(self,dieCount=1, dieSides=6, dieList = [], dieList2 = []):
'initialize a dice shaker'
Die.__init__(self,sides = 6)
self.dieCount = dieCount
self.dieSides = dieSides
self.dieList = dieList
self.dieList2 = dieList2
def shake(self):
'shake all the dice in the shaker'
counter = 0
dieList = []
while counter != self.dieCount:
self.dieList.append(Die.roll(self))
counter = counter + 1
return self.dieList
def getIndividualRolls(self):
'get a lsit of integers of all the individual die rolls'
for items in self.dieList:
self.dieList2.append(Die.get(self))
return self.dieList2
getIndividualRolls() is supposed to return a list with all of the dice that were passed through shake(), but the list printed is always just one number.
Example:
d=DiceShaker(3)
d.shake()
[None, None, None]
d.getIndividualRolls()
[3, 3, 3]
I have shake() returning [none] just so I know that the correct number of dice are going through, but I can't figure out how why getIndividualRolls() keeps printing out duplicates. Can anyone help me figure out what I'm doing wrong?

This will be too long for a comment:
As an aside, you should really be learning on Python 3. Python 2 is on the way out. Everything below works in Python 3.
Why is your DiceShaker a subclass of Die? This makes no sense. Subclassing is for is a relationships. DiceShaker isn't a type of Die (just like a ShoppingCart isn't a type of CartItem). Your relationship is compositional. DiceShaker has Dies (which you reflect with your dieList). In this case dieList is all you need here. You don't need to inherit from Die.
The underlying reason why you get the same number rolled is because you inherited from die. By subclassing Die, you made DiceShaker behave like a Die. That means you could've called ds.roll() and ds.get() (where ds = DiceShaker()) and it would behave exactly the same as if you called it on a Die. This is effectively what you've done when you've wrote Dice.get(self). That's equivalent to self.get() (since DiceShaker extends Dice). But DiceShaker is only one Die, not multiple. So calling get() will always return the same thing. What you've effectively done is the following:
die = Die()
die.roll()
for other_die in self.dieList:
self.dieList2.append(die.get()) # note you're getting the same die each time
So to fix this, you don't need to inherit from Die. In fact you shouldn't. Instead compose DieShaker and Die. This means DieShaker should delegate to the Dies it contains (by calling get on them, not itself).
from random import randint
class Die:
def __init__(self, sides=6):
self.sides = sides
def roll(self):
self._rolled_value = randint(1, self.sides)
def get_value_rolled(self):
return self._rolled_value
class DieShaker:
def __init__(self, num_die=1, num_die_sides=6):
self.dice = [Die(num_die_sides) for _ in range(num_die)]
def shake(self):
for die in self.dice:
die.roll()
def get_values_rolled(self):
return [die.get_value_rolled() for die in self.dice]
Note how in get_values_rolled (equivalent to your DieShaker.getIndividualRolls), we call get_value_rolled (equivalent to your Die.get) on each Die that our shaker contains (in the list self.dice).
Also note that I cleaned up your code in the following ways:
It is convention in Python to use snake_case for variables/properties and functions
I renamed a few of the functions to make their relationship more clear (get_value_rolled and get_values_rolled)
I used list comprehensions (ex. [Die(num_die_sides) for _ in range(num_die)] is the same thing as your while loop that appends to your self.dieList, but is more pythonic)
I replaced while with a for. Typically in python you want to use a for when you're working with iterables (list list).
I removed some of the optional arguments to your DieShaker constructor. For your purposes, it doesn't make much sense to allow them to be passed in (If you want to wade into the more technical, there is a reason why you'd do this, but for your purposes now, don't). It looks like you may think you need everything to be a parameter to __init__, but you don't. Instead of this (which actually has a serious issue unrelated to the style/semantics):
Bad:
def __init__(self, num_die=1, die_sides=6, dice = []):
self.dice = dice
You probably should do this:
def __init__(self, num_die=1, die_sides=6):
self.dice = []
I removed your second list. You only need to maintain a list of die (since after calling roll() on each die, it will return the same thing for get_value() each time)

Your DieShaker class should just be a wrapper around a list of Die objects.
class Die(object):
'a class representing a Die'
def __init__(self, sides=6):
'initialize a die, default 6 sides'
self.sides = sides
# You should make sure that by the time someone has created a Die object,
# all the attributes they might want to access have been defined
self.roll()
def roll(self):
'roll the dice'
self.dieRoll = random.randint(1, self.sides)
# Note I got rid of Die.get
# Anyone who can call Die.get can see Die.dieRoll
class DiceShaker(object):
def __init__(self, dice=()): # Note we avoid mutable default arguments
self.dice = list(dice) # Presumably it would be nice to add/remove dice
def shake(self):
for die in self.dice:
die.roll()
# return self.get_dice()
# It doesn't really make sense for this to return anything
def get_dice(self):
return [die.dieRoll for die in self.dice]

Related

Python: difference class with __init__ and class without it [duplicate]

This question already has answers here:
What is the difference between class and instance attributes?
(5 answers)
Closed 4 months ago.
I'm learning how classes works and I got stuck on a thing which I can't explain, I also didn't find a solution on the Internet so here am I with my first question on StackOverflow.
class Swords:
damage = 5
Here is a class with only one line, with attribute damage.
Let's make an instance of the class:
sharp_sword = Swords()
And here is the moment of truth.
My class doesn't have an init function, so right now my sharp_sword.__dict__ is empty - my object of the class has an empty namespace, it doesn't have any attributes or methods. Well, we can create an attribute since we already defined it in the class itself:
sharp_sword.damage = 10
Now if we print(sharp_sword.damage) the return will be 10, if we print(Swords.damage), the return will be 5. That means that our instance of the class has its own attribute - and it is without any init inside my class.
I can create another object, set its damage to 15, and I will have two objects with different damage. Since we didn't change damage in class, if we print(Swords.damage), the return will be 5.
The question coming by itself - why then should I use __init__ inside my class to set properties for objects if I can do it without it with even fewer lines of code in my class?
class SwordsWithoutInit:
damage = 5
class SwordsWithInit:
def __init__(self):
self.damage = 5
big_sword = SwordsWithoutInit()
big_sword.damage = 10
sharp_sword = SwordsWithInit()
sharp_sword.damage = 20
print(f'big sword (class {type(big_sword)}) damage equals {big_sword.damage}')
print(f'sharp sword (class {type(sharp_sword)}) damage equals {sharp_sword.damage}')
# it both works the same way
# note that even if you create another object of class without init, it still will work the same as with init, there won't be any errors
another_sword = SwordsWithoutInit()
another_sword.damage = 33
print(f'big sword (class {type(big_sword)}) damage equals {big_sword.damage}')
print(f'another sword (class {type(another_sword)}) damage equals {another_sword.damage}')
print(f'Class without init still will have damage attribute equals to {SwordsWithoutInit.damage} in itself - we didnt change it')
'''
output:
big sword (class <class '__main__.SwordsWithoutInit'>) damage equals 10
#sharp sword (class <class '__main__.SwordsWithInit'>) damage equals 20
big sword (class <class '__main__.SwordsWithoutInit'>) damage equals 10
another sword (class <class '__main__.SwordsWithoutInit'>) damage equals 33
Class without init still will have damage attribute equals to 5 in itself - we didnt change it
'''
The way you've set this up, via some odd sequence you'll sort of get the result you expect either way, but it's mostly due to int being immutable.
So, let's look at a mutable type instead:
class Sword:
pass
class Spear:
pass
# All players get a sword by default
class Items:
weapons = [Sword()]
player1_items = Items()
player2_items = Items()
# give player2 a spear
player2_items.weapons.append(Spear())
# player1 should still just have a sword right?
# whoops! they have a spear too!
print(player1_items.weapons)
live example
You might now ask "well then I'll just use __init__ if I have mutable types." and I suppose that would work for the cases you're describing. It's just a strange way of structuring the code and will make it harder to read over time. To answer the question why you should favor assigning to self even "if I can do it without it with even less lines of code"..
Minimizing the number of lines isn't a good motivation to do something.
Keeping your code readable, maintainable, and fairly consistent is a good motivation.
Listen, init func is very important. With its help you can set the attributes while you create the object. like
sword = Sword(5)
This function is very useful, when you have multiple attributes to set coz you won't want to set each attribute separetly, it is also very useful when you have to make many object of the same the class, you won't want to write another extra lines for each just setting the attributes.
Hope that helps!!
Why do we use __init__ in Python?
Using __init__ is simpler
At first glance, your approach does seem shorter. By not implementing an __init__ method, your class definition becomes one line shorter and looks much cleaner:
class NoInitSword:
damage = 5
However, it actually requires a lot more code. You now have to write two lines each time you want a sword with a different value:
butter_knife = NoInitSword()
butter_knife.damage = 1
This may not seem like a big deal, but it can quickly get messy. For example, if you wanted to create a list of swords:
big_sword = NoInitSword()
big_sword.damage = 10
sharp_sword = NoInitSword()
sharp_sword.damage = 20
normal_sword = NoInitSword()
big_sharp_sword = NoInitSword()
big_sharp_sword.damage = 30
sword_collection = [big_sword, sharp_sword, normal_sword, big_sharp_sword]
Or:
sword_collection = [NoInitSword(), NoInitSword(), NoInitSword(), NoInitSword()]
sword_collection[0].damage = 10
sword_collection[1].damage = 20
sword_collection[3].damage = 30
A better approach
Fortunately, Python provides us a way of creating an object and setting its attributes to whatever values we want—all in just one line. The only thing we have to do is define an __init__ method.
This method takes arguments that we can use when initializing the object. For instance:
class Sword:
def __init__(self, damage=5):
self.damage = damage
This allows you to easily create new objects in one line:
big_sword = Sword(10) # big_sword.damage == 10
sharp_sword = Sword(20) # sharp_sword.damage == 20
normal_sword = Sword() # normal_sword.damage == 5
And that list example from earlier? Here's what it looks like with an __init__ method:
sword_collection = [Sword(10), Sword(20), Sword(), Sword(30)]

Swapping attribute of two instances

So I am trying to make a turn-based card game, and one of the cards, when played , can swap the hand of the "caster" and the "castee" (not sure if that's even a word). I created a Player class, and instances Jim, Pam, Dwight and Michael. Whenever that card is played I have to swap the hand attribute between two players.
class Player:
def __init__(self, name, hand=[]):
self.name = name
self.hand = [self.draw_card() for n in range(0, 2)]
I've tried creating a method which takes the "castee", which is the self in this case, and swaps hand with the caster, but it doesn't work. Here caster is the name of the other instance, e.g. it could be Michael.hand instead of caster.hand:
def hand_swap(self):
self.hand, caster.hand = caster.hand, self.hand
I've also tried the following with no luck:
def hand_swap(self):
myhand = self.hand.copy()
casterhand = caster.hand.copy()
setattr(caster, "hand", myhand)
setattr(self, "hand", casterhand)
After every loop, players and their hands are printed in a dictionary. The code doesn't throw up any errors, but no hand has been swapped and everything remains the same.
Is there something wrong with my code or is it not so straightforward when it comes to having one instance change another instance's attributes?
edit1: what "caster" is
edit2: how I check that it fails
If you add caster to hand_swap() you will achieve your goal.
def hand_swap(self, caster):
self.hand, caster.hand = caster.hand, self.hand
Then to swap hands
pam.hand_swap(michael)

How to use dictonary to create an object?

I'm new in stackoverflow and I'd like to make my first question for a problem in this code I've tried to write to learn objects in python.
I'm trying to call the creation of an object through a dictionary.
My purpose is to create an object thanks to a number, for example I have the dictionary newch = {1 : Character.new_dragon(), 2 : Character.new_goblin()} and when I call Player1 = newch[1] it should create a new dragon (#classmethod new_dragon) and assign it to Player1
The problem is that when i run the program, Character.new_dragon() and Character.new_goblin() are called automatically (i put a control print), but when I write "DRAGO" after the request "which player?" the functions aren't called because there isn't the control print
import random
class Character:
def __init__(self,idd,height,weight,att,defe):
self.idd=idd
self.height=height
self.weight=weight
self.att=att
self.defe=defe
#classmethod
def new_goblin(cls):
print('newgoblin')
return cls(1,getr(1,1.5,0.1),getr(40,60,0.5),getr(5,15,1),getr(6,10,1))
#classmethod
def new_dragon(cls):
print('newdrago')
return cls(2,getr(20,30,1),getr(500,2000,5),getr(50,150,3),getr(20,100,3))
def getr(start,stop,step): #returns float
x=random.randint(1, 1000)
random.seed(x)
return random.randint(0, int((stop - start) / step)) * step + start
play={1:'p1', 2:'p2', 3:'p3', 4:'p4'} #dict for players
newch={1:Character.new_dragon(),2:Character.new_goblin()} ############This doesn't work
i=1
while True:
char=input("which player? Drago or Goblin?").upper()
if(char=="DRAGO"):
play[i]=newch[1] #here i try to call Character.new_dragon()
i+=1
break
elif(char=="GOBLIN"):
play[i]=newch[2]
i+=1
break
print("write \'Drago\' or \'Goblin\'")
print(play[1].height, play[1].weight, play[1].att, play[1].defe)
Here's my code, if you could help me, I would be very glad, thanks
The new object is created immediately when you call Character.new_dragon(), and the object is then stored in the dict.
Instead you could not store the object in the dict, but the function that creates it. That function would be Character.new_dragon (without the ()). Then you can call that function when the player selects a character:
play[i]=newch[1]()
Complete code:
import random
class Character:
def __init__(self,idd,height,weight,att,defe):
self.idd=idd
self.height=height
self.weight=weight
self.att=att
self.defe=defe
#classmethod
def new_goblin(cls):
print('newgoblin')
return cls(1,getr(1,1.5,0.1),getr(40,60,0.5),getr(5,15,1),getr(6,10,1))
#classmethod
def new_dragon(cls):
print('newdrago')
return cls(2,getr(20,30,1),getr(500,2000,5),getr(50,150,3),getr(20,100,3))
def getr(start,stop,step): #returns float
x=random.randint(1, 1000)
random.seed(x)
return random.randint(0, int((stop - start) / step)) * step + start
play={1:'p1', 2:'p2', 3:'p3', 4:'p4'} #dict for players
newch={1:Character.new_dragon,2:Character.new_goblin} ############This doesn't work
i=1
while True:
char=input("which player? Drago or Goblin?").upper()
if(char=="DRAGO"):
play[i]=newch[1]() #here i try to call Character.new_dragon()
i+=1
break
elif(char=="GOBLIN"):
play[i]=newch[2]()
i+=1
break
print("write \'Drago\' or \'Goblin\'")
print(play[1].height, play[1].weight, play[1].att, play[1].defe)
This works, however I would not say it is the best coding style. Its hard to judge from only this piece of code, but it might be a better idea to make Drago and Goblin subclasses of the Character class and store the type of those classes in that dictionary.
newch={1:Character.new_dragon(),2:Character.new_goblin()}
As this is written, the new_dragon and new_goblin functions are called when the dictionary is created. This is why you are seeing them both run "automatically" every time you run your program.
If you instead declared the dict like:
newch={1:Character.new_dragon ,2:Character.new_goblin}
And later have something like:
if(char=="DRAGO"):
play[i]=newch[1]()
(note the parenthesis after the newch[1]) you should get what you want.
Incidentally, those break statements aren't necessary. The If/elif/else chain doesn't fall through like a switch statement in other languages.
When you are initialising the dictionary this way:
newch={1:Character.new_dragon(),2:Character.new_goblin()}
You are binding keys (1 and 2) to the return values of the new_dragon and new_goblin functions. You need to bind the functions(without calling them) like so:
newch={1:Character.new_dragon,2:Character.new_goblin}
Notice there are no brackets!
And then, when you create players, you execute those functions like so:
play[i]=newch[1]() Notice here we have brackets!
Additionally, if I may suggest an improvement of the code here:
if(char=="DRAGO"):
play[i]=newch[1]()
i+=1
To avoid the if statement, you can create you mapping with a string:
newch={"DRAGO":Character.new_dragon,"GOBLIN":Character.new_goblin}
And create instances just by calling
play[i]=newch[char]()
To handle errors, you can add just a single if statement checking whether the char string is in the list with dict keys.

Automatically delete class instance when one of its attributes becomes dead

Set Up
Say I have a Snit:
class Snit(): pass
And a Snot, which contains weak references to up to, say, four Snits:
import weakref
class Snot():
def __init__(self,s1=None,s2=None,s3=None,s4=None):
self.s1 = weakref.ref(s1)
self.s2 = weakref.ref(s2)
self.s3 = weakref.ref(s3)
self.s4 = weakref.ref(s4)
I also have a Snot factory:
def snot_factory(snits,w,x,y,z):
return Snot(snits[w],snits[x],snits[y],snits[z])
And a list of Snits (a snit_list as it were):
snit_list = []
for i in range(12):
snit_list.append(Snit())
Now I make a list of Snots using the Snits in my snit_list:
snot_list = []
for i in range(3):
snot_list.append(snot_factory(snit_list[4*i],snit_list[4*i+1],snit_list[4*i+2],snit_list[4*i+3]))
The Problem
Whoops! I don't need snit_list[3] anymore, so I'll go ahead and remove it:
snit_list.pop(3)
But now I have a Snot hanging out there with a dead Snit:
snot_list[0].s4 # <weakref at 0x00BlahBlah; dead>
This cannot stand! A Snot with a dead Snit is - obviously - total nonsense.
So I would really like for any references to the Snot to at least return as None after one or more of its Snits has been destroyed. But ideally, it would be even better for the Snot to be automatically removed from the snot_list list as well (len(snot_list) shrinks by the number of removed Snots).
What's a good way of going about this?
Clarification:
A Snot is an object that should only exist when there is a valid set of Snits ("valid" means it has the same number of defined Snits it was initialized with), with the following behavior:
If any one Snit in a Snot goes away (when no strong references remain), the Snot should also go away (this is why I have set the s1, s2, etc to be weak references). Note that a Snot could have been initialized with 4, 3, 2, or 1 Snit. The number of Snits doesn't matter, the death of the Snit is what matters.
If any one Snot that contains a reference to a Snit goes away, the Snit remains.
OPTIONAL: When a Snot is deleted, the data structure containing the reference to the Snot object is updated as well (the Snot gets popped)
OPTIONAL: When ALL the Snots that reference a certain Snit are gone, the Snit goes away too, and any data structures containing the Snit are updated as in #3 (the Snit gets popped).
So the ideal solution will allow me to set things up such that I can write code like this:
snits = get_snits_list(some_input_with_10000_snits)
snots = get_snots_list(some_cross_referenced_input_with_8000_snots)
#e.g.: the input file will say:
#snot number 12 is made of snits 1, 4, 7
#snot number 45 is made of snits 8, 7, 0, 14
do_stuff_with_snits()
snits.pop(7) #snit 7 is common to snot 12 and 45
assert len(snots) == 7998 #snots 12 and 45 have been removed
However, if this is too hard, I'd be fine with:
assert snots[12] == None
assert snots[45] == None
I am open to changing things around somewhat. For example, if it makes the design easier, I think it would be fine to remove the weak references to the Snits, or to maybe move them instead to the list of Snits instead of having the Snot members be weak refs (though I don't see how either of these changes would improve things).
I have also considered creating Snot subclasses - ClearSnot with 1 Snit, YellowSnot with 2 Snits, GreenSnot with 3 Snits, etc. I'm uncertain if this would make things easier to maintain, or more difficult.
Nothing is truly automatic. You'll need to either have a function that you run manually to check for dead Snits, or have a function that is part of Snot that is called whenever anything interesting happens to a Snot to check for, and remove, dead Snits.
For example:
class Snot:
...
def __repr__(self):
# check for and remove any dead Snits
self._remove_dead_snits()
return ...
def _remove_dead_snits(self):
if self.s1() is None:
self.s1 = None
... # and so on and so forth
The fun part is adding that call to _remove_dead_snits for every interesting interaction with a Snot -- such as __getitem__, __iter__, and whatever else you may do with it.
Actually, thinking a bit more about this, if you only have the four possible Snits per each Snot you could use a SnitRef descriptor -- here's the code, with some changes to your original:
import weakref
class Snit(object):
def __init__(self, value):
self.value = value # just for testing
def __repr__(self):
return 'Snit(%r)' % self.value
class SnitRef(object): # 'object' not needed in Python 3
def __get__(self, inst, cls=None):
if inst is None:
return self
return self.ref() # either None or the obj
def __set__(self, inst, obj):
self.ref = weakref.ref(obj)
class Snot(object):
s0 = SnitRef()
s1 = SnitRef()
s2 = SnitRef()
s3 = SnitRef()
def __init__(self,s0=None,s1=None,s2=None,s3=None):
self.s0 = s0
self.s1 = s1
self.s2 = s2
self.s3 = s3
snits = [Snit(0), Snit(1), Snit(2), Snit(3)]
print snits
snot = Snot(*snits)
print(snot.s2)
snits.pop(2)
print snits
print(snot.s2)
and when run:
[Snit(0), Snit(1), Snit(2), Snit(3)]
Snit(2)
[Snit(0), Snit(1), Snit(3)]
None
Okay, so you have a Snot with a variable amount of Snits.
class Snot(object):
def __init__(self, *snits):
self.snits = [weakref.ref(s) for s in snits]
def __eq__(self, other):
if not isinstance(other, self.__class__) and other is not None:
return NotImplemented
# are all my snits still valid
valid = all(s() for s in self.snits)
if other is None:
return not valid # if valid is True, we are not equal to None
else:
# whatever it takes to see if this snot is the same as the other snot
Actually having the class instance disappear is going to take more work (such as having dict on the class to track them all, and then other data structures would just use weak-references -- but that could get ugly quick), so the next best thing will be having it become equal to None when any of its Snits goes away.
I see that snits and snots are both lists -- is order important? If order is not important you could use sets instead, and then it would be possible to have a performant solution where the the dead snot is actually removed from the data structure -- but it would add complexity: each Snot would have to keep track of which data struture it was in, and each Snit would have to keep a list of which Snots it was in, and the magic would have to live in __del__ which can lead to other problems...

dynamic class instantiation confusion in python

I asked a similar, yet lousy, question very late last night (Access to instance variable, but not instance method in Python) that caused a fair bit of confusion. I'd delete it if I could, but I can't.
I now can ask my question more clearly.
Background: I'm trying to build a black-jack game to learn python syntax. Each hand is an instance of the Hand class and I'm now at the point where I'm trying to allow for hands to be split. So, when it comes time for a hand to be split, I need to create two new hand instances. Given that further splits are possible, and I want to reuse the same methods for re-splitting hands. I therefore (I think) need to dynamically instantiate the Hand class.
Following is a code snippet I'm using to block out the mechanics:
import os
os.system("clear")
class Hand():
instances=[]
def __init__(self, hand_a, name):
Hand.instances.append(self)
self.name = name
self.hand_a = hand_a
def show_hand(self):
ln = len(self.hand_a)
for x in range(ln):
print self.hand_a[x]
class Creation():
def __init__(self):
pass
def create_name(self):
hil = len(Hand.instances)
new_name = 'hand_' + str(hil + 1)
return(new_name)
def new_instance(self):
new_dict = {0: 'Ace of Clubs', 1: '10 of Diamonds'}
new_hand_name = {}
new_hand_name.setdefault(self.create_name(), None)
print new_hand_name
new_hand_name[0] = Hand(new_dict, self.create_name())
print new_hand_name[0]
hand = Hand("blah", 'hand')
hand_z = Hand("blah_z", 'hand_z')
creation = Creation()
creation.new_instance()
here is the output:
{'hand_3': None}
<__main__.Hand instance at 0x10e0f06c8>
With regard to the instance created by the following statement:
new_hand_name[0] = Hand(new_dict, self.create_name)
Is new_hand_name[0] new the variable that refers to the instance?
Or, is hand_3 the variable?
i.e. when calling an instance method, can I use hand_3.show_hand()?
First, to answer your questions: new_hand_name[0] is the variable that refers to the instance- more specifically, it is the value in the new_hand_name dictionary accessed by the key 0. The new_hand_name dictionary, if you printed it, would look like:
{'hand_3': None, 0: <__main__.Hand instance at 0x10e0f06c8>}
Adding the value of "hand_3" to the dictionary is unnecessary, but for that matter, so is the dictionary.
What you really want to do has nothing to do with dynamic instantiation of new classes, which has nothing to do with your problem. The problem is that a Hand might represent a single list of cards, but might also represent a list of lists of cards, each of which have to be played separately. One great way to solve this is to make a separation between a player and a hand, and allow a player to have multiple hands. Imagine this design (I'm also leaving out a lot of the blackjack functionality, but leaving a little in to give you an idea of how to work this in with the rest of the program).
def draw_random_card():
"""
whatever function returns a new card. Might be in a Deck object, depends on
your design
"""
# some code here
class Player:
def __init__(self):
self.hands = []
def deal(self):
"""add a random hand"""
self.hands.append(Hand([draw_random_card(), draw_random_card()]))
def split(self, hand):
"""split the given hand"""
self.hands.remove(hand)
self.hands += hand.split()
class Hand:
def __init__(self, cards):
self.cards = cards
def hit(self):
"""add a random card"""
self.cards.append(draw_random_card())
def split(self):
"""split and return a pair of Hand objects"""
return [Hand(self.cards[0], draw_random_card()),
Hand(self.cards[1], draw_random_card())]
Isn't that simpler?
In response to your comment:
You can refer to any specific hand as self.hands[0] or self.hands[1] within the Players class.
If you want to keep track of a particular hand, you can just pass the hand itself around instead of passing a character string referring to that hand. Like this:
def process_hand(hand):
"""do something to a hand of cards"""
h.hit()
print h.cards()
h.hit()
h = Hand(cards)
process_hand(h)
This is important: modifications you make to the hand in the function work on the actual hand itself. Why put the extra step of passing a string that you then have to look up?
Also note that information specific to each hand, such as the bet, should probably be stored in the Hand class itself.
If you are sure you want to refer to each hand with a specific name (and again, it's not necessary in this case), you just use a dictionary with those names as keys:
self.hands = {}
self.hands["hand1"] = Hand([card1, card2])
self.hands["hand2"] = Hand([card1, card2])
print self.hands["hand1"]
But again, there is probably no good reason to do this. (And note that this is very different than instantiating a new variable "dynamically". It would be a good idea to look into how dictionaries work).

Categories