Dealing Cards in Python? [closed] - python

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 9 years ago.
I'm making a program that deals cards and assigns 5 random cards to each player, and it works up until I try to print the hands of each player (showHand function). I am trying to print the cards the given player has but it's telling me "cards" isn't a global attribute. I know it isn't, but I don't know how to print the cards for the player otherwise. Help?
import random
NUMCARDS = 52
DECK = 0
PLAYER = 1
COMP = 2
cardLoc = [0] * NUMCARDS
suitName = ("hearts", "diamonds", "spades", "clubs")
rankName = ("Ace", "Two", "Three", "Four", "Five", "Six", "Seven",
"Eight", "Nine", "Ten", "Jack", "Queen", "King")
playerName = ("deck", "player", "computer")
#assigns random card to given player
def assignCard(str):
#generates random card number to assign to player
randomNum = random.randrange(0, NUMCARDS-1)
#makes sure card being picked for player is not already assigned to another player
while cardLoc[randomNum] != 0:
randomNum = random.randrange(0, NUMCARDS-1)
cardLoc[randomNum] = str
#shows all the cards in the deck
def showDeck():
print "# Card Location"
print "---------------------------"
cardNum = 0
for x in rankName:
#assigns all ranks
rank = x
for y in suitName:
#assigns all suits
suit = y
cards = "%s of %s" % (rank, suit)
cardNum += 1
location = cardLoc[cardNum-1]
location = detLocation(location)
print "%s %s %s" % (cardNum, cards, location)
global cards
#has program print out appropriate location instead of number
def detLocation(location):
if location == PLAYER:
return "Player"
elif location == COMP:
return "Computer"
else:
return "Deck"
#shows given player's hand... but not their foot (;
def showHand(str):
global cards
location = detLocation(str)
print "Displaying %s Hand:" %location
for i in range(5):
cardLoc[cards] = str
print "%s" % cardNum
#calls all functions necessary
def main():
clearDeck()
for i in range(5):
assignCard(PLAYER)
assignCard(COMP)
#showDeck()
showHand(PLAYER)
showHand(COMP)

First of all your assignCard function doesn't modify the global variable (I assume that is what you actually wont to do)
so you have to add a line there like global cardLoc
having modified this global variable you can print your cards with the following code
for i in range(NUMCARDS-1):
if cardLoc[i] == str:
print whatever card is assigned to the position of i in your deck.

I think you need a global cards object that gets initialized and contains the label. Similar to what you do in showDeck. It could be just an array of NUMCARDS. Then in showHand you iterate through cardLoc and print only the ones given to the user:
for i in NUMCARDS:
if cardLoc[i] == str:
print cards[i]
I'm just not sure your object hierarchy is the most proper one for this case, but I'm just trying to solve your issue without heavily modifying your code.

This might not help with your specific problem, but perhaps it will give you some ideas as you go forward with Python.
import random
def main():
suits = 'S H D C'.split()
ranks = '2 3 4 5 6 7 8 9 X J Q K A'.split()
deck = [r + s for s in suits for r in ranks]
n_players = 4
hand_size = 5
random.shuffle(deck)
hands = deal(deck, n_players, hand_size)
for h in hands:
print h
def deal(deck, n_players, hand_size):
# A general-purpose dealing function.
# It takes a deck and returns a list of hands.
# Each hand is a list of cards dealt from the top of the deck.
hands = [[] for _ in range(n_players)]
i = -1
for _ in range(hand_size):
for h in hands:
i += 1
h.append(deck[i])
return hands
main()
A few general points about the example:
Rather than selecting random items from a list (and then worrying about whether subsequent selections are duplicates of prior selections) this example simply shuffles the deck and then deals from the top.
The dealing function does not rely on any global variables. Instead it receives the deck and other parameters as arguments and then returns the desired hands. To the extent that you can organize your programs to minimize (and localize) reliance on global variables you are usually better off (for many reasons).
The example uses list comprehension, which is a handy technique for creating lists in Python (where deck and hands are initialized).

A number of problems, so I suggest the real fix would be to throw this away and start over.
First thing to do: strings are for people, not computers, so don't represent cards as strings until you have to print something. Everywhere else, they should be numbers. Either a single number for each card, or a "card" object with numbers for rank and suit. Both player hands and decks are then just simple arrays (or in Python, lists).
Create a single global "deck" list from which to deal all the cards, initialize it with one of each, then deal to the player hands. There are two ways to deal random cards from a deck--your method is neither of them. Either (1) "shuffle" the deck using an algorithm called Fisher-Yates (Python's random.shuffle() does this) and pop cards off the end of the deck array as you append them to hand arrays, or (2) To deal each card, select a card randomly from the remaining cards deck, append it to the hand, and remove it from the deck. This is also easy in Python with random.choose() and remove().
A minor pythonism: don't use "str" as a variable name, it's already a built-in type.

Related

How to pass a list between two functions?

I have a list that's been modified in one function, and I want it to go to another function in order to be read and modified further.
def get_cards_player():
deck = Deck()
deck.shuffle()
player1 = []
player2 = []
you_won = False #if u won, var is true, if u lose, var is false
for i in range(5):
player1_cards = deck.get_card()
player1.append(player1_cards.get_name())
player2_cards = deck.get_card()
player2.append(player2_cards.get_name())
print('Your Hand:',', '.join(player1))
print('Opponent Hand:',', '.join(player2))
return player1, player2
def calc_winner():
I want player1 and player2 lists in get_cards_player function to go to the calc_winner function so that I can read the list and do stuff with it.
Thanks.
calc_winner should take these lists as parameters. I purposely changed the names to highlight that parameters don't have to have the same name in different functions.
get_cards_player already creates and returns the lists, so no need to change. Again, to show the different ways you can do this, I'm remembering the tuple containing the two players and using that in the call.
def calc_winner(p1list, p2list):
print(p1list, p2list)
players = get_card_player()
calc_winner(players[0], players[1])

Replacing the value 1 with the word Ace in a deck of cards

We got the assignement to make a program that shuffles a deck of cards. I'm still quite new to programming so this hasn't been the easiest assignement to do for me. We got the tip to do some research into itertools, so that's what I did. The program I have so far is as followed:
import itertools
import random
deck = list(itertools.product(range(1,14),['Heart','Spade','Club','Diamond']))
random.shuffle(deck)
print("Order of deck:")
for i in range(52):
print(deck[i][0], "of", deck[i][1])
If you run this you get:
Order of deck:
9 of Heart
10 of Diamond
2 of Spade
1 of Heart
11 of Spade
4 of Diamond
10 of Heart
...
You get a shuffled deck, as I wanted.
The only thing left to do is to change the values of 1 to "Ace", 11 to "Jack", ...
But I can't figure out how to do this, because my list "deck" consists of 52 items with each card as a tuple. For example
deck[0] = (1, 'Spade')
So far the only things I learned to replace are
deck.replace(1,"Ace")
and
for n, i in enumerate(deck):
if i == 1:
deck[n] = "Ace"
But these both don't work.
The first one doesn't work because my deck isn't a string, but a list.
And if I run the second one nothing changes, the output remains the exact same as without that piece of code.
So to sum everything up, I want to change the 1 to "Ace" in my little program, and then I could do the same for 11 to "Jack", ...
(Sorry for any bad English, hope everything is clear)
There are two ways to achieve this, depending on what you're trying to do. If you want to capture the notion that the value of Jack is 11, then you'll need to change only the displayed value (i.e. check before you print and replace 11 with Jack, etc). However, if the value isn't of particular concern, then you can fiddle with the deck at creation time:
deck = list(itertools.product(["A", *range(2,11), 'J', 'Q', 'K'],['Heart','Spade','Club','Diamond']))
Create your list with the strings that you would like to display:
values = ['Ace'] + [str(i) for i in range(2,11)] + ['Jack', 'Queen', 'King']
suits = ['Heart', 'Spade', 'Club', 'Diamond']
deck = list(itertools.product(values, suits))
You don't need to replace anything. You can use a function to return the appropriate string value
def get_face(x):
if x == 1:
return 'Ace'
elif x == 11:
return 'Jack'
else: # TODO: add others
return x
for card in deck:
print(get_face(card[0]), "of", card[1])

Python - Referencing lists in functions [duplicate]

This question already has answers here:
How do I clone a list so that it doesn't change unexpectedly after assignment?
(24 answers)
Closed 6 years ago.
Needless to say the following code does not work and the fault or problem seems to be that the function does not access or recognise the list [cards]. On the other hand if I place the list [cards] within the function, the code works perfectly. I had assumed that variables placed in the main code were global, and variables declared in a function were just local.
#!/usr/bin/python
import random
cards = ['A︎♣︎︎', '2︎♣︎︎', '3︎♣︎︎', '4︎♣︎︎', '5︎♣︎︎', '6︎♣︎︎', '7︎♣︎︎', '8︎♣︎︎', '9︎♣︎︎', '10︎♣︎︎', 'J︎♣︎︎', 'Q︎♣︎︎',
'K︎♣︎︎', 'A♠︎', '2♠︎', '3♠︎', '4♠︎', '5♠︎', '6♠︎', '7♠︎', '8♠︎', '9♠︎', '10♠︎', 'J♠︎',
'Q♠︎', 'K♠︎', 'A︎♥︎', '2︎♥︎', '3︎♥︎', '4︎♥︎', '5︎♥︎', '6︎♥︎', '7︎♥︎', '8︎♥︎', '9︎♥︎', '10︎♥︎',
'J︎♥︎', 'Q︎♥︎', 'K︎♥︎', 'A︎♦︎︎', '2︎♦︎︎', '3︎♦︎︎', '4︎♦︎︎', '5︎♦︎︎', '6︎♦︎︎', '7︎♦︎︎', '8︎♦︎︎', '9︎♦︎︎',
'10︎♦︎︎', 'J︎♦︎︎', 'Q︎♦︎︎', 'K︎♦︎︎']
# define function
def sort_cards(hand):
temp = []
for n in range(0, 7):
temp.append(cards.index(str(hand[n])))
# sort cards
temp.sort()
hand = []
# fetch card according to index and assign to hand
for c in temp:
hand.append(cards[c])
return hand
# copy cards list to working list
rem_cards = cards
# initiate players card list
player1 = []
player2 = []
player3 = []
# define variable
# 7 cards per player
deal = 7
while deal != 0:
# get a card from rem_cards and assign to player1
card = rem_cards[random.randint(0, len(rem_cards) - 1)]
player1.append(card)
# remove card from deck
rem_cards.remove(card)
card = rem_cards[random.randint(0, len(rem_cards) - 1)]
player2.append(card)
rem_cards.remove(card)
card = rem_cards[random.randint(0, len(rem_cards) - 1)]
player3.append(card)
rem_cards.remove(card)
deal -= 1
print(sort_cards(player1))
print(sort_cards(player2))
print(sort_cards(player3))
print("No of cards left in the deck is ", len(rem_cards))
Any suggestions or is my concept just wrong?
Take a look at your comments:
# copy cards list to working list
rem_cards = cards
This code does not make a copy of the list, it creates another name, under which the original list could be accessed. Whenever you modify rem_cards, cards is modified. Thus, rem_cards.remove(card) actually removes the card from cards!
If you want to copy the list, use copy.[deep]copy:
import copy
# copy cards list to working list
rem_cards = copy.deepcopy(cards) # if cards could contain other lists
rem_cards = copy.copy(cards) # create a simple shallow copy
rem_cards = cards[:] # you may create shallow copies without the copy module
This also will solve your problem without using copy.deepcopy
rem_cards = cards[:]
rem_cards = cards
does not copy the list, but just creates an alias. It should be
rem_cards = list(cards)

Random choice function

I am making a game using pygame based on the game war. I am getting an error when running the code for splitting my deck in the main loop (it is in its own file). The error says:
"/usr/local/Cellar/python/2.7.10_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/random.py", line 275, in choice
return seq[int(self.random() * len(seq))] # raises IndexError if seq is empty
IndexError: list index out of range
The deck splitting file looks like this:
import Deck
from random import *
deck = Deck.deck
playerDeck = []
AIDeck = []
def splitDeck():
player_deck_count = 26
while player_deck_count > 0:
transfer_card = (choice(deck))
playerDeck.append(transfer_card)
deck.remove(transfer_card)
player_deck_count -= 1
AIDeck = deck
shuffle(playerDeck)
shuffle(AIDeck)
print "Player deck length:" + str(len(playerDeck))
print "AI deck length:" + str(len(AIDeck))
print "Deck length:" + str(len(deck))
The Deck file looks like this:
deck = [
'2_c',
'2_d',
'2_h',
'2_s',
I get that is has to do with the sequence (list is empty) but there is obviously still 26 cards in the original deck. I have tried changing when the while player_deck_count loop stops but with no luck. Thanks for the help in advance
P.S. just comment if you want the main loop.
I believe your problem is that you are relying on the function to set the value of AIDeck. When you attempt to set
AIDeck = deck
you are moving the reference of AIDeck to the reference of deck. When the function returns, AIDeck is restored to its original definition, which was the empty list. If you are doing choice(AIDeck) anywhere in your file, it is probably the cause of your error.

Dice generator using class in Python

Update
Someone is probably going to drop the hammer on me for posting this way but there's not enough room in comments to cover this and they specifically tell you not to answer your own question with a follow-up, so here goes...
I've created the dice class like you guys were talking about.
class dice():
def __init__(self, sides, number):
self.sides = sides
self.number = number
def roll(self):
return random.randint(1, self.sides)
The number argument isn't doing anything though.
def att():
d = dice(20, 2)
base = d.roll()
if base == 1:
print 'Miss!'
elif base == 20:
crit = d.roll()
if crit < 10:
print 'Hit!'
else:
print 'Critical hit!\n'
effect = super_crit()
else:
print base
The only way I'm getting more than one die is if I do something like this:
def initiative():
d = dice(10, 1)
ini = d.roll(), d.roll()
print ini
I've also tried:
def initiative():
d = dice(10, 2)
d.sides = 10
d.number = 2
ini = d.roll()
print ini
That looks redundant to me but I get a Type Error: for having too few arguments without dice(10, 2). No matter which method I use, I get the same result - one die. Am I missing something?
Original post
I'm learning how to use classes in Python 2.7 and as an exercise I'm writing a combat module for a text-based RPG. It uses the old school dice roll method to determine outcomes and effects. A roll determines a hit or miss. If a natural 20 is rolled, another roll determines if it was a critical hit. If critical hit = TRUE, another die is rolled to determined which body part is effected. Each body part is paired with a number 1-12 in a dictionary. There are three possible output messages depending on the affected part. My problem is that the entire list of values is returned instead of a specific part. What am I doing wrong here?
Yes, I know this is super nerdy. Yes, I know the output is lame, but it's all place holder.
import sys, random
#dice generator
class dice():
def d4(self):
number = random.randint(1, 4)
return number
def d6(self):
number = random.randint(1, 6)
return number
def d10(self):
number = random.randint(0, 9)
return number
def d12(self):
number = random.randint(1, 12)
return number
def d20(self):
number = random.randint(1, 20)
return number
def d100(self):
number = random.randint(0, 99)
return number
#critical hit effect generator
class super_crit(dice):
def __init__(self):
roll = dice()
loc = roll.d12()
hit_loc = {1 : 'Head',
2 : 'Left Arm',
3 : 'Right Arm',
4 : 'Left Leg',
5 : 'Right Leg',
6 : 'Left Hand',
7 : 'Right Hand',
8 : 'Left Foot',
9 : 'Right Foot',
10 : 'Chest',
11 : 'Stomach',
12 : 'Body'}
part = hit_loc.values()
for w in part:
if loc <= 9:
print w, "has been severed!"
elif loc == 10:
print "You sink your blade into his", w, "and pierce the heart!"
elif loc == 11:
print "You slash him across the", w, "and eviscerate him!"
elif loc == 12:
print "You shred the enemy's", w, "to ribbons!"
class attackRoll(dice):
pass
#Attack function
def att():
roll = attackRoll()
base = roll.d20()
if base == 1:
print 'Miss!'
elif base == 20:
crit = roll.d20()
if crit < 10:
print 'Hit!'
else:
effect = super_crit()
else:
print base
def main():
att()
if __name__ == '__main__':
main()
You loop over all the values of the dict:
part = hit_loc.values()
for w in part:
# loop over each part, so print a damage message for all 12 bodyparts
Perhaps you meant to pick the one that is affected instead?
part = hit_loc[loc] # assign *one* body part to `part`
if loc <= 9:
print part, "has been severed!"
elif loc == 10:
print "You sink your blade into his", part, "and pierce the heart!"
elif loc == 11:
print "You slash him across the", part, "and eviscerate him!"
elif loc == 12:
print "You shred the enemy's", part, "to ribbons!"
In other words, you do not need a loop here at all.
Note that, whenever you find yourself using a sequential series of numbers as keys to a dictionary, you may as well make it a list instead:
hit_loc = [
'Head', 'Left Arm', 'Right Arm', 'Left Leg',
'Right Leg', 'Left Hand', 'Right Hand', 'Left Foot', 'Right Foot',
'Chest', 'Stomach', 'Body'
]
except now the indexes run from 0 to 11, so use loc - 1 to find the right body part:
part = hit_loc[loc - 1]
Tangent...
A simpler version of your dice class might be something like:
class Die(object):
def __init__(self, sides = 6):
self.sides = sides
def roll(self):
return randint(1, self.sides)
You now have a general purpose dice object that doesn't require you to add a new method every time you come up with a new roll mode. E.g.
d = Die(10);
d.roll() # ---> some value between 1 and 10
Adding optional start and increment attributes so that you can have a D20 that produces 5, 10, ..., 95, 100 left as an exercise to the student.
This looks like a fun program for learning Python. The other answers have mostly covered what you need, but I just wanted to point out one thing:
If you are trying to choose something in real life using dice, the way to do it is to assign numbers to the things, then roll the dice, and use the number from the dice to figure out which thing was chosen. But, in Python, you can do things more simply than that: you can tell Python "here is a bunch of stuff, I want one item chosen at random." The function for this is: random.choice()
So for the critical hit stuff, you can replace this:
hit_loc = {1 : 'Head',
2 : 'Left Arm',
3 : 'Right Arm',
4 : 'Left Leg',
5 : 'Right Leg',
6 : 'Left Hand',
7 : 'Right Hand',
8 : 'Left Foot',
9 : 'Right Foot',
10 : 'Chest',
11 : 'Stomach',
12 : 'Body'}
# two steps to randomly choose a hit location
loc = roll.d12()
part = hit_loc[loc]
with this:
locations = ('Head', 'Left Arm', 'Right Arm', 'Left Leg', 'Right Leg',
'Left Hand', 'Right Hand', 'Left Foot', 'Right Foot',
'Chest', 'Stomach', 'Body')
# one step to randomly choose a hit location
part = random.choice(locations)
In general, when you want to figure out how to use a module like random, it really pays to read the documentation. In Python, there is always some terse documentation provided with the code; from the Python prompt you can do this:
>> import random
>> help(random)
The help() command will print out terse documentation, and if you read through it you will find random.choice(), random.shuffle(), and other nifty stuff.
Have fun with Python!
P.S. This is a fun example program. It's not the best example of classes, though. You are using a class here just to get a namespace that groups related functions together, which isn't wrong, but a class is even more useful when it encapsulates some data and some functions that go together. Here is a suggestion: make a Person() class, which has a member variable called hit_points, and write a method function .attack() that attacks another Person instance. Then write a program that makes two Person() instances and has them fight each other!
I suggest it should work something like this:
black_knight = Person()
king_arthur = Person()
while True:
king_arthur.attack(black_knight)
if black_knight.hit_points <= 0:
print "Victory is mine!"
break
black_knight.attack(king_arthur)
if king_arthur.hit_points <= 0:
print "None shall pass!"
break
In this example, a Person instance bundles together the information about a person (hit points) and functions that work with a person (a person can attack another person).
You could extend the model to include critical hits; a Person instance could keep track of which critical hits it has received; a Person could have inventory, including weapons that always score a critical hit (such as "Vorpal Bunny Teeth", "Holy Hand Grenade of Antioch"), etc.
Indeed, you could keep adding more things for a Person to track, and more method functions, and if you kept at it you might turn the whole thing into a real game.
Instead of the for-loop over hit_loc.values(), just set loc=hit_loc.
There are also many other issues with the code. Here are a few:
Use function definitions instead of a class for dice
Indent the call to main()
The dictionary with number keys can be replaced with a list or tuple
Here's one possible refactoring of the first part of the code:
from functools import partial
from random import randint
d4 = partial(randint, 1, 4)
d6 = partial(randint, 1, 6)
d10 = partial(randint, 0, 10)
d12 = partial(randint, 1, 12)
d20 = partial(randint, 1, 20)
d100 = partial(randint, 0, 99)
def critical_hit_effect():
locations = ('Head', 'Left Arm', 'Right Arm', 'Left Leg', 'Right Leg',
'Left Hand', 'Right Hand', 'Left Foot', 'Right Foot',
'Chest', 'Stomach', 'Body')
roll = d12()
if roll == 10:
msg = "You sink your blade into his %s and pierce the heart!"
elif roll == 11:
msg = "You slash him across the %s and eviscerate him!"
elif roll == 12:
msg = "You shred the enemy's %s to ribbons!"
else:
msg = "%s has been severed!"
print msg % locations[roll-1]
I think it is okay to post another question when you have another question. Since you updated your question to ask about how to do multiple dice, I am putting another answer. (I doubt I will get any points for this, but I don't really care about the points.)
Some games need to know how the dice rolls came out. For example, in the super-hero game Champions, you sometimes need to count how many six-sided dice rolled ones. (I think... been a long time since I played.)
Anyway, instead of summing the dice and returning the sum, your dice class should return a list or tuple of dice roll results. I will show you both. If the tuple one is confusing, just ignore it until you know more about Python; you don't need to learn this now.
Python has a very convenient way to make a list, called a "list comprehension". If you Google for "list comprehension" you will find many pages of explanations. (Which is why I'm not getting any points for this... StackOverflow doesn't really need another list comprehensions explanation!) Basically, it works like this:
[expressionforvariableinsequenceifcondition]
Start looking in the middle, at the "for" part. This means "Set a variable to each member of a sequence. Check the condition, and if it is true, evaluate the expression and append the result to the new list."
A simple example: making a list of the squares of odd numbers from 1 to 10. This will do it:
lst_odd_squares = [x**2 for x in range(10) if (x % 2) == 1]
print(lst_odd_squares) # prints: [1, 9, 25, 49, 81]
Here is how to do this using a for loop:
lst_odd_squares = [] # start with an empty list
for x in range(10): # just like the for part
if (x % 2) == 1: # just like the condition part
lst_odd_squares.append(x**2) # append the expression part
The "condition" part is optional, and this particular example could be done this way:
lst_odd_squares = [x**2 for x in range(1, 10, 2)]
Here we start the range at 1, and step by 2, so we just directly produce the numbers we wanted.
The expression usually has the for variable in it, but it doesn't have to:
counters = [0 for x in range(10)]
This would produce a list of 10 integers, initially all set to 0. You could then use them as counters or something.
Anyway, here's the way to make a dice rolling class that returns a list:
import random
class Dice():
def __init__(self, sides):
self.sides = sides
def roll(self, number):
"""return a list of length number, with random values from 1 to self.sides"""
return [random.randint(1, self.sides) for _ in range(number)]
def roll_one(self):
"""return a single value from 1 to self.sides"""
return random.randint(1, self.sides)
d6 = Dice(6)
d10 = Dice(10)
print d6.roll(3) # roll 3d6
print d10.roll(2) # roll 2d10
print d10.roll(1) # roll 1d10 -- returns a list of length 1
print d10.roll_one() # roll a single d10 -- returns a single integer
I changed your code a bit. Instead of making a different class instance for each time you need to roll a different number of dice, I just made the .roll() method function take a number argument. Your way isn't necessarily wrong, but I think this way is a little easier.
It is usually best to use upper-case letters for class names. There is a list of rules that most Python folks like to use, called "PEP 8", and upper-case for class names is one of the rules:
http://www.python.org/dev/peps/pep-0008/#class-names
(By the way, the Learning Python book will give you all its examples in good PEP 8 style. So you will learn the PEP 8 way if you learn from that book. That's one of the ways that book teaches you all about Python.)
I used a single underscore (_) as the loop variable. That is legal, and it is sometimes done as a way to signal your intention to not use that value. We need a loop variable, but we don't actually use it, just like the counters example above.
Python has a "list comprehension" but doesn't have a "tuple comprehension". So here is one way to make a tuple: first make the list, then build a tuple out of the list.
lst = [random.randint(1, self.sides) for _ in range(number)]
return tuple(lst)
You can do it all in one go, with no explicit variable name for the list:
return tuple([random.randint(1, self.sides) for _ in range(number)])
This is kind of ugly. If we are going to build the list, why not just return it? Why feed it into tuple() and then destroy it after using it that one time?
Python offers something called a "generator expression" that is sort of like a list comprehension. The important two differences: 0) you use parentheses, not square braces; and 1) it doesn't build anything, it returns an "iterator" object that can produce a series of values. (When Python pulls out the series of values, we say that Python is "iterating" this object.) If you then pass this "iterator" to tuple(), you can directly build a tuple without first building a list and then destroying the list. The cool trick here: you need to use parentheses to call tuple(), and you can just use those parentheses as the ones you need for the generator expression.
return tuple(random.randint(1, self.sides) for _ in range(number))
Again, if the generator expression stuff seems weird or hard right now, just ignore it. The list comprehension is all you need for quite a lot of stuff.
This is how I've been dealing with multiple dice in the toolkit I'm building for the Pathfinder game I DM:
import random
def rolldice(number,sides):
Result = []
for dice in range(0, number):
Result.append(random.randint(1,sides))
return Result
class Dice():
def __init__(self, number, sides):
self.sides = sides
self.number = number
def roll(self):
return rolldice(self.number, self.sides)
# 10th level fireball
fireball = Dice(10,6)
damage = fireball.roll()
# all of the rolls (for 4d6 drop lowest, 2d20 take highest, etc)
print (damage)
# total damage
print (sum(damage))
# a d20 roll
d20 = Dice(1,20)
print (sum(d20.roll()))
output:
[2, 3, 1, 4, 2, 1, 5, 4, 5, 4]
31
13
I'll just leave here some nifty Python metaclass magic I wrote. This is for you, future code archaeologist who were determined enough to dig it up from buried deep into this thread.
from random import randint
class MetaRoll(type):
def __getattr__(cls, dice):
n,n_faces = [int(part) if part else 1 for part in
dice.lower().strip('a').strip('_').split('d')]
return sum( randint(1,n_faces) for i in range(n) )
class Roll(metaclass=MetaRoll):
""" A handy RPG styled dice roller.
The implementation has been enchanted using Python __getattr__ and metaclass
magic. Beyond that, only the random module is used.
enchanted using Python __getattr__ and metaclass magic.
As a result, any roll is exposed as a public attribute and the dice
roller that can be used like this:
``Roll.a_2D6 + 2``
Note that the ``a_`` is needed to allow names starting with numbers, and,
e.g. ``2D10`` is written as ``a_2D10`` or ``a2D10``.
Attributes:
a_3D6 (int): Rolls three six sided dice and returns the sum. This is
most boring example with a nice probability distribution.
D6 (int): If there is just one die, the attribute name does not start
with a number. Hence, the ``a_`` prefix can be omitted.
"""
pass
print(Roll.D6) # Outputs an integer between [1,6]
print(Roll.D20+2) # Note how easy it is to add modifiers
print(Roll.a_3d6-2) # Outputs a sum of random integers from a range [1,16].
ps. You know you are a wizard when describing what you've done takes longer than writing the code.

Categories

Resources