I have the code to keep going through a loop until a flush is made.
now I am trying to make it where I use count to show how many hands are dealt then divide by one to get the probability.
For the code i have right now using count it returns it as 0
from collections import namedtuple
from random import shuffle
Card = namedtuple("Card", "suit, rank")
class Deck:
suits = '♦♥♠♣'
ranks = '23456789JQKA'
def __init__(self):
self.cards = [Card(suit, rank) for suit in self.suits for rank in self.ranks]
shuffle(self.cards)
def deal(self, amount):
return tuple(self.cards.pop() for _ in range(amount))
flush = False
count = 0
while not flush:
deck = Deck()
stop = False
while len(deck.cards) > 5:
hand = deck.deal(5)
# (Card(suit='♣', rank='7'), Card(suit='♠', rank='2'), Card(suit='♥', rank='4'), Card(suit='♥', rank='K'), Card(suit='♣', rank='3'))
if len(set(card.suit for card in hand)) > 1:
#print(f"No Flush: {hand}")
continue
print(f"Yay, it's a Flush: {hand}")
flush = True
break
if flush:
break
else:
count +=1
print(f'Count is {count}')
There is a little more code at the top used for init methods if you need that too let me know
Your code (and what is available in #Mason's answer) will estimate the probability of eventually getting your first flush. To estimate the probability of getting a flush in general, which I believe is what you're after, you have to run that experiment many thousands of times over. In practice this is called a Monte Carlo simulation.
Side note: When I began learning about Monte Carlos I thought they were a sort of "magical", mysteriously complex thing... mostly because their name sounds so exotic. Don't be fooled. "Monte Carlo" is just an overly fancy and arbitrary name for "simulation". They can be quite elementary.
Even so, simulations are kind of magical because you can use them to brute force a solution out of a complex system even when a mathematical model of that system is hard to come by. Say, for example, you don't have a firm understanding of combination or permutation math - which would produce the exact answer to your question "What are the odds of getting a flush?". We can run many simulations of your card game to figure out what that probability would be to a high degree of certainty. I've done that below (commented out parts of your original code that weren't needed):
from collections import namedtuple
from random import shuffle
import pandas as pd
#%% What is the likelyhood of getting flush? Mathematical derivation
""" A flush consists of five cards which are all of the same suit.
We must remember that there are four suits each with a total of 13 cards.
Thus a flush is a combination of five cards from a total of 13 of the same suit.
This is done in C(13, 5) = 1287 ways.
Since there are four different suits, there are a total of 4 x 1287 = 5148 flushes possible.
Some of these flushes have already been counted as higher ranked hands.
We must subtract the number of straight flushes and royal flushes from 5148 in order to
obtain flushes that are not of a higher rank.
There are 36 straight flushes and 4 royal flushes.
We must make sure not to double count these hands.
This means that there are 5148 – 40 = 5108 flushes that are not of a higher rank.
We can now calculate the probability of a flush as 5108/2,598,960 = 0.1965%.
This probability is approximately 1/509. So in the long run, one out of every 509 hands is a flush."""
"SOURCE: https://www.thoughtco.com/probability-of-a-flush-3126591"
mathematically_derived_flush_probability = 5108/2598960 * 100
#%% What is the likelyhood of getting flush? Monte Carlo derivation
Card = namedtuple("Card", "suit, rank")
class Deck:
suits = '♦♥♠♣'
ranks = '23456789JQKA'
def __init__(self):
self.cards = [Card(suit, rank) for suit in self.suits for rank in self.ranks]
shuffle(self.cards)
def deal(self, amount):
return tuple(self.cards.pop() for _ in range(amount))
#flush = False
hand_count = 0
flush_count = 0
flush_cutoff = 150 # Increase this number to run the simulation over more hands.
column_names = ['hand_count', 'flush_count', 'flush_probability', 'estimation_error']
hand_results = pd.DataFrame(columns=column_names)
while flush_count < flush_cutoff:
deck = Deck()
while len(deck.cards) > 5:
hand_count +=1
hand = deck.deal(5)
# (Card(suit='♣', rank='7'), Card(suit='♠', rank='2'), Card(suit='♥', rank='4'), Card(suit='♥', rank='K'), Card(suit='♣', rank='3'))
if len(set(card.suit for card in hand)) == 1:
# print(f"Yay, it's a Flush: {hand}")
flush_count +=1
# break
# else:
# print(f"No Flush: {hand}")
monte_carlo_derived_flush_probability = flush_count / hand_count * 100
estimation_error = (monte_carlo_derived_flush_probability - mathematically_derived_flush_probability) / mathematically_derived_flush_probability * 100
hand_df = pd.DataFrame([[hand_count,flush_count,monte_carlo_derived_flush_probability, estimation_error]], columns=column_names)
hand_results = hand_results.append(hand_df)
#%% Analyze results
# Show how each consecutive hand helps us estimate the flush probability
hand_results.plot.line('hand_count','flush_probability').axhline(y=mathematically_derived_flush_probability,color='r')
# As the number of hands (experiments) increases, our estimation of the actual probability gets better.
# Below the error gets closer to 0 percent as the number of hands increases.
hand_results.plot.line('hand_count','estimation_error').axhline(y=0,color='black')
#%% Memory usage
print("Memory used to store all %s runs: %s megabytes" % (len(hand_results),round(hand_results.memory_usage(index=True,deep=True).sum()/1000000, 1)))
In this particular case, thanks to math, we could have just confidently derived the probability of getting a flush as 0.1965%. To prove that our simulation arrived at the correct answer we can compare its output after 80,000 hands:
As you can see, our simulated flush_probability (in blue) approaches the mathematically derived probability (in black).
Similarly, below is a plot of the estimation_error between the simulated probability and the mathematically derived value. As you can see, the estimation error was more than 100% off in the early runs of the simulation but gradually rose to within 5% of the error.
If you were to run the simulation for, say, twice the number of hands, then we would see that the blue and red lines eventually overlap with the black horizontal line in both charts - signifying that the simulated answer becomes equivalent to the mathematically derived answer.
To simulate or not to simulate?
Finally, you might wonder,
"If I can generate a precise answer to a problem by simulating it, then why bother with all the complicated math in the first place?"
The answer is, as with just about any decision in life, "trade offs".
In our example, we could run the simulation over enough hands to get a precise answer with a high degree of confidence. However, if one is running a simulation because they don't know the answer (which is often the case), then one needs to answer another question,
"How long do I run the simulation to be confident I have the right answer?"
The answer to that seems simple:
"Run it for a long time."
Eventually your estimated outputs could converge to a single value such that outputs from additional simulations don't drastically change from prior runs. The problem here is that in some cases, depending on the complexity of the system you're simulating, seemingly convergent output may be a temporary phenomena. That is, if you ran a hundred thousand more simulations, you might begin to see your outputs diverge from what you thought was your stable answer. In a different scenario, despite having run tens of millions of simulations, it could happen that an output still hasn't converged. Do you have the time to program and run the simulation? Or would a mathematical approximation get you there sooner?
There is yet another concern:
*"What is the cost?"
Consumer computers are relatively cheap today but 30 years ago they cost $4,000 to $9,000 in 2019 dollars. In comparison, a TI89 only cost $215 (again, in 2019 dollars). So if you were asking this question back in 1990 and you were good with probability math, you could have saved $3,800 by using a TI89. Cost is just as important today: simulating self-driving cars and protein folding can burn many millions of dollars.
Finally, mission critical applications may require both a simulation and a mathematical model to cross check the results of both approaches. A tidy example of this is when Matt Parker of StandUpMaths calculated the odds of landing on any property in the game of Monopoly by simulation and confirmed those results with Hannah Fry's mathematical model of the same game.
I think this should work for you. It depends on how your Deck() is defined though, I guess. I tried to leave your code in a similar state to how you had written it, but had to make some changes so you wouldn't get errors. I also didn't actually run it, since I don't have Deck defined.
flush = False
count = 0
while not flush:
deck = Deck()
stop = False
while len(deck.cards) > 5:
hand = deck.deal(5)
# (Card(suit='♣', rank='7'), Card(suit='♠', rank='2'), Card(suit='♥', rank='4'), Card(suit='♥', rank='K'), Card(suit='♣', rank='3'))
if len(set(card.suit for card in hand)) > 1:
print(f"No Flush: {hand}")
else:
print(f"Yay, it's a Flush: {hand}")
flush = True
break
count +=1
print(f'Count is {count}')
But it will not give you the probability of getting a flush, and you'll honestly probably run out of cards in deck before you get a flush in almost every run...
I would consider changing the code to this so, to take out some redundancies.
flush = False
count = 0
while not flush:
deck = Deck()
while len(deck.cards) > 5:
hand = deck.deal(5)
# (Card(suit='♣', rank='7'), Card(suit='♠', rank='2'), Card(suit='♥', rank='4'), Card(suit='♥', rank='K'), Card(suit='♣', rank='3'))
if len(set(card.suit for card in hand)) == 1:
print(f"Yay, it's a Flush: {hand}")
flush = True
break
else:
print(f"No Flush: {hand}")
count +=1
print(f'Count is {count}')
Related
SOLVED: I read through my code, it was a 'bug'. When I copied the dice roll method from the 'player character', since it uses the same mechanics for the enemies, I set the damage to 0 if it rolls with one die on accident.
Beginner here. (Python crash course halfway of chapter 9)
I am trying to build a simple turn based text game to practice (classes,if statement, modifying dictionaries/lists etc).
I will copy two snippets from my code, so you can understand my problem better.
(I'm really sorry that I can't give a short description, my best try was the title, but that still doesn't make it good enough. If you want an abridged tldr, go to the bottom with the bold texts.)
First, I have two characters, that you can choose from as an if-elif-else statement.
I used the same "player_xy" (xy being like health, damage etc) for the two characters, but assigning different values to them based on the player's choice. (My reasoning being is so I only have to reference the same variable in the code later in the battle system, making my job easier.)
(The variables fighter_max_hp.. etc are defined earlier, but it doesn't matter (tried moving it to before/inside the if statements.)
while select_repeat == True:
print("Type 'f' for fighter , 'm' for mage, or 'q' to quit!")
character = input("TYPE: ")
#player chooses fighter
if character == 'f':
player_max_hp = fighter_max_hp
player_max_mana = fighter_max_mana
#this goes on for a while, setting up all the stats
#player chooses mage
elif character == 'm':
player_max_hp = mage_max_hp
player_max_mana = mage_max_mana
#this goes on for a while, setting up all the stats
#player chooses to quit
elif character == 'q':
select_repeat = False
#invalid input
else:
print("\nPlease choose a valid option!")
Later in the code, I have a part where a randomizer sets up enemies to fight.
I used the same "enemy_xy" (xy being like health, damage etc) for the enemies. (My reasoning was the same here as for the characters.)
(Same, as with the player variables (tried moving it to before/inside the if statements.)
while enemy_select == True:
#game chooses an enemy to fight!
min = 1
max = 3
enemy_chooser = int(random.randint(min, max))
if enemy_chooser == 1:
#choose werewolf
enemy_hp = werewolf_hp
enemy_dice = werewolf_dice
#this goes on for a while, setting up all the stats
if enemy_chooser == 2:
#choose lesser mimic
enemy_hp = int(player_max_hp / 2)
enemy_dice = player_dice
elif enemy_chooser == 3:
#choose zombie
enemy_hp = zombie_hp
enemy_dice = zombie_dice
#this goes on for a while, setting up all the stats
Keep in mind, all of these enemies use the same "enemy_hp", "enemy_dice" etc. variables, within the same battle system, just assigned as "enemy_hp = werewolf_hp" or "enemy_hp = "zombie_hp".
The fight happens, and:
If your enemy is the werewolf:
you deal damage to it
you receive damage from it
you can kill it
you can get killed by it
If your enemy is the lesser mimic:
you deal damage to it
you can ONLY receive damage from it if you are a fighter (mage's hp doesn't decrease)
you can kill it
you can ONLY get killed by it if you are a fighter (obviously, since it doesn't deal damage to mage hp)
If your enemy is the zombie:
you deal damage to it
you CAN NOT receive damage from it (not the fighter, or the mage)
you can kill it
you can not get killed by it (obviously, since no damage)
Otherwise, it prints out the different variable values as assigned (different stats for each monster) as expected, and it uses correct calculations to deal damage.. it just can't in the two cases mentioned above.
Now comes the main part of my question...
If I change the variables like this:
elif enemy_chooser == 2:
#choose zombie
enemy_hp = werewolf_hp ##CHANGE
enemy_dice = werewolf_dice ##CHANGE
#this goes on for a while, setting up all the stats
Then the zombie can finally deal damage to the player (with the werewolf's stats).
It's as if because the lines
enemy_hp = werewolf_hp
enemy_dice = werewolf_dice
#etc
are written earlier than:
enemy_hp = zombie_hp
enemy_dice = zombie_dice
#etc
it somehow effects the variable (regardless or not if the "if" statement is true).
because werewolf_xy was defined earlier than zombie_xy
#enemy werewolf defined first in the code
werewolf_hp = 20
werewolf_dice = 2
#etc
#enemy zombie defined right after
zombie_hp = 35
zombie_dice = 1
#etc
Same happens with the fighter and mage selection.
Somehow the player_hp = xy_hp only works if xy = fighter, because the fighters variables are defined earlier in the code, and thus making the "lesser mimic" deal damage only to the fighter.
My question is "simply".. why?
I tried everything in my power, to no avail.
As you have seen, I could identify what causes the problem (and thus I >could< potentionally work around it), but I still don't know why Python does what it does, and that bothers me.
Any help or input from more experienced users would be greatly appreciated.
Thank you in advance!
Tankerka
You have a bug.
There's not enough details in this (long!) narrative to identify the bug.
Here's how you fix it:
breakpoint()
Put that near the top of your code,
and use n next, plus p print var,
to see what your code is doing.
It is quicker and more flexible than print( ... ).
Read up on that pair of commands here:
https://docs.python.org/3/library/pdb.html
Separate item: refactor your code as you go along.
You're starting to have enough if / elif logic
that it won't all fit in a single screenful
with no scrolling.
That suggests that it's a good time to use def
to break out a helper function.
You might def get_enemy_hp( ... ):, for example,
and also define get_enemy_dice().
Other things you might choose to study:
a class can be a good way to organize the variables you're defining -- embrace the self syntax!
a dict could map from enemy type to hp, or to dice
The nice thing about helper functions is they're
the perfect target for unit tests.
It takes a minute to write a test, but it winds up saving you time.
https://docs.python.org/3/library/unittest.html
When you identify and fix the problem, let us know!
https://stackoverflow.com/help/self-answer
A program I'm developing involves a function that adjusts the BPM (beats per minute) of one song to the BPM of another target song.
The issue is, sometimes adjusting the song to the exact BPM of the target song isn't a good idea, as the first song may be sped up or slowed down too much, so it sounds too fast or too slow. In these situations, it makes sense to adjust the BPM of the song to another closer BPM that is still compatible with the target song's BPM.
Here's an example:
BPM of song 1: 150BPM
BPM of target song: 84BPM
In this case, slowing down a 150BPM song to 84BPM isn't very practical - the audio will become too distorted and sound weird. It makes more sense to adjust song 1 to 168BPM - double the BPM of the target song, as this requires significantly less modification to song 1, and will sound more natural.
Looking at the same example switched around:
BPM of song 1: 84BPM
BPM of target song: 150BPM
In this case, it makes sense to slow song 1 down to 75BPM, half of 150, rather than speeding the song all the way up to 150.
In this program, the BPMs of song 1 and the target song differ based on what songs it is given (obviously). What is want this function to do is always go to the closest number compatible with the target BPM - what's the math I need? I'm using Python. I've had a huge headache today making the rest of the whole program.
I think you have all you need. You can figure it out as a human, so put that into code. The problem often is that you don't exactly know what you're doing, because you're too used to it.
Let's get started with a function definition:
def targetbpm(bpmPlaying: int, bpmTarget: int) -> int:
pass
And the given expected outcomes of the two examples you provided
assert(targetbpm(150, 84) == 168)
assert(targetbpm(84, 150) == 75)
Let's tackle the first assertion. What options are there? As you said, you could go to 84, but it would be better to double that.
def targetbpm(bpmPlaying: int, bpmTarget: int) -> int:
choices = [bpmTarget, bpmTarget*2]
Which one to choose? The one with the minimum difference to what is currently playing. So let's get these differences and the minimum of these:
bpmdiff = [abs(bpmPlaying - choice) for choice in choices]
minimum = min(bpmdiff)
Now let's go back to the original choices and select the corresponding one
index = bpmdiff.index(minimum)
return choices[index]
Now you'll find that the second assertion is not passing yet. The reason is that you have another choice here: half the BPM. So let's add that:
choices = [bpmTarget, bpmTarget*2, bpmTarget // 2]
Problem solved.
This would be one quick way to do it.
def get_BPM_multiple(base, target):
distance = abs(target - base)
multiple = 1
for i in range(2, 8): # 8 or whatever
if base < target:
newdist = abs(target - base * i)
else:
newdist = abs(target - base / i)
if newdist < distance:
distance = newdist
multiple = i
else:
break
if base < target:
print(f"Multiply base {base}BPM by {multiple}: {base * multiple}BPM, "
f"close to target {target}BPM.")
return base * multiple
else:
print(f"Divide base {base}BPM by {multiple}: {base / multiple}BPM, "
f"close to target {target}BPM.")
return base / multiple
get_BPM_multiple(base=300, target=84)
# Divide base 300BPM by 4: 75.0BPM, close to target 84BPM.
get_BPM_multiple(base=84, target=150)
# Multiply base 84BPM by 2: 168BPM, close to target 150BPM.
I am trying the famous 37% rule from the book- "Algorithms to live by" by Brian Christian.
The 37% Rule basically says that when you need to screen a range of options in a limited amount of time - be they candidates for a job, new apartments, or potential romantic partners - the best time to make a decision is when you've looked at 37% of those options.
At that point in a selection process, you'll have gathered enough information to make an informed decision, but you won't have wasted too much time looking at more options than necessary. At the 37% mark, you're in a good place to pick the best of the bunch.
A common thought experiment to demonstrate this theory - developed by non-PC math guys in the 1960s - is called "The Secretary Problem."
The program is running but I wanted to start to consider selecting the candidates after 37% of the candidates. Since I am using dictionary, I do not get to access the elements after a specified number of candidates. How can I make this possible?
import matplotlib.pyplot as plt
# for visualising scores
def initiate(candidates):
print("Total candidates are : ",len(candidates))
lookup_num=int(len(candidates) *0.37)
#finds 37% of the candidates
average=lookup(candidates)
# returns average of lookup phase
chosen=select_cad(candidates,lookup_num,average)
# selects a candidate based on lookUp average
print("The chosen candidate is : {} ".format(chosen))
def lookup(candidates):
average_score=0
for cands,score in candidates.items():
average_score+=score
average_score=int(average_score/len(candidates))
print("The average score in lookup is : ",average_score)
#return the average score to average local variable
return average_score
def select_cad(candidates,lookup_num,average):
for cands,score in candidates.items():
if(score>average):
return cands
else:
continue
print("Something went wrong!")
quit
candidates={"Husain":85, "Chirag":94 ,"Asim":70,"Ankit":65 ,"Saiteja":65 ,"Absar":75 ,"Premraj":70 ,"Sagar":75 ,"Himani":75 ,"Parth":76 ,"Sumedha":70 ,"Revati":65 ,"Sageer":65 ,"Noorjahan":60 ,"Muzammil":65 ,"Shifa":56 , "Dipti":65 , "Dheeraj":70 }
initiate(candidates)
plt.bar(range(len(candidates)), list(candidates.values()), align='center', color='green')
plt.xticks(range(len(candidates)), list(candidates.keys()))
plt.show()
How can I make it more flexible to update the average score even in selection phase?
Just read about this "Rule of 37%" so I hope I understood it correctly. I would implement something like that:
import random
def rule_of_37(candidates):
# first I date random 37% of the candidates
who_i_date = random.sample(list(candidates), int(len(candidates)*.37))
print("I meet those 37% of the candidates", who_i_date)
# then I calculate their average score
average = sum(candidates[name] for name in who_i_date) / len(who_i_date)
print("The average score was", average)
# then I settle with the next person that has a score higher than the average (obviously I cannot re-date candidates)
# hopefully there is still someone with an higher score than average...
try:
who_i_marry = next(name
for name, score in candidates.items()
if name not in who_i_date
and score > average)
print("I end up with", who_i_marry, "who has a score of", candidates[who_i_marry])
except StopIteration:
print("I end up all alone, there was nobody left with an higher score than", average, "...")
candidates={"Husain":85, "Chirag":94 ,"Asim":70,"Ankit":65 ,"Saiteja":65 ,"Absar":75 ,"Premraj":70 ,"Sagar":75 ,"Himani":75 ,"Parth":76 ,"Sumedha":70 ,"Revati":65 ,"Sageer":65 ,"Noorjahan":60 ,"Muzammil":65 ,"Shifa":56 , "Dipti":65 , "Dheeraj":70 }
rule_of_37(candidates)
Example execution (yours may vary since the first 37% candidates are picked at random):
I meet those 37% of the candidates ['Dipti', 'Chirag', 'Revati', 'Sumedha', 'Dhe
eraj', 'Muzammil']
The average score was 71.5
I end up with Husain who has a score of 85
If you want to select the first candidates yourself instead of relying on random, you can simply replace who_i_date by your pre-selected list:
who_i_date = ['Husain', 'Chirag', 'Asim', 'Ankit', 'Saiteja', 'Absar']
But then the other 63% will be arbitrarily ordered so you may not always select the same one (unless you use Python 3.6+ which keeps dicts in order by default). If you want to date the remaining 63% in order, you have to iterate over a list of the candidates names rather than on the dict that maps names to scores.
I leave that up to you.
You can use lookup_num along with numpy to simulate what candidates are "seen" within your function and then calculate the average score. This function will randomly select lookup_num number of candidates from your dictionary (without replacement). Using that subset the average_score is calculated. The function will return the average score along with the dictionary of "seen" candidates to determine who was the best candidate from the 37% subset.
def lookup(candidates,lookup_num):
# Randomly select lookup_num candidates without replacement
seen_names = np.random.choice(candidates.keys(), size=lookup_num, replace=False)
# Create a dictionary with the scores from the seen candidates.
seen = {k: v for (k, v) in candidates.items() if k in seen_names}
# Calculate the average score for the candidates who were seen
average_score = sum([v for (k, v) in seen.items()]) / float(lookup_num)
return seen, average_score
Your code calling lookup would become:
seen, average_score=lookup(candidates,lookup_num)
With the average_score and the list of candidates who were seen you can compare that to the rest of the candidates and apply your decision criteria for choosing the best candidate.
I've updated my previous code with a few variables and used lookup_num for iterating. Still using the unordered dictionary and it works like a charm. Check it out.
import matplotlib.pyplot as plt
# for visualising scores
def initiate(candidates):
print("Total candidates are : ",len(candidates))
lookup_num=int(len(candidates) *0.37)
#finds 37% of the candidates
average, lookup_candidates=lookup(candidates,lookup_num)
# returns average of lookup phase
chosen=select_cad(candidates,lookup_num,average,lookup_candidates)
# selects a candidate based on lookUp average
print("The chosen candidate is : {} ".format(chosen))
def lookup(candidates,lookup_num):
average_score=0
count=0
lookup_candidates=[]
for cands,score in candidates.items():
if(not count>=lookup_num):
lookup_candidates.append(cands)
average_score+=score
count+=1
average_score=int(average_score/count)
print("Look Up candidates are : ",lookup_candidates)
print("The average score in lookup is : ",average_score)
#return the average score to average local variable
return average_score,lookup_candidates
def select_cad(candidates,lookup_num,average,lookup_candidates):
for cands,score in candidates.items():
if(score>average and cands not in lookup_candidates):
#because 37% rule does not allows us to go back to select a candidate from the lookup phase
return cands
#return selected candidate to chosen variable
else:
continue
print("Something went wrong!")
quit
candidates={"Husain":85, "Chirag":94 ,"Asim":70,"Ankit":65 ,"Saiteja":65 ,"Absar":75 ,"Premraj":70 ,"Sagar":75 ,"Himani":75 ,"Parth":76 ,"Sumedha":70 ,"Revati":65 ,"Sageer":65 ,"Noorjahan":60 ,"Muzammil":65 ,"Shifa":56 , "Dipti":65 , "Dheeraj":70 }
initiate(candidates)
plt.bar(range(len(candidates)), list(candidates.values()), align='center', color='green')
plt.xticks(range(len(candidates)), list(candidates.keys()))
plt.xlabel("Candidates")
plt.ylabel("Score")
plt.title("37% Algorithm")
plt.show()
I'm trying to make a blackjack game, and I want to have the option to deal additional cards, but what I've run up against is I'm trying to make a function that will deal additional cards, but I'm not sure the best way to do that. I have an idea on how to do it, but I'm not clear on how I would accomplish it.
Idea:
Have a function that will deal addition cards, and will +1 a variable counting how many cards the player already has, starting at 2, the first time the function is run, will give the player card #3. The function would then randomly give a card, and then return the card, (perhaps playerCardThree, playerCardFour) with a limit of 5 cards allowed at a time. Here is an example of what I'd like it to do, but I'm not sure if it'd work.
def deal():
playerCardTotal + 1
if playerCardTotal >= 5:
print("Error detected, returning...")
play()
newPlayerCard = randint(1,10)
print("You are given card ",playerCardTotal,"it is a",newPlayerCard)
newPlayerCard = playerCard(4)
If someone could help me do something similar, or perhaps lead me to a way that would accomplish the goal cleaner, as I'm sure what I've thought up isn't the most efficient way.
How about using a counter within a class, which will automatically increase each time you instantiate it. Then each card will be an instance. You can raise an error when the counter exceeds 5, as you described in your question. A simplified starting point for your game might be something like this (you should elaborate it as you need):
import random
class MyGame():
counter = 0
def __init__(self):
MyGame.counter += 1
def deal_card(self):
self.card = random.randint(1,10)
if self.get_count() > 5:
raise RuntimeError('too many cards have been played')
print("You are given card #", self.get_count())
def get_count(self):
return MyGame.counter
a = MyGame()
a.deal_card()
b = MyGame()
b.deal_card()
I was trying to solve the Infinite Monkey Theorem which is part of a programming assignment that I came across online.
The problem statement is:
The theorem states that a monkey hitting keys at random on a typewriter keyboard for an infinite amount of time will almost surely type a given text, such as the complete works of William Shakespeare. Well, suppose we replace a monkey with a Python function. How long do you think it would take for a Python function to generate just one sentence of Shakespeare? The sentence we’ll shoot for is: “methinks it is like a weasel”
I am trying to see a) whether it will be possible to generate the string b) After how many iterations was the string generated
I have set recursion limit as 10000 looking at a previous SO question, but I am still getting the run time error for Maximum recursion depth reached.
I am still finding my way around python. I hope to see suggestions on how I could do it in a better way without coming across recursion depth issue.
Here is my code so far:
import random
import sys
alphabet=['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',' ']
quote="methinks it is like a weasel"
msg='cont'
count=0
sys.setrecursionlimit(10000)
def generate(msg):
sentence=''
while len(sentence)!=27:
#random.choice() prints a random element from list 'alphabet'
sentence=sentence+random.choice(alphabet)
if msg=='cont':
verify(sentence)
def verify(msg2):
global count
if msg2.find(quote)==-1:
count+=1
generate('cont')
else:
print 'sentence is ',msg2 ,'count is',count
if __name__ == '__main__':
generate(msg)
This is a case where it's better to think before doing. If we ignore capitalization and punctuation, your string is comprised of 28 characters, each of which can in principle be any of the 26 letters of the alphabet or a space. The number of combinations is 2728, which happens to be 11972515182562019788602740026717047105681. If you could enumerate a billion guesses per second, 2728 / 1E9 (tries/sec) / 3600 (sec/hr) / 24 (hrs/day) / 365.25 (days/yr) / 14E9 (yrs/current age of universe)
=> 27099008032844.297. The good news is that you might stumble on the answer at any point, so the expected amount of time is only half of 27 trillion times the current age of the universe.
Blowing out the stack is the least of your problems.
The reason it's called the infinite monkey theorem is that you can divide by the number of monkeys who can process this in parallel, and if that's infinity the solution time becomes the per monkey amount of time to generate a guess, 1 billionth of a second.
It would be better not to call verify() from generate() (and vice-versa) in the likely event that the monkeys have not written Shakespeare.
Having two functions which repeatedly call one another without returning if what causes the recursion depth to be exceeded.
Instead of using recursion, you could simply check whether you've produced your sentence with an iterative approach. For example have a loop which takes a random sentence, then checks whether it matches your required sentence, and if so, outputs the number of tries it took (and if not loops back to the start).
done = False
count = 1
while not done:
msg = generate()
if verify(msg):
print 'success, count = ', count
done = True
count += 1
Maybe something like the following. It runs on CPython 2.[67], CPython 3.[01234], pypy 2.4.0, pypy3 2.3.1 and jython 2.7b3. It should take a very long time to run with --production, even on pypy or pypy3.
#!/usr/local/cpython-3.4/bin/python
'''Infinite monkeys randomly typing Shakespeare (or one monkey randomly typing Shakespeare very fast'''
# pylint: disable=superfluous-parens
# superfluous-parens: Parentheses are good for clarity and portability
import sys
import itertools
def generate(alphabet, desired_string, divisor):
'''Generate matches'''
desired_tuple = tuple(desired_string)
num_possibilities = len(alphabet) ** len(desired_string)
for candidateno, candidate_tuple in enumerate(itertools.product(alphabet, repeat=len(desired_string))):
if candidateno % divisor == 0:
sys.stderr.write('checking candidateno {0} ({1}%)\n'.format(candidateno, candidateno * 100.0 / num_possibilities))
if candidate_tuple == desired_tuple:
match = ''.join(candidate_tuple)
yield match
def usage(retval):
'''Output a usage message'''
sys.stderr.write('Usage: {0} --production\n'.format(sys.argv[0]))
sys.exit(retval)
def print_them(alphabet, quote, divisor):
'''Print the matches'''
for matchno, match in enumerate(generate(alphabet, quote, divisor)):
print('{0} {1}'.format(matchno, match))
def main():
'''Main function'''
production = False
while sys.argv[1:]:
if sys.argv[1] == '--production':
production = True
elif sys.argv[1] in ['--help', '-h']:
usage(0)
else:
sys.stderr.write('{0}: Unrecognized option: {1}\n'.format(sys.argv[0], sys.argv[1]))
usage(1)
if production:
print_them(alphabet='abcdefghijklmnopqrstuvwxyz ', quote='methinks it is like a weasel', divisor=10000)
else:
print_them(alphabet='abcdef', quote='cab', divisor=10)
main()