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.
Related
For my game, I'm trying to make it so whatever happens to a player that lands on a tile only happens to that player and not to everyone in the game so I don't copy the same variable 4 times for everything a player is able to do.
For example, I have something like:
badChest1 = random.choice([mob1, jail1, trapped_chest1, ("You creak open the chest to discover nothing but a pile of cobwebs")])
badChest2 = random.choice([mob2, jail2, trapped_chest2, ("You creak open the chest to discover nothing but a pile of cobwebs")])
badChest3 = random.choice([mob3, jail3, trapped_chest3, ("You creak open the chest to discover nothing but a pile of cobwebs")])
badChest4 = random.choice([mob4, jail4, trapped_chest4, ("You creak open the chest to discover nothing but a pile of cobwebs")])
in my code. I would be grateful if anyone knows a way I only need to make one variable each.
The best way to get rid of many variables is to have loops and lists. Instead of one variable for each player, you can simply add a list:
#mob1 = Mob()
#mob2 = Mob()
mobs = [Mob(), Mob()]
as such, you can also reference each as a list, and loop through them:
mobs[1]
for mob in mobs:
print("The repr of my mob:", mob)
If you happen to have had already written most of your code, and don't want to change hundreds of lines (we all hate it too :), you can use a for loop with an exec statement. Not recommended, but it works, and saves you having to rewrite a lot of your code:
for value in range(1, 5): #range returns a list of numbers. In this case 1-4
exec("""badChest%i = random.choice([mob%i, jail%i, trapped_chest%i, ("You creak open the chest to discover nothing but a pile of cobwebs")])""" % ((value,)*4))
That was a bit of a whopper of new code there, so let's break it down. First, I used multiline strings ("""). These are just a different type of quote. If I had used double quotes, the double quotes in the actual string would have cancelled them out, raising a lot of errors:
>>> "Hello world - "Joe""
SyntaxError: invalid syntax
Next, I used python's string formatting with the %s. It allows you to insert values into strings. Some of the most common indices are %s, for strings, %d or %i for integers, %f for floating-point values. In a string, you put these indices in wherever you want, then you pass them values at the end with another %:
>>> x = 5
>>> print( "Hello world, x is %i, and this is my string: %d" % (x, "strings") )
Hello world, x is 5, and this is my string: strings
Next, I just used list multiplication, to make (value,) (comma is to make it a tuple) four times longer, as we have four %is in the string:
>>> y = [1, 2, 3]
>>> y*3
[1, 2, 3, 1, 2, 3, 1, 2, 3]
and exec just runs strings as if they were code. To summarize, this loops through a range of 1-4, and for each value of the loop, it runs code this code, but with the %i signs equal to the current iteration through the range:
badChest%i = random.choice([mob%i, jail%i, trapped_chest%i, ("You creak open the chest to discover nothing but a pile of cobwebs")])
Which is exactly equal to:
badChest1 = random.choice([mob1, jail1, trapped_chest1, ("You creak open the chest to discover nothing but a pile of cobwebs")])
badChest2 = random.choice([mob2, jail2, trapped_chest2, ("You creak open the chest to discover nothing but a pile of cobwebs")])
badChest3 = random.choice([mob3, jail3, trapped_chest3, ("You creak open the chest to discover nothing but a pile of cobwebs")])
badChest4 = random.choice([mob4, jail4, trapped_chest4, ("You creak open the chest to discover nothing but a pile of cobwebs")])
I will just create an example for someone new in Python.
Let's say you have a list or a dict.
lst = [1, 2, 3]
if you do something like this.
x = lst
y = lst
you are not copying lst, both x and y are referring to the same object. A usual way to deal with it is something like:
[lst for _ in range(10)]
This will create 10 different duplicates of lst in another list.
This might not answer your question directly, but this is a common pitfall to new developers in python. Have a look about python's reference to its object types to have a better idea.
I think this is where object oriented programming starts to become useful. You should write functions that only do something to specified objects.
I didn't really stick to your example, but I wrote a small toy example for you. The important thing is that open_trapped_cheast() takes objects(players) as input:
from collections import defaultdict
import random
class Player:
def __init__(self, name, health):
self.name = name
self.status = set()
self.health = health()
self.inventory = defaultdict(int)
def add_item(self, item, number):
self.inventory[item] += number
def open_trapped_chest(player):
status = random.choice(["nothing", "poison"])
loot_table = {
"coins": 97,
"rubies": 2,
"artifact": 1,
"cool hats": 3,
}
player.status.add(status)
draws = random.randint(5, 25)
loot = random.choices(list(loot_table.keys()),
list(loot_table.values()),
k=draws)
for item in loot:
player.inventory[item] += 1
hero1 = Player("Johnny", 100)
hero2 = Player("Anita", 100)
open_trapped_chest(hero1)
open_trapped_chest(hero2)
# The result is random but could something like:
print(f"{hero1.name} is affected by {hero1.status}")
# Johnny is affected by {'poison'}
print(f"{hero1.name} has the following items {dict(hero1.inventory)}")
# Johnny has the following items {'coins': 19, 'cool hats': 1, 'artifact': 1})
print(f"{hero2.name} is affected by {hero2.status}")
# Anita is affected by {'nothing'}
print(f"{hero2.name} has the following items {dict(hero2.inventory})")
# Anita has the following items {'coins': 14, 'rubies': 2})
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])
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.
I am attempting to create a function with the following characteristics;
function name: new_scores,
parameters: (p1_score,p2_score,current_score,current_player)
p1_score: user inputed player 1 score,
p2_score: user inputed player 2 score,
current_score: the current score of set player after any actions performed
current_player: either 'PLAYER_ONE' or 'PLAYER_TWO'
the problem with my code is that it doesnt work in all cases, for example try replacing values in PLAYER_ONE, it just spits back out the p1_score, and p2_score that i inputed. Thanks for the help
def new_scores(p1_score,p2_score,current_score,current_player):
if current_player == 'PLAYER_ONE':
p1_score = current_score
return (p1_score,p2_score)
elif current_player == 'PLAYER_TWO':
p2_score = current_score
return (p1_score,p2_score)
Your code is needlessly complex, which is quite the feat for so few lines. :) There's no point in the assignment, just return the intended data directly:
def new_scores(p1_score, p2_score, current_score, current_player):
if current_player == 'PLAYER_ONE':
return (current_score, p2_score)
elif current_player == 'PLAYER_TWO':
return (p1_score, current_score)
Still, your code looks correct. Verify that the parameters are correct, using strings for semantics like this is a bit error-prone.
Make sure you are using the returned value and not expecting the parameters you passed in to be altered as a side effect.
>>> new_scores(3, 4, 5, "PLAYER_ONE")
(5, 4)
>>> new_scores(3, 4, 7, "PLAYER_TWO")
(3, 7)
Running your function as above shows it works as expected
I've wasted my entire day trying to use the minimax algorithm to make an unbeatable tictactoe AI. I missed something along the way (brain fried).
I'm not looking for code here, just a better explanation of where I went wrong.
Here is my current code (the minimax method always returns 0 for some reason):
from copy import deepcopy
class Square(object):
def __init__(self, player=None):
self.player = player
#property
def empty(self):
return self.player is None
class Board(object):
winning_combos = (
[0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8],
[0, 4, 8], [2, 4, 6],
)
def __init__(self, squares={}):
self.squares = squares
for i in range(9):
if self.squares.get(i) is None:
self.squares[i] = Square()
#property
def available_moves(self):
return [k for k, v in self.squares.iteritems() if v.empty]
#property
def complete(self):
for combo in self.winning_combos:
combo_available = True
for pos in combo:
if not pos in self.available_moves:
combo_available = False
if combo_available:
return self.winner is not None
return True
#property
def player_won(self):
return self.winner == 'X'
#property
def computer_won(self):
return self.winner == 'O'
#property
def tied(self):
return self.complete == True and self.winner is None
#property
def winner(self):
for player in ('X', 'O'):
positions = self.get_squares(player)
for combo in self.winning_combos:
win = True
for pos in combo:
if pos not in positions:
win = False
if win:
return player
return None
#property
def heuristic(self):
if self.player_won:
return -1
elif self.tied:
return 0
elif self.computer_won:
return 1
def get_squares(self, player):
return [k for k,v in self.squares.iteritems() if v.player == player]
def make_move(self, position, player):
self.squares[position] = Square(player)
def minimax(self, node, player):
if node.complete:
return node.heuristic
a = -1e10000
for move in node.available_moves:
child = deepcopy(node)
child.make_move(move, player)
a = max([a, -self.minimax(child, get_enemy(player))])
return a
def get_enemy(player):
if player == 'X':
return 'O'
return 'X'
Step 1: Build your game tree
Starting from the current board generate all possible moves your opponent can make.
Then for each of those generate all the possible moves you can make.
For Tic-Tac-Toe simply continue until no one can play. In other games you'll generally stop after a given time or depth.
This looks like a tree, draw it yourself on a piece of paper, current board at top, all opponent moves one layer below, all your possible moves in response one layer below etc.
Step 2: Score all boards at the bottom of the tree
For a simple game like Tic-Tac-Toe make the score 0 if you lose, 50 tie, 100 win.
Step 3: Propagate the score up the tree
This is where the min-max come into play. The score of a previously unscored board depends on its children and who gets to play. Assume both you and your opponent always choose the best possible move at the given state. The best move for the opponent is the move that gives you the worst score. Likewise, your best move is the move that gives you the highest score. In case of the opponent's turn, you choose the child with the minimum score (that maximizes his benefit). If it is your turn you assume you'll make the best possible move, so you choose the maximum.
Step 4: Pick your best move
Now play the move that results in the best propagated score among all your possible plays from the current position.
Try it on a piece of paper, if starting from a blank board is too much for you start from some advanced Tic-Tac-Toe position.
Using recursion:
Very often this can be simplified by using recursion. The "scoring" function is called recursively at each depth and depending on whether or not the depth is odd or even it select max or min respectively for all possible moves. When no moves are possible it evaluates the static score of the board. Recursive solutions (e.g. the example code) can be a bit trickier to grasp.
As you already know the idea of Minimax is to deep search for the best value, assuming the opponent will always play the move with the worst value (worst for us, so best for them).
The idea is, you will try to give a value to each position. The position where you lose is negative (we don't want that) and the position where you win is positive. You assume you will always try for the highest-value position, but you also assume the opponent will always aim at the lowest-value position, which has the worst outcome for us, and the best for them (they win, we lose). So you put yourself in their shoes, try to play as good as you can as them, and assume they will do that.
So if you find out you have possible two moves, one giving them the choice to win or to lose, one resulting in a draw anyway, you assume they will go for the move that will have them win if you let them do that. So it's better to go for the draw.
Now for a more "algorithmic" view.
Imagine your grid is nearly full except for two possible positions.
Consider what happens when you play the first one :
The opponent will play the other one. It's their only possible move so we don't have to consider other moves from them. Look at the result, associate a resulting value (+∞ if won, 0 if draw, -∞ if lost : for tic tac toe you can represent those as +1 0 and -1).
Now consider what happens when you play the second one :
(same thing here, opponent has only one move, look at the resulting position, value the position).
You need to choose between the two moves. It's our move, so we want the best result (this is the "max" in minimax). Choose the one with the higher result as our "best" move. That's it for the "2 moves from end" example.
Now imagine you have not 2 but 3 moves left.
The principle is the same, you want to assign a value to each of your 3 possible moves, so that you can choose the best.
So you start by considering one of the three moves.
You are now in the situation above, with only 2 possible moves, but it's the opponent's turn. Then you start considering one of the possible moves for the opponent, like we did above. Likewise, you look at each of the possible moves, and you find an outcome value for both of them. It's the opponent move, so we assume they will play the "best" move for them, the one with the worst turnout for us, so it's the one with the lesser value (this is the "min" in minimax). Ignore the other one ; assume they will play what you found was best for them anyway. This is what your move will yield, so it's the value you assign to the first of your three moves.
Now you consider each of your other possible 2 moves. You give them a value in the same manner. And from your three moves, you choose the one with the max value.
Now consider what happens with 4 moves. For each of your 4 moves, you look what happens for the 3 moves of your opponent, and for each of them you assume they will choose the one that gives you the worst possible outcome of the best of the 2 remaining moves for you.
You see where this is headed. To evaluate a move n steps from the end, you look at what may happen for each of the n possible moves, trying to give them a value so that you can pick the best. In the process, you will have to try to find the best move for the player that plays at n-1 : the opponent, and choose the step with the lesser value. In the process of evaluating the n-1 move, you have to choose between the possible n-2 moves, which will be ours, and assume we will play as well as we can at this step. Etc.
This is why this algorithm is inherently recursive. Whatever n, at step n you evaluate all possible steps at n-1. Rinse and repeat.
For tic-tac-toe todays machines are far powerful enough to compute all possible outcomes right off from the start of the game, because there are only a few hundred of them. When you look to implement it for a more complex game, you will have to stop computing at some point because it will take too long. So for a complex game, you will also have to write code that decides whether to continue looking for all possible next moves or to try to give a value to the position now and return early. It means you will also have to compute a value for position that is not final - for example for chess you would take into account how much material each opponent has on the board, the immediate possibilities of check without mate, how many tiles you control and all, which makes it not trivial.
Your complete function is not working as expected, causing games to be declared tied before anything can happen. For instance, consider this setup:
>> oWinning = {
1: Square('X'),
3: Square('O'), 4: Square('X'),
6: Square('O'), 8: Square('X'),
}
>> nb = Board(oWinning)
>> nb.complete
True
>> nb.tied
True
This should be a win for the computer on the next move. Instead, it says the game is tied.
The problem is that your logic in complete, right now, checks to see if all of the squares in a combo are free. If any of them are not, it presumes that that combo can't be won with. What it needs to do is check if any positions in that combo are occupied, and so long as all of those combos are either None or the same player, that combo should be considered still available.
e.g.
def available_combos(self, player):
return self.available_moves + self.get_squares(player)
#property
def complete(self):
for player in ('X', 'O'):
for combo in self.winning_combos:
combo_available = True
for pos in combo:
if not pos in self.available_combos(player):
combo_available = False
if combo_available:
return self.winner is not None
return True
Now that I properly tested this with the updated code I'm getting the expected result on this test case:
>>> nb.minimax(nb, 'O')
-1
>>> nb.minimax(nb, 'X')
1