Slot Machine in Python 3
Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
4
down vote
favorite
I have made this program which simulates a slot machine.
import random, time, os
def input_verify(message):
while True:
user_input = input(message).lower()
if user_input in ("yes", "no"):
return user_input
print("Invalid Input")
def win(number_of_matches, fruit):
global balance
print("Well done! You got", number_of_matches, "of the same fruit!")
time.sleep(0.5)
reward = fruits[fruit][1] * number_of_matches
print("You won ã!".format(reward))
balance += reward
fruits = "apple": [10000, 1], "banana": [7000, 1.5], "lemon": [6000, 1.75], "orange": [5000, 2], "kiwi": [4000, 4],
"peach": [3000, 6], "avocado": [2000, 10], "grapes": [1000, 20], "mango": [500, 100], "melon": [1, 10000]
weighted_fruits = [fruit for fruit in fruits for i in range(fruits[fruit][0])]
print("Welcome to my slot machine! Each turn costs ã1.")
balance = 10
if os.path.exists("One Armed Bandit.txt"):
with open("One Armed Bandit.txt", "r") as f:
contents = f.readlines()
continue_saved_game = input_verify("Do you want to continue your saved game?")
if continue_saved_game == "yes":
balance = float(contents[1].strip())
time.sleep(0.5)
print("nYour current balance is ã.n".format(balance))
time.sleep(1)
while True:
balance -= 1
reward = 0
a, b, c = [random.choice(weighted_fruits) for i in range(3)]
print("Spinning...")
for fruit in (a, b, c):
time.sleep(0.5)
print(fruit)
time.sleep(0.5)
if a == b == c:
win(3, a)
elif a == b or a == c or b == c:
if b == c:
win(2, c)
else:
win(2, a)
else:
print("Unlucky. None of your fruits matched each other.")
time.sleep(0.5)
print("nYour current balance is ã.n".format(balance))
time.sleep(0.5)
replay = input_verify("Do you want to play again?")
if replay == "no":
save = input_verify("Do you want to save your game?")
if save == "yes":
name = input("What is you name?")
with open("One Armed Bandit.txt", "w") as f:
f.write("'s Balance:n".format(name, balance))
break
I don't like how there are so many time.sleep()
calls scattered throughout my code. Is there a way to achieve the same thing but in a cleaner way. Moreover, could you suggest some ways to make it more efficient?
python python-3.x
add a comment |Â
up vote
4
down vote
favorite
I have made this program which simulates a slot machine.
import random, time, os
def input_verify(message):
while True:
user_input = input(message).lower()
if user_input in ("yes", "no"):
return user_input
print("Invalid Input")
def win(number_of_matches, fruit):
global balance
print("Well done! You got", number_of_matches, "of the same fruit!")
time.sleep(0.5)
reward = fruits[fruit][1] * number_of_matches
print("You won ã!".format(reward))
balance += reward
fruits = "apple": [10000, 1], "banana": [7000, 1.5], "lemon": [6000, 1.75], "orange": [5000, 2], "kiwi": [4000, 4],
"peach": [3000, 6], "avocado": [2000, 10], "grapes": [1000, 20], "mango": [500, 100], "melon": [1, 10000]
weighted_fruits = [fruit for fruit in fruits for i in range(fruits[fruit][0])]
print("Welcome to my slot machine! Each turn costs ã1.")
balance = 10
if os.path.exists("One Armed Bandit.txt"):
with open("One Armed Bandit.txt", "r") as f:
contents = f.readlines()
continue_saved_game = input_verify("Do you want to continue your saved game?")
if continue_saved_game == "yes":
balance = float(contents[1].strip())
time.sleep(0.5)
print("nYour current balance is ã.n".format(balance))
time.sleep(1)
while True:
balance -= 1
reward = 0
a, b, c = [random.choice(weighted_fruits) for i in range(3)]
print("Spinning...")
for fruit in (a, b, c):
time.sleep(0.5)
print(fruit)
time.sleep(0.5)
if a == b == c:
win(3, a)
elif a == b or a == c or b == c:
if b == c:
win(2, c)
else:
win(2, a)
else:
print("Unlucky. None of your fruits matched each other.")
time.sleep(0.5)
print("nYour current balance is ã.n".format(balance))
time.sleep(0.5)
replay = input_verify("Do you want to play again?")
if replay == "no":
save = input_verify("Do you want to save your game?")
if save == "yes":
name = input("What is you name?")
with open("One Armed Bandit.txt", "w") as f:
f.write("'s Balance:n".format(name, balance))
break
I don't like how there are so many time.sleep()
calls scattered throughout my code. Is there a way to achieve the same thing but in a cleaner way. Moreover, could you suggest some ways to make it more efficient?
python python-3.x
1
Isn't using asking "could you suggest some ways to make it more efficient?" in conflict with usingtime.sleep()
.
â Peilonrayz
Jan 27 at 15:56
Yes. That is why I don't like the time.sleep() but the delays provide a better experience for the user.@Peilonrayz
â Milan Tom
Jan 27 at 19:03
add a comment |Â
up vote
4
down vote
favorite
up vote
4
down vote
favorite
I have made this program which simulates a slot machine.
import random, time, os
def input_verify(message):
while True:
user_input = input(message).lower()
if user_input in ("yes", "no"):
return user_input
print("Invalid Input")
def win(number_of_matches, fruit):
global balance
print("Well done! You got", number_of_matches, "of the same fruit!")
time.sleep(0.5)
reward = fruits[fruit][1] * number_of_matches
print("You won ã!".format(reward))
balance += reward
fruits = "apple": [10000, 1], "banana": [7000, 1.5], "lemon": [6000, 1.75], "orange": [5000, 2], "kiwi": [4000, 4],
"peach": [3000, 6], "avocado": [2000, 10], "grapes": [1000, 20], "mango": [500, 100], "melon": [1, 10000]
weighted_fruits = [fruit for fruit in fruits for i in range(fruits[fruit][0])]
print("Welcome to my slot machine! Each turn costs ã1.")
balance = 10
if os.path.exists("One Armed Bandit.txt"):
with open("One Armed Bandit.txt", "r") as f:
contents = f.readlines()
continue_saved_game = input_verify("Do you want to continue your saved game?")
if continue_saved_game == "yes":
balance = float(contents[1].strip())
time.sleep(0.5)
print("nYour current balance is ã.n".format(balance))
time.sleep(1)
while True:
balance -= 1
reward = 0
a, b, c = [random.choice(weighted_fruits) for i in range(3)]
print("Spinning...")
for fruit in (a, b, c):
time.sleep(0.5)
print(fruit)
time.sleep(0.5)
if a == b == c:
win(3, a)
elif a == b or a == c or b == c:
if b == c:
win(2, c)
else:
win(2, a)
else:
print("Unlucky. None of your fruits matched each other.")
time.sleep(0.5)
print("nYour current balance is ã.n".format(balance))
time.sleep(0.5)
replay = input_verify("Do you want to play again?")
if replay == "no":
save = input_verify("Do you want to save your game?")
if save == "yes":
name = input("What is you name?")
with open("One Armed Bandit.txt", "w") as f:
f.write("'s Balance:n".format(name, balance))
break
I don't like how there are so many time.sleep()
calls scattered throughout my code. Is there a way to achieve the same thing but in a cleaner way. Moreover, could you suggest some ways to make it more efficient?
python python-3.x
I have made this program which simulates a slot machine.
import random, time, os
def input_verify(message):
while True:
user_input = input(message).lower()
if user_input in ("yes", "no"):
return user_input
print("Invalid Input")
def win(number_of_matches, fruit):
global balance
print("Well done! You got", number_of_matches, "of the same fruit!")
time.sleep(0.5)
reward = fruits[fruit][1] * number_of_matches
print("You won ã!".format(reward))
balance += reward
fruits = "apple": [10000, 1], "banana": [7000, 1.5], "lemon": [6000, 1.75], "orange": [5000, 2], "kiwi": [4000, 4],
"peach": [3000, 6], "avocado": [2000, 10], "grapes": [1000, 20], "mango": [500, 100], "melon": [1, 10000]
weighted_fruits = [fruit for fruit in fruits for i in range(fruits[fruit][0])]
print("Welcome to my slot machine! Each turn costs ã1.")
balance = 10
if os.path.exists("One Armed Bandit.txt"):
with open("One Armed Bandit.txt", "r") as f:
contents = f.readlines()
continue_saved_game = input_verify("Do you want to continue your saved game?")
if continue_saved_game == "yes":
balance = float(contents[1].strip())
time.sleep(0.5)
print("nYour current balance is ã.n".format(balance))
time.sleep(1)
while True:
balance -= 1
reward = 0
a, b, c = [random.choice(weighted_fruits) for i in range(3)]
print("Spinning...")
for fruit in (a, b, c):
time.sleep(0.5)
print(fruit)
time.sleep(0.5)
if a == b == c:
win(3, a)
elif a == b or a == c or b == c:
if b == c:
win(2, c)
else:
win(2, a)
else:
print("Unlucky. None of your fruits matched each other.")
time.sleep(0.5)
print("nYour current balance is ã.n".format(balance))
time.sleep(0.5)
replay = input_verify("Do you want to play again?")
if replay == "no":
save = input_verify("Do you want to save your game?")
if save == "yes":
name = input("What is you name?")
with open("One Armed Bandit.txt", "w") as f:
f.write("'s Balance:n".format(name, balance))
break
I don't like how there are so many time.sleep()
calls scattered throughout my code. Is there a way to achieve the same thing but in a cleaner way. Moreover, could you suggest some ways to make it more efficient?
python python-3.x
edited Jan 27 at 23:51
Jamalâ¦
30.1k11114225
30.1k11114225
asked Jan 27 at 13:29
Milan Tom
1279
1279
1
Isn't using asking "could you suggest some ways to make it more efficient?" in conflict with usingtime.sleep()
.
â Peilonrayz
Jan 27 at 15:56
Yes. That is why I don't like the time.sleep() but the delays provide a better experience for the user.@Peilonrayz
â Milan Tom
Jan 27 at 19:03
add a comment |Â
1
Isn't using asking "could you suggest some ways to make it more efficient?" in conflict with usingtime.sleep()
.
â Peilonrayz
Jan 27 at 15:56
Yes. That is why I don't like the time.sleep() but the delays provide a better experience for the user.@Peilonrayz
â Milan Tom
Jan 27 at 19:03
1
1
Isn't using asking "could you suggest some ways to make it more efficient?" in conflict with using
time.sleep()
.â Peilonrayz
Jan 27 at 15:56
Isn't using asking "could you suggest some ways to make it more efficient?" in conflict with using
time.sleep()
.â Peilonrayz
Jan 27 at 15:56
Yes. That is why I don't like the time.sleep() but the delays provide a better experience for the user.@Peilonrayz
â Milan Tom
Jan 27 at 19:03
Yes. That is why I don't like the time.sleep() but the delays provide a better experience for the user.@Peilonrayz
â Milan Tom
Jan 27 at 19:03
add a comment |Â
2 Answers
2
active
oldest
votes
up vote
3
down vote
accepted
Thanks for sharing your code,
You should write code to adhere to the PEP8 standard, for the most part you've done that correctly, you have snake_case
variable and function names, but you've put all your imports on one line.
import random, time, os
should become
import random
import time
import os
Give one line per import.
Your code lacks a main guard. If I was to import any functions or classes from your current code, your slot machine game would start! Generally you just want to wrap your main
code in a conditional check that looks like
if __name__ == "__main__":
main()
where main
is a function that starts the execution of your program.
A pattern you use in your code is relying on a string value ("yes" or "no") to determine which function executes. This is standard enough and nothing really wrong with it, but I would recommend instead of checking for this string literals, to use boolean values instead which are taken from shorter helper functions. A quick example.
You offer the ability to save and load game state, this is a good thing. However, you save the state in a Human readable fashion - i.e. "Bob's Balance: 24". Unless you're relying on this format somewhere else or even just displaying it, you could just save a single number in a file. No need to record a name.
The main code does a lot, you could break this up into helper functions. Some possible function names that jump out are.
load_game
, save_game
, wants_to_save
, wants_to_replay
A pattern you use in your code is relying on a string value ("yes" or "no") to determine which function executes. This is standard enough and nothing really wrong with it, but I would recommend instead of checking for this string literals, to use boolean values instead which are taken from shorter helper functions. A quick example.
Your current code for prompting for saving the game
replay = input_verify("Do you want to play again?")
if replay == "no":
save = input_verify("Do you want to save your game?")
if save == "yes":
name = input("What is you name?")
with open("One Armed Bandit.txt", "w") as f:
f.write("'s Balance:n".format(name, balance))
break
I would create some helper functions that look like
def wants_to_replay():
return input_verify("Do you want to play again?") == "yes"
def wants_to_save():
return input_verify("Do you want to save your game?") == "yes"
def save():
name = input("What is you name?")
with open("One Armed Bandit.txt", "w") as f:
f.write("'s Balance:n".format(name, balance))
And then the code becomes
if not wants_to_replay():
if wants_to_save():
name = input("What is you name?")
with open("One Armed Bandit.txt", "w") as f:
f.write("'s Balance:n".format(name, balance))
break
The presence of a global
variable is a bit suspect. This usually indicates that maybe you should be using a class instead. Perhaps a SlotMachine
object.
It might be nice to be able to instantiate a slot machine object and call a start
method on it.
slot_machine = SlotMachine(saved_balance)
slot_machine.start()
this line,
a, b, c = [random.choice(weighted_fruits) for i in range(3)]
when there is an unused variable, you should indicate as such by naming it _
so this line becomes
a, b, c = [random.choice(weighted_fruits) for _ in range(3)]
But then a
, b
and c
are not great variable names. They do have a small scope and are used immediately after, but there's no reason to not give them a better name.
Why not just call them all_fruit
then in the next loop you can just use
for fruit in all_fruit:
...
In the next section you compare them to each other, you could simply then unpack these into variables like
fruit_a, fruit_b, fruit_c = all_fruit
I think single letter variable names (excluding _
and loop counters) should be avoided at all costs.
You currently have all the fruits values hard coded in, it might be worth while to extract that data into a file to make it easier to add/remove new ones. That's probably not a huge deal though and may have no real benefit.
I'm not sure if there's any cleaner way to achieve the sleep
s without scattering the calls throughout the code. You could maybe use named constants for the duration, but there may not be much value in that either.
You have no docstrings in your code. The print
statements act as a form of documentation however, and your variable names are (mostly) very good. But there may be some room for some useful comments.
Hopefully this review was useful for you!
1
You repeated the part betweenThanks for sharing your code,
andA quick example.
twice accidentally.
â Georgy
Jan 28 at 18:44
add a comment |Â
up vote
1
down vote
@chatton already said that your fruits' characteristics should be stored in a separate file. I propose to read them in a list of namedtuple
s. For exmaple, your Fruit
class definition will look like this:
from collections import namedtuple
Fruit = namedtuple('Fruit', ['name', 'weight', 'reward'])
And then after reading the fruits, you will have a list of Fruit
instances like this:
fruits = [Fruit(name='apple', weight=10000, reward=1),
Fruit(name='banana', weight=7000, reward=1.5),
Fruit(name='lemon', weight=6000, reward=1.75),
...
Fruit(name='melon', weight=1, reward=10000)]
weighted_fruits = [fruit for fruit in fruits for i in range(fruits[fruit][0])]
...
a, b, c = [random.choice(weighted_fruits) for i in range(3)]
You don't need this huge weighted_fruits
list of thousands elements to get random items with weights. Instead you can use random.choices
:
weights = [fruit.weight
for fruit in fruits]
while True:
selected_fruits = random.choices(fruits,
k=3,
weights=weights)
...
Use Counter to get fruits that were selected 2 or 3 times:
most_common_fruit, most_common_fruit_count = (Counter(selected_fruits)
.most_common(n=1)[0])
if most_common_fruit_count == 1:
print("Unlucky. None of your fruits matched each other.")
else:
...
Instead of a weirdly named function win
you should have something like this:
def get_new_balance(original_balance: float,
*,
matches: int,
reward: float) -> float:
return original_balance + reward * matches
No globals (really bad practice), no printing (it should be outside) and supplying amount of reward directly instead of a fruit itself. I added type hints as well. It's a good practice to include them. You help yourself and other people by being explicit about what type of data should be supplied to and returned from a function. And asterisk means that all arguments that follow after are keyword-only now:
if most_common_fruit_count == 1:
print("Unlucky. None of your fruits matched each other.")
else:
balance = get_new_balance(balance,
matches=most_common_fruit_count,
reward=most_common_fruit.reward)
Some other advises:
- Don't read a savefile before asking if user actually wants to read it.
float(contents[1].strip())
- there is no need tostrip
here.reward = 0
is never used.- Avoid hardcoding things like
'One Armed Bandit.txt'
or magic numbers likebalance = 10
. You could put them to a main function's signature as default parameters. - You could use f-strings if you are with Python 3.6:
print(f"You won ãreward!")
. - Try to follow PEP 8 style guide. @chatton already said about imports on separate lines. Also, don't exceed 79 characters on one line and separate functions by 2 blank lines.
open("One Armed Bandit.txt", "r")
- you can omit"r"
as it is default parameter.
Very helpful! Thank you! @Georgy
â Milan Tom
Jan 29 at 21:16
add a comment |Â
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
3
down vote
accepted
Thanks for sharing your code,
You should write code to adhere to the PEP8 standard, for the most part you've done that correctly, you have snake_case
variable and function names, but you've put all your imports on one line.
import random, time, os
should become
import random
import time
import os
Give one line per import.
Your code lacks a main guard. If I was to import any functions or classes from your current code, your slot machine game would start! Generally you just want to wrap your main
code in a conditional check that looks like
if __name__ == "__main__":
main()
where main
is a function that starts the execution of your program.
A pattern you use in your code is relying on a string value ("yes" or "no") to determine which function executes. This is standard enough and nothing really wrong with it, but I would recommend instead of checking for this string literals, to use boolean values instead which are taken from shorter helper functions. A quick example.
You offer the ability to save and load game state, this is a good thing. However, you save the state in a Human readable fashion - i.e. "Bob's Balance: 24". Unless you're relying on this format somewhere else or even just displaying it, you could just save a single number in a file. No need to record a name.
The main code does a lot, you could break this up into helper functions. Some possible function names that jump out are.
load_game
, save_game
, wants_to_save
, wants_to_replay
A pattern you use in your code is relying on a string value ("yes" or "no") to determine which function executes. This is standard enough and nothing really wrong with it, but I would recommend instead of checking for this string literals, to use boolean values instead which are taken from shorter helper functions. A quick example.
Your current code for prompting for saving the game
replay = input_verify("Do you want to play again?")
if replay == "no":
save = input_verify("Do you want to save your game?")
if save == "yes":
name = input("What is you name?")
with open("One Armed Bandit.txt", "w") as f:
f.write("'s Balance:n".format(name, balance))
break
I would create some helper functions that look like
def wants_to_replay():
return input_verify("Do you want to play again?") == "yes"
def wants_to_save():
return input_verify("Do you want to save your game?") == "yes"
def save():
name = input("What is you name?")
with open("One Armed Bandit.txt", "w") as f:
f.write("'s Balance:n".format(name, balance))
And then the code becomes
if not wants_to_replay():
if wants_to_save():
name = input("What is you name?")
with open("One Armed Bandit.txt", "w") as f:
f.write("'s Balance:n".format(name, balance))
break
The presence of a global
variable is a bit suspect. This usually indicates that maybe you should be using a class instead. Perhaps a SlotMachine
object.
It might be nice to be able to instantiate a slot machine object and call a start
method on it.
slot_machine = SlotMachine(saved_balance)
slot_machine.start()
this line,
a, b, c = [random.choice(weighted_fruits) for i in range(3)]
when there is an unused variable, you should indicate as such by naming it _
so this line becomes
a, b, c = [random.choice(weighted_fruits) for _ in range(3)]
But then a
, b
and c
are not great variable names. They do have a small scope and are used immediately after, but there's no reason to not give them a better name.
Why not just call them all_fruit
then in the next loop you can just use
for fruit in all_fruit:
...
In the next section you compare them to each other, you could simply then unpack these into variables like
fruit_a, fruit_b, fruit_c = all_fruit
I think single letter variable names (excluding _
and loop counters) should be avoided at all costs.
You currently have all the fruits values hard coded in, it might be worth while to extract that data into a file to make it easier to add/remove new ones. That's probably not a huge deal though and may have no real benefit.
I'm not sure if there's any cleaner way to achieve the sleep
s without scattering the calls throughout the code. You could maybe use named constants for the duration, but there may not be much value in that either.
You have no docstrings in your code. The print
statements act as a form of documentation however, and your variable names are (mostly) very good. But there may be some room for some useful comments.
Hopefully this review was useful for you!
1
You repeated the part betweenThanks for sharing your code,
andA quick example.
twice accidentally.
â Georgy
Jan 28 at 18:44
add a comment |Â
up vote
3
down vote
accepted
Thanks for sharing your code,
You should write code to adhere to the PEP8 standard, for the most part you've done that correctly, you have snake_case
variable and function names, but you've put all your imports on one line.
import random, time, os
should become
import random
import time
import os
Give one line per import.
Your code lacks a main guard. If I was to import any functions or classes from your current code, your slot machine game would start! Generally you just want to wrap your main
code in a conditional check that looks like
if __name__ == "__main__":
main()
where main
is a function that starts the execution of your program.
A pattern you use in your code is relying on a string value ("yes" or "no") to determine which function executes. This is standard enough and nothing really wrong with it, but I would recommend instead of checking for this string literals, to use boolean values instead which are taken from shorter helper functions. A quick example.
You offer the ability to save and load game state, this is a good thing. However, you save the state in a Human readable fashion - i.e. "Bob's Balance: 24". Unless you're relying on this format somewhere else or even just displaying it, you could just save a single number in a file. No need to record a name.
The main code does a lot, you could break this up into helper functions. Some possible function names that jump out are.
load_game
, save_game
, wants_to_save
, wants_to_replay
A pattern you use in your code is relying on a string value ("yes" or "no") to determine which function executes. This is standard enough and nothing really wrong with it, but I would recommend instead of checking for this string literals, to use boolean values instead which are taken from shorter helper functions. A quick example.
Your current code for prompting for saving the game
replay = input_verify("Do you want to play again?")
if replay == "no":
save = input_verify("Do you want to save your game?")
if save == "yes":
name = input("What is you name?")
with open("One Armed Bandit.txt", "w") as f:
f.write("'s Balance:n".format(name, balance))
break
I would create some helper functions that look like
def wants_to_replay():
return input_verify("Do you want to play again?") == "yes"
def wants_to_save():
return input_verify("Do you want to save your game?") == "yes"
def save():
name = input("What is you name?")
with open("One Armed Bandit.txt", "w") as f:
f.write("'s Balance:n".format(name, balance))
And then the code becomes
if not wants_to_replay():
if wants_to_save():
name = input("What is you name?")
with open("One Armed Bandit.txt", "w") as f:
f.write("'s Balance:n".format(name, balance))
break
The presence of a global
variable is a bit suspect. This usually indicates that maybe you should be using a class instead. Perhaps a SlotMachine
object.
It might be nice to be able to instantiate a slot machine object and call a start
method on it.
slot_machine = SlotMachine(saved_balance)
slot_machine.start()
this line,
a, b, c = [random.choice(weighted_fruits) for i in range(3)]
when there is an unused variable, you should indicate as such by naming it _
so this line becomes
a, b, c = [random.choice(weighted_fruits) for _ in range(3)]
But then a
, b
and c
are not great variable names. They do have a small scope and are used immediately after, but there's no reason to not give them a better name.
Why not just call them all_fruit
then in the next loop you can just use
for fruit in all_fruit:
...
In the next section you compare them to each other, you could simply then unpack these into variables like
fruit_a, fruit_b, fruit_c = all_fruit
I think single letter variable names (excluding _
and loop counters) should be avoided at all costs.
You currently have all the fruits values hard coded in, it might be worth while to extract that data into a file to make it easier to add/remove new ones. That's probably not a huge deal though and may have no real benefit.
I'm not sure if there's any cleaner way to achieve the sleep
s without scattering the calls throughout the code. You could maybe use named constants for the duration, but there may not be much value in that either.
You have no docstrings in your code. The print
statements act as a form of documentation however, and your variable names are (mostly) very good. But there may be some room for some useful comments.
Hopefully this review was useful for you!
1
You repeated the part betweenThanks for sharing your code,
andA quick example.
twice accidentally.
â Georgy
Jan 28 at 18:44
add a comment |Â
up vote
3
down vote
accepted
up vote
3
down vote
accepted
Thanks for sharing your code,
You should write code to adhere to the PEP8 standard, for the most part you've done that correctly, you have snake_case
variable and function names, but you've put all your imports on one line.
import random, time, os
should become
import random
import time
import os
Give one line per import.
Your code lacks a main guard. If I was to import any functions or classes from your current code, your slot machine game would start! Generally you just want to wrap your main
code in a conditional check that looks like
if __name__ == "__main__":
main()
where main
is a function that starts the execution of your program.
A pattern you use in your code is relying on a string value ("yes" or "no") to determine which function executes. This is standard enough and nothing really wrong with it, but I would recommend instead of checking for this string literals, to use boolean values instead which are taken from shorter helper functions. A quick example.
You offer the ability to save and load game state, this is a good thing. However, you save the state in a Human readable fashion - i.e. "Bob's Balance: 24". Unless you're relying on this format somewhere else or even just displaying it, you could just save a single number in a file. No need to record a name.
The main code does a lot, you could break this up into helper functions. Some possible function names that jump out are.
load_game
, save_game
, wants_to_save
, wants_to_replay
A pattern you use in your code is relying on a string value ("yes" or "no") to determine which function executes. This is standard enough and nothing really wrong with it, but I would recommend instead of checking for this string literals, to use boolean values instead which are taken from shorter helper functions. A quick example.
Your current code for prompting for saving the game
replay = input_verify("Do you want to play again?")
if replay == "no":
save = input_verify("Do you want to save your game?")
if save == "yes":
name = input("What is you name?")
with open("One Armed Bandit.txt", "w") as f:
f.write("'s Balance:n".format(name, balance))
break
I would create some helper functions that look like
def wants_to_replay():
return input_verify("Do you want to play again?") == "yes"
def wants_to_save():
return input_verify("Do you want to save your game?") == "yes"
def save():
name = input("What is you name?")
with open("One Armed Bandit.txt", "w") as f:
f.write("'s Balance:n".format(name, balance))
And then the code becomes
if not wants_to_replay():
if wants_to_save():
name = input("What is you name?")
with open("One Armed Bandit.txt", "w") as f:
f.write("'s Balance:n".format(name, balance))
break
The presence of a global
variable is a bit suspect. This usually indicates that maybe you should be using a class instead. Perhaps a SlotMachine
object.
It might be nice to be able to instantiate a slot machine object and call a start
method on it.
slot_machine = SlotMachine(saved_balance)
slot_machine.start()
this line,
a, b, c = [random.choice(weighted_fruits) for i in range(3)]
when there is an unused variable, you should indicate as such by naming it _
so this line becomes
a, b, c = [random.choice(weighted_fruits) for _ in range(3)]
But then a
, b
and c
are not great variable names. They do have a small scope and are used immediately after, but there's no reason to not give them a better name.
Why not just call them all_fruit
then in the next loop you can just use
for fruit in all_fruit:
...
In the next section you compare them to each other, you could simply then unpack these into variables like
fruit_a, fruit_b, fruit_c = all_fruit
I think single letter variable names (excluding _
and loop counters) should be avoided at all costs.
You currently have all the fruits values hard coded in, it might be worth while to extract that data into a file to make it easier to add/remove new ones. That's probably not a huge deal though and may have no real benefit.
I'm not sure if there's any cleaner way to achieve the sleep
s without scattering the calls throughout the code. You could maybe use named constants for the duration, but there may not be much value in that either.
You have no docstrings in your code. The print
statements act as a form of documentation however, and your variable names are (mostly) very good. But there may be some room for some useful comments.
Hopefully this review was useful for you!
Thanks for sharing your code,
You should write code to adhere to the PEP8 standard, for the most part you've done that correctly, you have snake_case
variable and function names, but you've put all your imports on one line.
import random, time, os
should become
import random
import time
import os
Give one line per import.
Your code lacks a main guard. If I was to import any functions or classes from your current code, your slot machine game would start! Generally you just want to wrap your main
code in a conditional check that looks like
if __name__ == "__main__":
main()
where main
is a function that starts the execution of your program.
A pattern you use in your code is relying on a string value ("yes" or "no") to determine which function executes. This is standard enough and nothing really wrong with it, but I would recommend instead of checking for this string literals, to use boolean values instead which are taken from shorter helper functions. A quick example.
You offer the ability to save and load game state, this is a good thing. However, you save the state in a Human readable fashion - i.e. "Bob's Balance: 24". Unless you're relying on this format somewhere else or even just displaying it, you could just save a single number in a file. No need to record a name.
The main code does a lot, you could break this up into helper functions. Some possible function names that jump out are.
load_game
, save_game
, wants_to_save
, wants_to_replay
A pattern you use in your code is relying on a string value ("yes" or "no") to determine which function executes. This is standard enough and nothing really wrong with it, but I would recommend instead of checking for this string literals, to use boolean values instead which are taken from shorter helper functions. A quick example.
Your current code for prompting for saving the game
replay = input_verify("Do you want to play again?")
if replay == "no":
save = input_verify("Do you want to save your game?")
if save == "yes":
name = input("What is you name?")
with open("One Armed Bandit.txt", "w") as f:
f.write("'s Balance:n".format(name, balance))
break
I would create some helper functions that look like
def wants_to_replay():
return input_verify("Do you want to play again?") == "yes"
def wants_to_save():
return input_verify("Do you want to save your game?") == "yes"
def save():
name = input("What is you name?")
with open("One Armed Bandit.txt", "w") as f:
f.write("'s Balance:n".format(name, balance))
And then the code becomes
if not wants_to_replay():
if wants_to_save():
name = input("What is you name?")
with open("One Armed Bandit.txt", "w") as f:
f.write("'s Balance:n".format(name, balance))
break
The presence of a global
variable is a bit suspect. This usually indicates that maybe you should be using a class instead. Perhaps a SlotMachine
object.
It might be nice to be able to instantiate a slot machine object and call a start
method on it.
slot_machine = SlotMachine(saved_balance)
slot_machine.start()
this line,
a, b, c = [random.choice(weighted_fruits) for i in range(3)]
when there is an unused variable, you should indicate as such by naming it _
so this line becomes
a, b, c = [random.choice(weighted_fruits) for _ in range(3)]
But then a
, b
and c
are not great variable names. They do have a small scope and are used immediately after, but there's no reason to not give them a better name.
Why not just call them all_fruit
then in the next loop you can just use
for fruit in all_fruit:
...
In the next section you compare them to each other, you could simply then unpack these into variables like
fruit_a, fruit_b, fruit_c = all_fruit
I think single letter variable names (excluding _
and loop counters) should be avoided at all costs.
You currently have all the fruits values hard coded in, it might be worth while to extract that data into a file to make it easier to add/remove new ones. That's probably not a huge deal though and may have no real benefit.
I'm not sure if there's any cleaner way to achieve the sleep
s without scattering the calls throughout the code. You could maybe use named constants for the duration, but there may not be much value in that either.
You have no docstrings in your code. The print
statements act as a form of documentation however, and your variable names are (mostly) very good. But there may be some room for some useful comments.
Hopefully this review was useful for you!
edited Jan 28 at 19:18
answered Jan 27 at 23:44
chatton
1,371139
1,371139
1
You repeated the part betweenThanks for sharing your code,
andA quick example.
twice accidentally.
â Georgy
Jan 28 at 18:44
add a comment |Â
1
You repeated the part betweenThanks for sharing your code,
andA quick example.
twice accidentally.
â Georgy
Jan 28 at 18:44
1
1
You repeated the part between
Thanks for sharing your code,
and A quick example.
twice accidentally.â Georgy
Jan 28 at 18:44
You repeated the part between
Thanks for sharing your code,
and A quick example.
twice accidentally.â Georgy
Jan 28 at 18:44
add a comment |Â
up vote
1
down vote
@chatton already said that your fruits' characteristics should be stored in a separate file. I propose to read them in a list of namedtuple
s. For exmaple, your Fruit
class definition will look like this:
from collections import namedtuple
Fruit = namedtuple('Fruit', ['name', 'weight', 'reward'])
And then after reading the fruits, you will have a list of Fruit
instances like this:
fruits = [Fruit(name='apple', weight=10000, reward=1),
Fruit(name='banana', weight=7000, reward=1.5),
Fruit(name='lemon', weight=6000, reward=1.75),
...
Fruit(name='melon', weight=1, reward=10000)]
weighted_fruits = [fruit for fruit in fruits for i in range(fruits[fruit][0])]
...
a, b, c = [random.choice(weighted_fruits) for i in range(3)]
You don't need this huge weighted_fruits
list of thousands elements to get random items with weights. Instead you can use random.choices
:
weights = [fruit.weight
for fruit in fruits]
while True:
selected_fruits = random.choices(fruits,
k=3,
weights=weights)
...
Use Counter to get fruits that were selected 2 or 3 times:
most_common_fruit, most_common_fruit_count = (Counter(selected_fruits)
.most_common(n=1)[0])
if most_common_fruit_count == 1:
print("Unlucky. None of your fruits matched each other.")
else:
...
Instead of a weirdly named function win
you should have something like this:
def get_new_balance(original_balance: float,
*,
matches: int,
reward: float) -> float:
return original_balance + reward * matches
No globals (really bad practice), no printing (it should be outside) and supplying amount of reward directly instead of a fruit itself. I added type hints as well. It's a good practice to include them. You help yourself and other people by being explicit about what type of data should be supplied to and returned from a function. And asterisk means that all arguments that follow after are keyword-only now:
if most_common_fruit_count == 1:
print("Unlucky. None of your fruits matched each other.")
else:
balance = get_new_balance(balance,
matches=most_common_fruit_count,
reward=most_common_fruit.reward)
Some other advises:
- Don't read a savefile before asking if user actually wants to read it.
float(contents[1].strip())
- there is no need tostrip
here.reward = 0
is never used.- Avoid hardcoding things like
'One Armed Bandit.txt'
or magic numbers likebalance = 10
. You could put them to a main function's signature as default parameters. - You could use f-strings if you are with Python 3.6:
print(f"You won ãreward!")
. - Try to follow PEP 8 style guide. @chatton already said about imports on separate lines. Also, don't exceed 79 characters on one line and separate functions by 2 blank lines.
open("One Armed Bandit.txt", "r")
- you can omit"r"
as it is default parameter.
Very helpful! Thank you! @Georgy
â Milan Tom
Jan 29 at 21:16
add a comment |Â
up vote
1
down vote
@chatton already said that your fruits' characteristics should be stored in a separate file. I propose to read them in a list of namedtuple
s. For exmaple, your Fruit
class definition will look like this:
from collections import namedtuple
Fruit = namedtuple('Fruit', ['name', 'weight', 'reward'])
And then after reading the fruits, you will have a list of Fruit
instances like this:
fruits = [Fruit(name='apple', weight=10000, reward=1),
Fruit(name='banana', weight=7000, reward=1.5),
Fruit(name='lemon', weight=6000, reward=1.75),
...
Fruit(name='melon', weight=1, reward=10000)]
weighted_fruits = [fruit for fruit in fruits for i in range(fruits[fruit][0])]
...
a, b, c = [random.choice(weighted_fruits) for i in range(3)]
You don't need this huge weighted_fruits
list of thousands elements to get random items with weights. Instead you can use random.choices
:
weights = [fruit.weight
for fruit in fruits]
while True:
selected_fruits = random.choices(fruits,
k=3,
weights=weights)
...
Use Counter to get fruits that were selected 2 or 3 times:
most_common_fruit, most_common_fruit_count = (Counter(selected_fruits)
.most_common(n=1)[0])
if most_common_fruit_count == 1:
print("Unlucky. None of your fruits matched each other.")
else:
...
Instead of a weirdly named function win
you should have something like this:
def get_new_balance(original_balance: float,
*,
matches: int,
reward: float) -> float:
return original_balance + reward * matches
No globals (really bad practice), no printing (it should be outside) and supplying amount of reward directly instead of a fruit itself. I added type hints as well. It's a good practice to include them. You help yourself and other people by being explicit about what type of data should be supplied to and returned from a function. And asterisk means that all arguments that follow after are keyword-only now:
if most_common_fruit_count == 1:
print("Unlucky. None of your fruits matched each other.")
else:
balance = get_new_balance(balance,
matches=most_common_fruit_count,
reward=most_common_fruit.reward)
Some other advises:
- Don't read a savefile before asking if user actually wants to read it.
float(contents[1].strip())
- there is no need tostrip
here.reward = 0
is never used.- Avoid hardcoding things like
'One Armed Bandit.txt'
or magic numbers likebalance = 10
. You could put them to a main function's signature as default parameters. - You could use f-strings if you are with Python 3.6:
print(f"You won ãreward!")
. - Try to follow PEP 8 style guide. @chatton already said about imports on separate lines. Also, don't exceed 79 characters on one line and separate functions by 2 blank lines.
open("One Armed Bandit.txt", "r")
- you can omit"r"
as it is default parameter.
Very helpful! Thank you! @Georgy
â Milan Tom
Jan 29 at 21:16
add a comment |Â
up vote
1
down vote
up vote
1
down vote
@chatton already said that your fruits' characteristics should be stored in a separate file. I propose to read them in a list of namedtuple
s. For exmaple, your Fruit
class definition will look like this:
from collections import namedtuple
Fruit = namedtuple('Fruit', ['name', 'weight', 'reward'])
And then after reading the fruits, you will have a list of Fruit
instances like this:
fruits = [Fruit(name='apple', weight=10000, reward=1),
Fruit(name='banana', weight=7000, reward=1.5),
Fruit(name='lemon', weight=6000, reward=1.75),
...
Fruit(name='melon', weight=1, reward=10000)]
weighted_fruits = [fruit for fruit in fruits for i in range(fruits[fruit][0])]
...
a, b, c = [random.choice(weighted_fruits) for i in range(3)]
You don't need this huge weighted_fruits
list of thousands elements to get random items with weights. Instead you can use random.choices
:
weights = [fruit.weight
for fruit in fruits]
while True:
selected_fruits = random.choices(fruits,
k=3,
weights=weights)
...
Use Counter to get fruits that were selected 2 or 3 times:
most_common_fruit, most_common_fruit_count = (Counter(selected_fruits)
.most_common(n=1)[0])
if most_common_fruit_count == 1:
print("Unlucky. None of your fruits matched each other.")
else:
...
Instead of a weirdly named function win
you should have something like this:
def get_new_balance(original_balance: float,
*,
matches: int,
reward: float) -> float:
return original_balance + reward * matches
No globals (really bad practice), no printing (it should be outside) and supplying amount of reward directly instead of a fruit itself. I added type hints as well. It's a good practice to include them. You help yourself and other people by being explicit about what type of data should be supplied to and returned from a function. And asterisk means that all arguments that follow after are keyword-only now:
if most_common_fruit_count == 1:
print("Unlucky. None of your fruits matched each other.")
else:
balance = get_new_balance(balance,
matches=most_common_fruit_count,
reward=most_common_fruit.reward)
Some other advises:
- Don't read a savefile before asking if user actually wants to read it.
float(contents[1].strip())
- there is no need tostrip
here.reward = 0
is never used.- Avoid hardcoding things like
'One Armed Bandit.txt'
or magic numbers likebalance = 10
. You could put them to a main function's signature as default parameters. - You could use f-strings if you are with Python 3.6:
print(f"You won ãreward!")
. - Try to follow PEP 8 style guide. @chatton already said about imports on separate lines. Also, don't exceed 79 characters on one line and separate functions by 2 blank lines.
open("One Armed Bandit.txt", "r")
- you can omit"r"
as it is default parameter.
@chatton already said that your fruits' characteristics should be stored in a separate file. I propose to read them in a list of namedtuple
s. For exmaple, your Fruit
class definition will look like this:
from collections import namedtuple
Fruit = namedtuple('Fruit', ['name', 'weight', 'reward'])
And then after reading the fruits, you will have a list of Fruit
instances like this:
fruits = [Fruit(name='apple', weight=10000, reward=1),
Fruit(name='banana', weight=7000, reward=1.5),
Fruit(name='lemon', weight=6000, reward=1.75),
...
Fruit(name='melon', weight=1, reward=10000)]
weighted_fruits = [fruit for fruit in fruits for i in range(fruits[fruit][0])]
...
a, b, c = [random.choice(weighted_fruits) for i in range(3)]
You don't need this huge weighted_fruits
list of thousands elements to get random items with weights. Instead you can use random.choices
:
weights = [fruit.weight
for fruit in fruits]
while True:
selected_fruits = random.choices(fruits,
k=3,
weights=weights)
...
Use Counter to get fruits that were selected 2 or 3 times:
most_common_fruit, most_common_fruit_count = (Counter(selected_fruits)
.most_common(n=1)[0])
if most_common_fruit_count == 1:
print("Unlucky. None of your fruits matched each other.")
else:
...
Instead of a weirdly named function win
you should have something like this:
def get_new_balance(original_balance: float,
*,
matches: int,
reward: float) -> float:
return original_balance + reward * matches
No globals (really bad practice), no printing (it should be outside) and supplying amount of reward directly instead of a fruit itself. I added type hints as well. It's a good practice to include them. You help yourself and other people by being explicit about what type of data should be supplied to and returned from a function. And asterisk means that all arguments that follow after are keyword-only now:
if most_common_fruit_count == 1:
print("Unlucky. None of your fruits matched each other.")
else:
balance = get_new_balance(balance,
matches=most_common_fruit_count,
reward=most_common_fruit.reward)
Some other advises:
- Don't read a savefile before asking if user actually wants to read it.
float(contents[1].strip())
- there is no need tostrip
here.reward = 0
is never used.- Avoid hardcoding things like
'One Armed Bandit.txt'
or magic numbers likebalance = 10
. You could put them to a main function's signature as default parameters. - You could use f-strings if you are with Python 3.6:
print(f"You won ãreward!")
. - Try to follow PEP 8 style guide. @chatton already said about imports on separate lines. Also, don't exceed 79 characters on one line and separate functions by 2 blank lines.
open("One Armed Bandit.txt", "r")
- you can omit"r"
as it is default parameter.
answered Jan 28 at 18:41
Georgy
5521415
5521415
Very helpful! Thank you! @Georgy
â Milan Tom
Jan 29 at 21:16
add a comment |Â
Very helpful! Thank you! @Georgy
â Milan Tom
Jan 29 at 21:16
Very helpful! Thank you! @Georgy
â Milan Tom
Jan 29 at 21:16
Very helpful! Thank you! @Georgy
â Milan Tom
Jan 29 at 21:16
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f186129%2fslot-machine-in-python-3%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
1
Isn't using asking "could you suggest some ways to make it more efficient?" in conflict with using
time.sleep()
.â Peilonrayz
Jan 27 at 15:56
Yes. That is why I don't like the time.sleep() but the delays provide a better experience for the user.@Peilonrayz
â Milan Tom
Jan 27 at 19:03