Slot Machine in Python 3

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
4
down vote

favorite
1












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?







share|improve this question

















  • 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

















up vote
4
down vote

favorite
1












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?







share|improve this question

















  • 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













up vote
4
down vote

favorite
1









up vote
4
down vote

favorite
1






1





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?







share|improve this question













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?









share|improve this question












share|improve this question




share|improve this question








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 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













  • 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








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











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 sleeps 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!






share|improve this answer



















  • 1




    You repeated the part between Thanks for sharing your code, and A quick example. twice accidentally.
    – Georgy
    Jan 28 at 18:44

















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 namedtuples. 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:



  1. Don't read a savefile before asking if user actually wants to read it.


  2. float(contents[1].strip()) - there is no need to strip here.


  3. reward = 0 is never used.

  4. Avoid hardcoding things like 'One Armed Bandit.txt' or magic numbers like balance = 10. You could put them to a main function's signature as default parameters.

  5. You could use f-strings if you are with Python 3.6: print(f"You won £reward!").

  6. 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.


  7. open("One Armed Bandit.txt", "r") - you can omit "r" as it is default parameter.





share|improve this answer





















  • Very helpful! Thank you! @Georgy
    – Milan Tom
    Jan 29 at 21:16










Your Answer




StackExchange.ifUsing("editor", function ()
return StackExchange.using("mathjaxEditing", function ()
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
);
);
, "mathjax-editing");

StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
convertImagesToLinks: false,
noModals: false,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);








 

draft saved


draft discarded


















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






























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 sleeps 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!






share|improve this answer



















  • 1




    You repeated the part between Thanks for sharing your code, and A quick example. twice accidentally.
    – Georgy
    Jan 28 at 18:44














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 sleeps 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!






share|improve this answer



















  • 1




    You repeated the part between Thanks for sharing your code, and A quick example. twice accidentally.
    – Georgy
    Jan 28 at 18:44












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 sleeps 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!






share|improve this answer















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 sleeps 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!







share|improve this answer















share|improve this answer



share|improve this answer








edited Jan 28 at 19:18


























answered Jan 27 at 23:44









chatton

1,371139




1,371139







  • 1




    You repeated the part between Thanks for sharing your code, and A quick example. twice accidentally.
    – Georgy
    Jan 28 at 18:44












  • 1




    You repeated the part between Thanks for sharing your code, and A 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












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 namedtuples. 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:



  1. Don't read a savefile before asking if user actually wants to read it.


  2. float(contents[1].strip()) - there is no need to strip here.


  3. reward = 0 is never used.

  4. Avoid hardcoding things like 'One Armed Bandit.txt' or magic numbers like balance = 10. You could put them to a main function's signature as default parameters.

  5. You could use f-strings if you are with Python 3.6: print(f"You won £reward!").

  6. 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.


  7. open("One Armed Bandit.txt", "r") - you can omit "r" as it is default parameter.





share|improve this answer





















  • Very helpful! Thank you! @Georgy
    – Milan Tom
    Jan 29 at 21:16














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 namedtuples. 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:



  1. Don't read a savefile before asking if user actually wants to read it.


  2. float(contents[1].strip()) - there is no need to strip here.


  3. reward = 0 is never used.

  4. Avoid hardcoding things like 'One Armed Bandit.txt' or magic numbers like balance = 10. You could put them to a main function's signature as default parameters.

  5. You could use f-strings if you are with Python 3.6: print(f"You won £reward!").

  6. 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.


  7. open("One Armed Bandit.txt", "r") - you can omit "r" as it is default parameter.





share|improve this answer





















  • Very helpful! Thank you! @Georgy
    – Milan Tom
    Jan 29 at 21:16












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 namedtuples. 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:



  1. Don't read a savefile before asking if user actually wants to read it.


  2. float(contents[1].strip()) - there is no need to strip here.


  3. reward = 0 is never used.

  4. Avoid hardcoding things like 'One Armed Bandit.txt' or magic numbers like balance = 10. You could put them to a main function's signature as default parameters.

  5. You could use f-strings if you are with Python 3.6: print(f"You won £reward!").

  6. 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.


  7. open("One Armed Bandit.txt", "r") - you can omit "r" as it is default parameter.





share|improve this answer













@chatton already said that your fruits' characteristics should be stored in a separate file. I propose to read them in a list of namedtuples. 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:



  1. Don't read a savefile before asking if user actually wants to read it.


  2. float(contents[1].strip()) - there is no need to strip here.


  3. reward = 0 is never used.

  4. Avoid hardcoding things like 'One Armed Bandit.txt' or magic numbers like balance = 10. You could put them to a main function's signature as default parameters.

  5. You could use f-strings if you are with Python 3.6: print(f"You won £reward!").

  6. 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.


  7. open("One Armed Bandit.txt", "r") - you can omit "r" as it is default parameter.






share|improve this answer













share|improve this answer



share|improve this answer











answered Jan 28 at 18:41









Georgy

5521415




5521415











  • 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




Very helpful! Thank you! @Georgy
– Milan Tom
Jan 29 at 21:16












 

draft saved


draft discarded


























 


draft saved


draft discarded














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













































































Popular posts from this blog

Chat program with C++ and SFML

Function to Return a JSON Like Objects Using VBA Collections and Arrays

Will my employers contract hold up in court?