Python Bingo game that stores card in a dictionaryFind Missing Numbers in an int listWar card game...
How to prove (A v B), (A → C), (B → D) therefore (C v D)
Find the number for the question mark
Power Adapter for Traveling to Scotland (I live in the US)
Injection from two strings to one string
Can I voluntarily exit from the US after a 20 year overstay, or could I be detained at the airport?
Is there any problem with students seeing faculty naked in university gym?
"To Verb a Noun"
A goat is tied to the corner of a shed
Had there been instances of national states banning harmful imports before the Opium wars?
Remove one or more fields, delimited by a "-", at end of line
I've been fired, was allowed to announce it as if I quit and given extra notice, how to handle the questions?
Characters in a conversation
Forcing all requests to HTTPS vs not forcing all requests
Why does the first method take more than twice as long to create an array?
Meaning/Translation of title "The Light Fantastic" By Terry Pratchet
How to know the size of a package
The locus of polynomials with specified root multiplicities
What benefits are there to blocking most search engines?
In the Star Trek: TNG continuity is cloning illegal?
Which accidental continues through the bar?
Does the Flixbus N770 from Antwerp to Copenhagen go by ferry to Denmark
Coffee Grounds and Gritty Butter Cream Icing
How come the Russian cognate for the Czech word "čerstvý" (fresh) means entirely the opposite thing (stale)?
Does SQL Server's serializable isolation level lock entire table
Python Bingo game that stores card in a dictionary
Find Missing Numbers in an int listWar card game simulatorApproximate (250 over 100) permutation best fitting certain criteriaCustom card gameGuess number gameA simple lotto game in PythonSimple game of lotto in Python v 2.0Randomly pick key from dictionary weighted by the keys valueTake a list of nums and return max and minPython function to get numbers around another number
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty{
margin-bottom:0;
}
$begingroup$
I am super proud of this python bingo game I wrote. I am posting it here to make get some help reviewing it to make sure I'm using the best practices. This is for a class, and my professor is a stickler for using best practices. I went above and beyond what the assignment called for so I need a little more helping making sure the code is okay. Any help is appreciated!
Here is the code:
# Author: Helana Brock
# Homework 2, problem 3
import random
random_draw_list = random.sample(range(1, 76), 75)
def generate_card():
"""
Generates a bingo card and stores the numbers in a dictionary.
"""
card = {
"B": [],
"I": [],
"N": [],
"G": [],
"O": [],
}
min = 1
max = 15
for letter in card:
card[letter] = random.sample(range(min, max), 5)
min += 15
max += 15
if letter == "N":
card[letter][2] = "X" # free space!
return card
def print_card(card):
"""
Prints the bingo card.
Arguments:
card (dictionary): The card to be printed out.
"""
for letter in card:
print(letter, end="t")
for number in card[letter]:
print(number, end="t")
print("n")
print("n")
def draw(card, list):
"""
Pops a number off a list of random numbers. Using the pop method ensures no duplicate
numbers will be drawn.
Arguments:
card (dictionary): The card to to check for the number that was drawn.
list (list): The list of random numbers to be drawn from.
"""
number_drawn = random_draw_list.pop()
for letter in card:
i = 0
for number in card[letter]:
if number == number_drawn:
card[letter][i] = "X"
i += 1
return number_drawn
def check_win(card):
"""
First checks for diagonal wins, then four-corner, then horizontal, and finally, vertical.
Arguments:
card (dictionary): The card to check for a win.
"""
win = False
if card["B"][0] == "X" and card["I"][1] == "X" and card["N"][2] == "X" and card["G"][3] == "X" and card["O"][4] == "X":
win = True
elif card["O"][0] == "X" and card["G"][1] == "X" and card["N"][2] == "X" and card["I"][3] == "X" and card["B"][4] == "X":
win = True
elif card["B"][0] == "X" and card["O"][4] == "X" and card["B"][4] == "X" and card["O"][0] == "X":
win = True
for letter in card:
if(len(set(card[letter]))==1):
win = True
for i in range(5):
cnt = 0
for letter in card:
if card[letter][i] == "X":
cnt += 1
print(cnt)
if cnt == 5:
win = True
break
return win
def main():
"""
Main method for the program. Generates a card, prints it, and loops through drawing
and printing until the check_win method returns True or the user enters "quit".
"""
print("Let's play bingo!")
card = generate_card()
print("nHere is your card:n")
print_card(card)
print("nKeep pressing enter to continue or type quit to exit.n")
user_input = input()
win = check_win(card)
balls_till_win = 0
while win == False and user_input != "quit":
number_drawn = draw(card, random_draw_list)
balls_till_win += 1
print(f"nNumber drawn: {number_drawn}.")
print(f"Total balls drawn: {balls_till_win}.n")
print_card(card)
win = check_win(card)
user_input = input()
if win == True:
print(f"nBingo! Total balls to win: {balls_till_win}.")
else:
print("Goodbye! Thanks for playing!")
main()
python game homework
$endgroup$
add a comment
|
$begingroup$
I am super proud of this python bingo game I wrote. I am posting it here to make get some help reviewing it to make sure I'm using the best practices. This is for a class, and my professor is a stickler for using best practices. I went above and beyond what the assignment called for so I need a little more helping making sure the code is okay. Any help is appreciated!
Here is the code:
# Author: Helana Brock
# Homework 2, problem 3
import random
random_draw_list = random.sample(range(1, 76), 75)
def generate_card():
"""
Generates a bingo card and stores the numbers in a dictionary.
"""
card = {
"B": [],
"I": [],
"N": [],
"G": [],
"O": [],
}
min = 1
max = 15
for letter in card:
card[letter] = random.sample(range(min, max), 5)
min += 15
max += 15
if letter == "N":
card[letter][2] = "X" # free space!
return card
def print_card(card):
"""
Prints the bingo card.
Arguments:
card (dictionary): The card to be printed out.
"""
for letter in card:
print(letter, end="t")
for number in card[letter]:
print(number, end="t")
print("n")
print("n")
def draw(card, list):
"""
Pops a number off a list of random numbers. Using the pop method ensures no duplicate
numbers will be drawn.
Arguments:
card (dictionary): The card to to check for the number that was drawn.
list (list): The list of random numbers to be drawn from.
"""
number_drawn = random_draw_list.pop()
for letter in card:
i = 0
for number in card[letter]:
if number == number_drawn:
card[letter][i] = "X"
i += 1
return number_drawn
def check_win(card):
"""
First checks for diagonal wins, then four-corner, then horizontal, and finally, vertical.
Arguments:
card (dictionary): The card to check for a win.
"""
win = False
if card["B"][0] == "X" and card["I"][1] == "X" and card["N"][2] == "X" and card["G"][3] == "X" and card["O"][4] == "X":
win = True
elif card["O"][0] == "X" and card["G"][1] == "X" and card["N"][2] == "X" and card["I"][3] == "X" and card["B"][4] == "X":
win = True
elif card["B"][0] == "X" and card["O"][4] == "X" and card["B"][4] == "X" and card["O"][0] == "X":
win = True
for letter in card:
if(len(set(card[letter]))==1):
win = True
for i in range(5):
cnt = 0
for letter in card:
if card[letter][i] == "X":
cnt += 1
print(cnt)
if cnt == 5:
win = True
break
return win
def main():
"""
Main method for the program. Generates a card, prints it, and loops through drawing
and printing until the check_win method returns True or the user enters "quit".
"""
print("Let's play bingo!")
card = generate_card()
print("nHere is your card:n")
print_card(card)
print("nKeep pressing enter to continue or type quit to exit.n")
user_input = input()
win = check_win(card)
balls_till_win = 0
while win == False and user_input != "quit":
number_drawn = draw(card, random_draw_list)
balls_till_win += 1
print(f"nNumber drawn: {number_drawn}.")
print(f"Total balls drawn: {balls_till_win}.n")
print_card(card)
win = check_win(card)
user_input = input()
if win == True:
print(f"nBingo! Total balls to win: {balls_till_win}.")
else:
print("Goodbye! Thanks for playing!")
main()
python game homework
$endgroup$
add a comment
|
$begingroup$
I am super proud of this python bingo game I wrote. I am posting it here to make get some help reviewing it to make sure I'm using the best practices. This is for a class, and my professor is a stickler for using best practices. I went above and beyond what the assignment called for so I need a little more helping making sure the code is okay. Any help is appreciated!
Here is the code:
# Author: Helana Brock
# Homework 2, problem 3
import random
random_draw_list = random.sample(range(1, 76), 75)
def generate_card():
"""
Generates a bingo card and stores the numbers in a dictionary.
"""
card = {
"B": [],
"I": [],
"N": [],
"G": [],
"O": [],
}
min = 1
max = 15
for letter in card:
card[letter] = random.sample(range(min, max), 5)
min += 15
max += 15
if letter == "N":
card[letter][2] = "X" # free space!
return card
def print_card(card):
"""
Prints the bingo card.
Arguments:
card (dictionary): The card to be printed out.
"""
for letter in card:
print(letter, end="t")
for number in card[letter]:
print(number, end="t")
print("n")
print("n")
def draw(card, list):
"""
Pops a number off a list of random numbers. Using the pop method ensures no duplicate
numbers will be drawn.
Arguments:
card (dictionary): The card to to check for the number that was drawn.
list (list): The list of random numbers to be drawn from.
"""
number_drawn = random_draw_list.pop()
for letter in card:
i = 0
for number in card[letter]:
if number == number_drawn:
card[letter][i] = "X"
i += 1
return number_drawn
def check_win(card):
"""
First checks for diagonal wins, then four-corner, then horizontal, and finally, vertical.
Arguments:
card (dictionary): The card to check for a win.
"""
win = False
if card["B"][0] == "X" and card["I"][1] == "X" and card["N"][2] == "X" and card["G"][3] == "X" and card["O"][4] == "X":
win = True
elif card["O"][0] == "X" and card["G"][1] == "X" and card["N"][2] == "X" and card["I"][3] == "X" and card["B"][4] == "X":
win = True
elif card["B"][0] == "X" and card["O"][4] == "X" and card["B"][4] == "X" and card["O"][0] == "X":
win = True
for letter in card:
if(len(set(card[letter]))==1):
win = True
for i in range(5):
cnt = 0
for letter in card:
if card[letter][i] == "X":
cnt += 1
print(cnt)
if cnt == 5:
win = True
break
return win
def main():
"""
Main method for the program. Generates a card, prints it, and loops through drawing
and printing until the check_win method returns True or the user enters "quit".
"""
print("Let's play bingo!")
card = generate_card()
print("nHere is your card:n")
print_card(card)
print("nKeep pressing enter to continue or type quit to exit.n")
user_input = input()
win = check_win(card)
balls_till_win = 0
while win == False and user_input != "quit":
number_drawn = draw(card, random_draw_list)
balls_till_win += 1
print(f"nNumber drawn: {number_drawn}.")
print(f"Total balls drawn: {balls_till_win}.n")
print_card(card)
win = check_win(card)
user_input = input()
if win == True:
print(f"nBingo! Total balls to win: {balls_till_win}.")
else:
print("Goodbye! Thanks for playing!")
main()
python game homework
$endgroup$
I am super proud of this python bingo game I wrote. I am posting it here to make get some help reviewing it to make sure I'm using the best practices. This is for a class, and my professor is a stickler for using best practices. I went above and beyond what the assignment called for so I need a little more helping making sure the code is okay. Any help is appreciated!
Here is the code:
# Author: Helana Brock
# Homework 2, problem 3
import random
random_draw_list = random.sample(range(1, 76), 75)
def generate_card():
"""
Generates a bingo card and stores the numbers in a dictionary.
"""
card = {
"B": [],
"I": [],
"N": [],
"G": [],
"O": [],
}
min = 1
max = 15
for letter in card:
card[letter] = random.sample(range(min, max), 5)
min += 15
max += 15
if letter == "N":
card[letter][2] = "X" # free space!
return card
def print_card(card):
"""
Prints the bingo card.
Arguments:
card (dictionary): The card to be printed out.
"""
for letter in card:
print(letter, end="t")
for number in card[letter]:
print(number, end="t")
print("n")
print("n")
def draw(card, list):
"""
Pops a number off a list of random numbers. Using the pop method ensures no duplicate
numbers will be drawn.
Arguments:
card (dictionary): The card to to check for the number that was drawn.
list (list): The list of random numbers to be drawn from.
"""
number_drawn = random_draw_list.pop()
for letter in card:
i = 0
for number in card[letter]:
if number == number_drawn:
card[letter][i] = "X"
i += 1
return number_drawn
def check_win(card):
"""
First checks for diagonal wins, then four-corner, then horizontal, and finally, vertical.
Arguments:
card (dictionary): The card to check for a win.
"""
win = False
if card["B"][0] == "X" and card["I"][1] == "X" and card["N"][2] == "X" and card["G"][3] == "X" and card["O"][4] == "X":
win = True
elif card["O"][0] == "X" and card["G"][1] == "X" and card["N"][2] == "X" and card["I"][3] == "X" and card["B"][4] == "X":
win = True
elif card["B"][0] == "X" and card["O"][4] == "X" and card["B"][4] == "X" and card["O"][0] == "X":
win = True
for letter in card:
if(len(set(card[letter]))==1):
win = True
for i in range(5):
cnt = 0
for letter in card:
if card[letter][i] == "X":
cnt += 1
print(cnt)
if cnt == 5:
win = True
break
return win
def main():
"""
Main method for the program. Generates a card, prints it, and loops through drawing
and printing until the check_win method returns True or the user enters "quit".
"""
print("Let's play bingo!")
card = generate_card()
print("nHere is your card:n")
print_card(card)
print("nKeep pressing enter to continue or type quit to exit.n")
user_input = input()
win = check_win(card)
balls_till_win = 0
while win == False and user_input != "quit":
number_drawn = draw(card, random_draw_list)
balls_till_win += 1
print(f"nNumber drawn: {number_drawn}.")
print(f"Total balls drawn: {balls_till_win}.n")
print_card(card)
win = check_win(card)
user_input = input()
if win == True:
print(f"nBingo! Total balls to win: {balls_till_win}.")
else:
print("Goodbye! Thanks for playing!")
main()
python game homework
python game homework
edited 7 hours ago
AlexV
4,4732 gold badges11 silver badges36 bronze badges
4,4732 gold badges11 silver badges36 bronze badges
asked 8 hours ago
Helana BrockHelana Brock
666 bronze badges
666 bronze badges
add a comment
|
add a comment
|
3 Answers
3
active
oldest
votes
$begingroup$
Some low-hanging fruit:
if win == True
can be written as
if win:
Know that print
delimits by a newline by default; you don't need to explicitly add n
unless you want two newlines.
You can use classes to make card
a class on its own.
$endgroup$
add a comment
|
$begingroup$
Style
Python comes with an "official" Style Guide for Python Code (aka PEP 8) which is a widely accepted standard for naming conventions and the like. Since you say your professor is keen on using best practices, I'd recommend to have a look at those guidelines. There is also a variety of tools that can help you to check for those best practices automatically. A non-exhaustive list (including Pylint) can be found in this meta-post here on Code Review.
From what I have seen, your code looks quite good in that regard. I especially like your docstrings. The most prevalent issue in your code would be that there are usually two blank lines between different function definitions in order to make their separation more clear.
Code
Idiomatic Python
As @nz_21 alread mentioned, if sth == True:
and also if sth == False:
can be expressed in a more idiomatic way as if sth:
and if not sth:
(Note: Pylint would also catch this particular "mistake".)
Redefining built-ins
Some of the variable names you have chosen (min
, max
, list
) redefine built-in functions from the Python core. You should avoid that. The simplest way is to append a single trailing underscore, e.g. list_
, in order to avoid that issue. Also the parameter list
seems to be unused in draw(...)
.
Random number generation
card[letter] = random.sample(range(min_, max_), 5)
could also be rewritten as card[letter] = [random.randrange(min_, max_) for _ in range(5)]
. Although I don't think that it will make a big difference in your case. The documentation explicitly list them as alternatives:
random.randrange(start, stop[, step])
Return a randomly selected element from range(start, stop, step). This
is equivalent to choice(range(start, stop, step)), but doesn’t
actually build a range object.
From that it seems that the later approach would make more sense for a large range of values to choose from.
Global variables
random_draw_list
is used as a global variable, but unfortunately not as a constant as one might expect, but as a mutable global variable. That makes it harder to reuse code. Since draw(...)
is the only function using this variable, it makes no sense to have it as a global variable. Instead, it should defined in main()
after you have changed draw(...)
to accept it as paramter (maybe that's what list
was supposed to do in the first place?) and passed when calling draw(...)
from main()
.
Top-level script environment
In order to show what part of the file is actually supposed to be run as a script, Python defines the concept of a top-level script environment. Although the name sounds daunting at first, it's quite likely that you have seen it in someone else's Python code: if __name__ == "__main__":
This tells the Python interpreter to only run that part of code if the file is used as a script. In your case all there is to do is to do add the above right before calling the main
-function:
if __name__ == "__main__":
main()
That also helps you to avoid the issue that Python always starts a new bingo game whenever you try to import
a function from your file ;-)
$endgroup$
add a comment
|
$begingroup$
Initializing your bingo card
You don't need to initialize your dictionary with lists, since random.sample
returns a list by default. Just iterate through the string "BINGO"
. Also, the if
check doesn't need to happen, you know that you will be replacing the 3rd element of the list at the key 'N'
every time, so just have that be an instruction at the end:
def generate_card():
"""
Generates a bingo card and stores the numbers in a dictionary.
"""
# just start with a plain dictionary, random.sample
# returns a new list, so no need to pre-allocate lists
# you won't be using in the future
card = {}
# min and max are builtin functions, so use
# underscores to avoid name shadowing
_min = 1
_max = 15
for letter in 'BINGO':
card[letter] = random.sample(range(_min, _max), 5)
_min += 15
_max += 15
# You know which letter this needs to be, no need for the if block
card["N"][2] = "X" # free space!
return card
Checking the win condition
To check over the diagonals, you can use zip
for your keys and the indices to validate. For horizontals iterate over card.values()
and check all(item == 'X' for item in row)
. For columns, you can zip together the rows using argument unpacking:
# down to the right
if all(card[k][idx]=='X' for idx, key in enumerate(card)):
print('win!')
return True
# up to the right
elif all(card[k][idx]=='X' for idx, key in enumerate(reversed(card))):
print('win!')
return True
# horizontal condition
for row in card.values():
if all(item=='X' for item in row):
return True
# vertical condition
for column in zip(*card.values()):
if all(item=='X' for item in column):
return True
To show how this works:
import random
d = {k: random.sample(range(10), 3) for k in 'abcd'}
# {'a': [5, 3, 7], 'b': [1, 8, 7], 'c': [4, 5, 8], 'd': [2, 4, 9]}
# rows
for row in d.values():
print(row)
[5, 3, 7]
[1, 8, 7]
[4, 5, 8]
[2, 4, 9]
for col in zip(*d.values()):
print(col)
(5, 1, 4, 2)
(3, 8, 5, 4)
(7, 7, 8, 9)
Also, the return True
on all of the conditions means that the function will stop once a winning condition is hit, rather than going through all of the conditions when you really don't need to.
Looking at this a bit further, I'd probably refactor the row and column check into a different function:
def check_line(values):
"""
values is an iterable over either rows or columns
of the card. Should be iterable(str)
returns boolean
"""
for line in values:
if all(val=='X' for val in values):
return True
# the last two loops then look like:
elif check_line(card.values()):
return True
elif check_line(zip(*card.values())):
return True
return False
Class?
The fact that you're passing card
around to a lot of functions says (to me) that you could probably use a class here. The generate_card
method can be done on __init__
, and everything else takes a self
rather than a card
:
class bingoCard:
def __init__(self, _min=1, _max=15):
"""
Generates a bingo card and stores the numbers in a dictionary.
_min and _max are integers that default to 1 and 15
if a default game is desired
"""
# just start with a plain dictionary, random.sample
# returns a new list, so no need to pre-allocate lists
# you won't be using in the future
self.card = {}
for letter in 'BINGO':
self.card[letter] = random.sample(range(_min, _max), 5)
_min += 15
_max += 15
# You know which letter this needs to be, no need for the if block
self.card["N"][2] = "X" # free space!
# __init__ doesn't return anything
# this makes your card printable
def __repr__(self):
return 'n'.join('t'.join((letter, *map(str, values))) for letter, values in self.card.items())
def draw(self, random_draw_list):
"""
Pops a number off a list of random numbers. Using the pop method ensures no duplicate
numbers will be drawn.
Arguments:
self (dictionary): The card to to check for the number that was drawn.
list (list): The list of random numbers to be drawn from.
"""
number_drawn = random_draw_list.pop()
for letter in self.card:
# don't track an index here, use enumerate
for i, number in enumerate(self.card[letter]):
if number == number_drawn:
self.card[letter][i] = "X"
return number_drawn
def check_win(self):
# down to the right
if all(self.card[k][idx]=='X' for idx, key in enumerate(self.card)):
print('win!')
return True
# up to the right
elif all(self.card[k][idx]=='X' for idx, key in enumerate(reversed(self.card))):
print('win!')
return True
# horizontal condition
elif self.check_line(self.card.values()):
return True
# vertical condition
elif self.check_line(zip(*self.card.values())):
return True
return False
@staticmethod
def check_line(values):
"""
values is an iterable over either rows or columns
of the card. Should be iterable(str)
returns boolean
"""
for line in values:
if all(val=='X' for val in values):
return True
# then, your bingo card is an instance of this class
card = bingoCard()
card.check_win()
# False
random_draw_list
To make this work, I'd add it as a condition on your while
loop. You pop
elements off of it, but what happens when there's nothing else to pop
? You will get an IndexError
for attempting to pop
from an empty list. To make the game re-playable, I'd have the user choose to re-play, and generate that new list on each play, just as you re-create the bingo card on each play:
def generate_random_list():
return random.sample(range(1, 76), 75)
def main():
card = bingoCard()
# generate the draw list here, on each play of the game
# that way if you run out of balls, you can handle that by restarting
# the game
random_draw_list = generate_random_list()
print(f"This is your card:n {card}")
# you don't need the user input here, I think it might
# be better to include it elsewhere, before generating the
# card. Instead, keep going while there are numbers to draw
while random_draw_list:
number_drawn = card.draw(random_draw_list)
# this variable is better named balls_drawn
balls_drawn += 1
print(f"You drew {number_drawn}")
print(f"Total balls drawn: {balls_drawn}")
if check_win(card):
print(f"You won after drawing {balls_drawn} balls!")
break
# if you were to check for user input during the game,
# it would make more sense to do that after you've at least
# checked one round
keep_playing = input("Keep playing? (y/n)").strip().lower()
if keep_playing == 'n':
print("Ok, thanks for playing")
break
else:
print("Sorry, there are no more balls to draw")
# This is where you prompt the user to play:
if __name__ == "__main__":
while True:
play = input("Would you like to play bingo? (y/n)")
if play.strip().lower() == 'y':
main()
else:
break
$endgroup$
add a comment
|
Your Answer
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',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/4.0/"u003ecc by-sa 4.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
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
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f229872%2fpython-bingo-game-that-stores-card-in-a-dictionary%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
3 Answers
3
active
oldest
votes
3 Answers
3
active
oldest
votes
active
oldest
votes
active
oldest
votes
$begingroup$
Some low-hanging fruit:
if win == True
can be written as
if win:
Know that print
delimits by a newline by default; you don't need to explicitly add n
unless you want two newlines.
You can use classes to make card
a class on its own.
$endgroup$
add a comment
|
$begingroup$
Some low-hanging fruit:
if win == True
can be written as
if win:
Know that print
delimits by a newline by default; you don't need to explicitly add n
unless you want two newlines.
You can use classes to make card
a class on its own.
$endgroup$
add a comment
|
$begingroup$
Some low-hanging fruit:
if win == True
can be written as
if win:
Know that print
delimits by a newline by default; you don't need to explicitly add n
unless you want two newlines.
You can use classes to make card
a class on its own.
$endgroup$
Some low-hanging fruit:
if win == True
can be written as
if win:
Know that print
delimits by a newline by default; you don't need to explicitly add n
unless you want two newlines.
You can use classes to make card
a class on its own.
answered 7 hours ago
nz_21nz_21
2431 silver badge9 bronze badges
2431 silver badge9 bronze badges
add a comment
|
add a comment
|
$begingroup$
Style
Python comes with an "official" Style Guide for Python Code (aka PEP 8) which is a widely accepted standard for naming conventions and the like. Since you say your professor is keen on using best practices, I'd recommend to have a look at those guidelines. There is also a variety of tools that can help you to check for those best practices automatically. A non-exhaustive list (including Pylint) can be found in this meta-post here on Code Review.
From what I have seen, your code looks quite good in that regard. I especially like your docstrings. The most prevalent issue in your code would be that there are usually two blank lines between different function definitions in order to make their separation more clear.
Code
Idiomatic Python
As @nz_21 alread mentioned, if sth == True:
and also if sth == False:
can be expressed in a more idiomatic way as if sth:
and if not sth:
(Note: Pylint would also catch this particular "mistake".)
Redefining built-ins
Some of the variable names you have chosen (min
, max
, list
) redefine built-in functions from the Python core. You should avoid that. The simplest way is to append a single trailing underscore, e.g. list_
, in order to avoid that issue. Also the parameter list
seems to be unused in draw(...)
.
Random number generation
card[letter] = random.sample(range(min_, max_), 5)
could also be rewritten as card[letter] = [random.randrange(min_, max_) for _ in range(5)]
. Although I don't think that it will make a big difference in your case. The documentation explicitly list them as alternatives:
random.randrange(start, stop[, step])
Return a randomly selected element from range(start, stop, step). This
is equivalent to choice(range(start, stop, step)), but doesn’t
actually build a range object.
From that it seems that the later approach would make more sense for a large range of values to choose from.
Global variables
random_draw_list
is used as a global variable, but unfortunately not as a constant as one might expect, but as a mutable global variable. That makes it harder to reuse code. Since draw(...)
is the only function using this variable, it makes no sense to have it as a global variable. Instead, it should defined in main()
after you have changed draw(...)
to accept it as paramter (maybe that's what list
was supposed to do in the first place?) and passed when calling draw(...)
from main()
.
Top-level script environment
In order to show what part of the file is actually supposed to be run as a script, Python defines the concept of a top-level script environment. Although the name sounds daunting at first, it's quite likely that you have seen it in someone else's Python code: if __name__ == "__main__":
This tells the Python interpreter to only run that part of code if the file is used as a script. In your case all there is to do is to do add the above right before calling the main
-function:
if __name__ == "__main__":
main()
That also helps you to avoid the issue that Python always starts a new bingo game whenever you try to import
a function from your file ;-)
$endgroup$
add a comment
|
$begingroup$
Style
Python comes with an "official" Style Guide for Python Code (aka PEP 8) which is a widely accepted standard for naming conventions and the like. Since you say your professor is keen on using best practices, I'd recommend to have a look at those guidelines. There is also a variety of tools that can help you to check for those best practices automatically. A non-exhaustive list (including Pylint) can be found in this meta-post here on Code Review.
From what I have seen, your code looks quite good in that regard. I especially like your docstrings. The most prevalent issue in your code would be that there are usually two blank lines between different function definitions in order to make their separation more clear.
Code
Idiomatic Python
As @nz_21 alread mentioned, if sth == True:
and also if sth == False:
can be expressed in a more idiomatic way as if sth:
and if not sth:
(Note: Pylint would also catch this particular "mistake".)
Redefining built-ins
Some of the variable names you have chosen (min
, max
, list
) redefine built-in functions from the Python core. You should avoid that. The simplest way is to append a single trailing underscore, e.g. list_
, in order to avoid that issue. Also the parameter list
seems to be unused in draw(...)
.
Random number generation
card[letter] = random.sample(range(min_, max_), 5)
could also be rewritten as card[letter] = [random.randrange(min_, max_) for _ in range(5)]
. Although I don't think that it will make a big difference in your case. The documentation explicitly list them as alternatives:
random.randrange(start, stop[, step])
Return a randomly selected element from range(start, stop, step). This
is equivalent to choice(range(start, stop, step)), but doesn’t
actually build a range object.
From that it seems that the later approach would make more sense for a large range of values to choose from.
Global variables
random_draw_list
is used as a global variable, but unfortunately not as a constant as one might expect, but as a mutable global variable. That makes it harder to reuse code. Since draw(...)
is the only function using this variable, it makes no sense to have it as a global variable. Instead, it should defined in main()
after you have changed draw(...)
to accept it as paramter (maybe that's what list
was supposed to do in the first place?) and passed when calling draw(...)
from main()
.
Top-level script environment
In order to show what part of the file is actually supposed to be run as a script, Python defines the concept of a top-level script environment. Although the name sounds daunting at first, it's quite likely that you have seen it in someone else's Python code: if __name__ == "__main__":
This tells the Python interpreter to only run that part of code if the file is used as a script. In your case all there is to do is to do add the above right before calling the main
-function:
if __name__ == "__main__":
main()
That also helps you to avoid the issue that Python always starts a new bingo game whenever you try to import
a function from your file ;-)
$endgroup$
add a comment
|
$begingroup$
Style
Python comes with an "official" Style Guide for Python Code (aka PEP 8) which is a widely accepted standard for naming conventions and the like. Since you say your professor is keen on using best practices, I'd recommend to have a look at those guidelines. There is also a variety of tools that can help you to check for those best practices automatically. A non-exhaustive list (including Pylint) can be found in this meta-post here on Code Review.
From what I have seen, your code looks quite good in that regard. I especially like your docstrings. The most prevalent issue in your code would be that there are usually two blank lines between different function definitions in order to make their separation more clear.
Code
Idiomatic Python
As @nz_21 alread mentioned, if sth == True:
and also if sth == False:
can be expressed in a more idiomatic way as if sth:
and if not sth:
(Note: Pylint would also catch this particular "mistake".)
Redefining built-ins
Some of the variable names you have chosen (min
, max
, list
) redefine built-in functions from the Python core. You should avoid that. The simplest way is to append a single trailing underscore, e.g. list_
, in order to avoid that issue. Also the parameter list
seems to be unused in draw(...)
.
Random number generation
card[letter] = random.sample(range(min_, max_), 5)
could also be rewritten as card[letter] = [random.randrange(min_, max_) for _ in range(5)]
. Although I don't think that it will make a big difference in your case. The documentation explicitly list them as alternatives:
random.randrange(start, stop[, step])
Return a randomly selected element from range(start, stop, step). This
is equivalent to choice(range(start, stop, step)), but doesn’t
actually build a range object.
From that it seems that the later approach would make more sense for a large range of values to choose from.
Global variables
random_draw_list
is used as a global variable, but unfortunately not as a constant as one might expect, but as a mutable global variable. That makes it harder to reuse code. Since draw(...)
is the only function using this variable, it makes no sense to have it as a global variable. Instead, it should defined in main()
after you have changed draw(...)
to accept it as paramter (maybe that's what list
was supposed to do in the first place?) and passed when calling draw(...)
from main()
.
Top-level script environment
In order to show what part of the file is actually supposed to be run as a script, Python defines the concept of a top-level script environment. Although the name sounds daunting at first, it's quite likely that you have seen it in someone else's Python code: if __name__ == "__main__":
This tells the Python interpreter to only run that part of code if the file is used as a script. In your case all there is to do is to do add the above right before calling the main
-function:
if __name__ == "__main__":
main()
That also helps you to avoid the issue that Python always starts a new bingo game whenever you try to import
a function from your file ;-)
$endgroup$
Style
Python comes with an "official" Style Guide for Python Code (aka PEP 8) which is a widely accepted standard for naming conventions and the like. Since you say your professor is keen on using best practices, I'd recommend to have a look at those guidelines. There is also a variety of tools that can help you to check for those best practices automatically. A non-exhaustive list (including Pylint) can be found in this meta-post here on Code Review.
From what I have seen, your code looks quite good in that regard. I especially like your docstrings. The most prevalent issue in your code would be that there are usually two blank lines between different function definitions in order to make their separation more clear.
Code
Idiomatic Python
As @nz_21 alread mentioned, if sth == True:
and also if sth == False:
can be expressed in a more idiomatic way as if sth:
and if not sth:
(Note: Pylint would also catch this particular "mistake".)
Redefining built-ins
Some of the variable names you have chosen (min
, max
, list
) redefine built-in functions from the Python core. You should avoid that. The simplest way is to append a single trailing underscore, e.g. list_
, in order to avoid that issue. Also the parameter list
seems to be unused in draw(...)
.
Random number generation
card[letter] = random.sample(range(min_, max_), 5)
could also be rewritten as card[letter] = [random.randrange(min_, max_) for _ in range(5)]
. Although I don't think that it will make a big difference in your case. The documentation explicitly list them as alternatives:
random.randrange(start, stop[, step])
Return a randomly selected element from range(start, stop, step). This
is equivalent to choice(range(start, stop, step)), but doesn’t
actually build a range object.
From that it seems that the later approach would make more sense for a large range of values to choose from.
Global variables
random_draw_list
is used as a global variable, but unfortunately not as a constant as one might expect, but as a mutable global variable. That makes it harder to reuse code. Since draw(...)
is the only function using this variable, it makes no sense to have it as a global variable. Instead, it should defined in main()
after you have changed draw(...)
to accept it as paramter (maybe that's what list
was supposed to do in the first place?) and passed when calling draw(...)
from main()
.
Top-level script environment
In order to show what part of the file is actually supposed to be run as a script, Python defines the concept of a top-level script environment. Although the name sounds daunting at first, it's quite likely that you have seen it in someone else's Python code: if __name__ == "__main__":
This tells the Python interpreter to only run that part of code if the file is used as a script. In your case all there is to do is to do add the above right before calling the main
-function:
if __name__ == "__main__":
main()
That also helps you to avoid the issue that Python always starts a new bingo game whenever you try to import
a function from your file ;-)
answered 6 hours ago
AlexVAlexV
4,4732 gold badges11 silver badges36 bronze badges
4,4732 gold badges11 silver badges36 bronze badges
add a comment
|
add a comment
|
$begingroup$
Initializing your bingo card
You don't need to initialize your dictionary with lists, since random.sample
returns a list by default. Just iterate through the string "BINGO"
. Also, the if
check doesn't need to happen, you know that you will be replacing the 3rd element of the list at the key 'N'
every time, so just have that be an instruction at the end:
def generate_card():
"""
Generates a bingo card and stores the numbers in a dictionary.
"""
# just start with a plain dictionary, random.sample
# returns a new list, so no need to pre-allocate lists
# you won't be using in the future
card = {}
# min and max are builtin functions, so use
# underscores to avoid name shadowing
_min = 1
_max = 15
for letter in 'BINGO':
card[letter] = random.sample(range(_min, _max), 5)
_min += 15
_max += 15
# You know which letter this needs to be, no need for the if block
card["N"][2] = "X" # free space!
return card
Checking the win condition
To check over the diagonals, you can use zip
for your keys and the indices to validate. For horizontals iterate over card.values()
and check all(item == 'X' for item in row)
. For columns, you can zip together the rows using argument unpacking:
# down to the right
if all(card[k][idx]=='X' for idx, key in enumerate(card)):
print('win!')
return True
# up to the right
elif all(card[k][idx]=='X' for idx, key in enumerate(reversed(card))):
print('win!')
return True
# horizontal condition
for row in card.values():
if all(item=='X' for item in row):
return True
# vertical condition
for column in zip(*card.values()):
if all(item=='X' for item in column):
return True
To show how this works:
import random
d = {k: random.sample(range(10), 3) for k in 'abcd'}
# {'a': [5, 3, 7], 'b': [1, 8, 7], 'c': [4, 5, 8], 'd': [2, 4, 9]}
# rows
for row in d.values():
print(row)
[5, 3, 7]
[1, 8, 7]
[4, 5, 8]
[2, 4, 9]
for col in zip(*d.values()):
print(col)
(5, 1, 4, 2)
(3, 8, 5, 4)
(7, 7, 8, 9)
Also, the return True
on all of the conditions means that the function will stop once a winning condition is hit, rather than going through all of the conditions when you really don't need to.
Looking at this a bit further, I'd probably refactor the row and column check into a different function:
def check_line(values):
"""
values is an iterable over either rows or columns
of the card. Should be iterable(str)
returns boolean
"""
for line in values:
if all(val=='X' for val in values):
return True
# the last two loops then look like:
elif check_line(card.values()):
return True
elif check_line(zip(*card.values())):
return True
return False
Class?
The fact that you're passing card
around to a lot of functions says (to me) that you could probably use a class here. The generate_card
method can be done on __init__
, and everything else takes a self
rather than a card
:
class bingoCard:
def __init__(self, _min=1, _max=15):
"""
Generates a bingo card and stores the numbers in a dictionary.
_min and _max are integers that default to 1 and 15
if a default game is desired
"""
# just start with a plain dictionary, random.sample
# returns a new list, so no need to pre-allocate lists
# you won't be using in the future
self.card = {}
for letter in 'BINGO':
self.card[letter] = random.sample(range(_min, _max), 5)
_min += 15
_max += 15
# You know which letter this needs to be, no need for the if block
self.card["N"][2] = "X" # free space!
# __init__ doesn't return anything
# this makes your card printable
def __repr__(self):
return 'n'.join('t'.join((letter, *map(str, values))) for letter, values in self.card.items())
def draw(self, random_draw_list):
"""
Pops a number off a list of random numbers. Using the pop method ensures no duplicate
numbers will be drawn.
Arguments:
self (dictionary): The card to to check for the number that was drawn.
list (list): The list of random numbers to be drawn from.
"""
number_drawn = random_draw_list.pop()
for letter in self.card:
# don't track an index here, use enumerate
for i, number in enumerate(self.card[letter]):
if number == number_drawn:
self.card[letter][i] = "X"
return number_drawn
def check_win(self):
# down to the right
if all(self.card[k][idx]=='X' for idx, key in enumerate(self.card)):
print('win!')
return True
# up to the right
elif all(self.card[k][idx]=='X' for idx, key in enumerate(reversed(self.card))):
print('win!')
return True
# horizontal condition
elif self.check_line(self.card.values()):
return True
# vertical condition
elif self.check_line(zip(*self.card.values())):
return True
return False
@staticmethod
def check_line(values):
"""
values is an iterable over either rows or columns
of the card. Should be iterable(str)
returns boolean
"""
for line in values:
if all(val=='X' for val in values):
return True
# then, your bingo card is an instance of this class
card = bingoCard()
card.check_win()
# False
random_draw_list
To make this work, I'd add it as a condition on your while
loop. You pop
elements off of it, but what happens when there's nothing else to pop
? You will get an IndexError
for attempting to pop
from an empty list. To make the game re-playable, I'd have the user choose to re-play, and generate that new list on each play, just as you re-create the bingo card on each play:
def generate_random_list():
return random.sample(range(1, 76), 75)
def main():
card = bingoCard()
# generate the draw list here, on each play of the game
# that way if you run out of balls, you can handle that by restarting
# the game
random_draw_list = generate_random_list()
print(f"This is your card:n {card}")
# you don't need the user input here, I think it might
# be better to include it elsewhere, before generating the
# card. Instead, keep going while there are numbers to draw
while random_draw_list:
number_drawn = card.draw(random_draw_list)
# this variable is better named balls_drawn
balls_drawn += 1
print(f"You drew {number_drawn}")
print(f"Total balls drawn: {balls_drawn}")
if check_win(card):
print(f"You won after drawing {balls_drawn} balls!")
break
# if you were to check for user input during the game,
# it would make more sense to do that after you've at least
# checked one round
keep_playing = input("Keep playing? (y/n)").strip().lower()
if keep_playing == 'n':
print("Ok, thanks for playing")
break
else:
print("Sorry, there are no more balls to draw")
# This is where you prompt the user to play:
if __name__ == "__main__":
while True:
play = input("Would you like to play bingo? (y/n)")
if play.strip().lower() == 'y':
main()
else:
break
$endgroup$
add a comment
|
$begingroup$
Initializing your bingo card
You don't need to initialize your dictionary with lists, since random.sample
returns a list by default. Just iterate through the string "BINGO"
. Also, the if
check doesn't need to happen, you know that you will be replacing the 3rd element of the list at the key 'N'
every time, so just have that be an instruction at the end:
def generate_card():
"""
Generates a bingo card and stores the numbers in a dictionary.
"""
# just start with a plain dictionary, random.sample
# returns a new list, so no need to pre-allocate lists
# you won't be using in the future
card = {}
# min and max are builtin functions, so use
# underscores to avoid name shadowing
_min = 1
_max = 15
for letter in 'BINGO':
card[letter] = random.sample(range(_min, _max), 5)
_min += 15
_max += 15
# You know which letter this needs to be, no need for the if block
card["N"][2] = "X" # free space!
return card
Checking the win condition
To check over the diagonals, you can use zip
for your keys and the indices to validate. For horizontals iterate over card.values()
and check all(item == 'X' for item in row)
. For columns, you can zip together the rows using argument unpacking:
# down to the right
if all(card[k][idx]=='X' for idx, key in enumerate(card)):
print('win!')
return True
# up to the right
elif all(card[k][idx]=='X' for idx, key in enumerate(reversed(card))):
print('win!')
return True
# horizontal condition
for row in card.values():
if all(item=='X' for item in row):
return True
# vertical condition
for column in zip(*card.values()):
if all(item=='X' for item in column):
return True
To show how this works:
import random
d = {k: random.sample(range(10), 3) for k in 'abcd'}
# {'a': [5, 3, 7], 'b': [1, 8, 7], 'c': [4, 5, 8], 'd': [2, 4, 9]}
# rows
for row in d.values():
print(row)
[5, 3, 7]
[1, 8, 7]
[4, 5, 8]
[2, 4, 9]
for col in zip(*d.values()):
print(col)
(5, 1, 4, 2)
(3, 8, 5, 4)
(7, 7, 8, 9)
Also, the return True
on all of the conditions means that the function will stop once a winning condition is hit, rather than going through all of the conditions when you really don't need to.
Looking at this a bit further, I'd probably refactor the row and column check into a different function:
def check_line(values):
"""
values is an iterable over either rows or columns
of the card. Should be iterable(str)
returns boolean
"""
for line in values:
if all(val=='X' for val in values):
return True
# the last two loops then look like:
elif check_line(card.values()):
return True
elif check_line(zip(*card.values())):
return True
return False
Class?
The fact that you're passing card
around to a lot of functions says (to me) that you could probably use a class here. The generate_card
method can be done on __init__
, and everything else takes a self
rather than a card
:
class bingoCard:
def __init__(self, _min=1, _max=15):
"""
Generates a bingo card and stores the numbers in a dictionary.
_min and _max are integers that default to 1 and 15
if a default game is desired
"""
# just start with a plain dictionary, random.sample
# returns a new list, so no need to pre-allocate lists
# you won't be using in the future
self.card = {}
for letter in 'BINGO':
self.card[letter] = random.sample(range(_min, _max), 5)
_min += 15
_max += 15
# You know which letter this needs to be, no need for the if block
self.card["N"][2] = "X" # free space!
# __init__ doesn't return anything
# this makes your card printable
def __repr__(self):
return 'n'.join('t'.join((letter, *map(str, values))) for letter, values in self.card.items())
def draw(self, random_draw_list):
"""
Pops a number off a list of random numbers. Using the pop method ensures no duplicate
numbers will be drawn.
Arguments:
self (dictionary): The card to to check for the number that was drawn.
list (list): The list of random numbers to be drawn from.
"""
number_drawn = random_draw_list.pop()
for letter in self.card:
# don't track an index here, use enumerate
for i, number in enumerate(self.card[letter]):
if number == number_drawn:
self.card[letter][i] = "X"
return number_drawn
def check_win(self):
# down to the right
if all(self.card[k][idx]=='X' for idx, key in enumerate(self.card)):
print('win!')
return True
# up to the right
elif all(self.card[k][idx]=='X' for idx, key in enumerate(reversed(self.card))):
print('win!')
return True
# horizontal condition
elif self.check_line(self.card.values()):
return True
# vertical condition
elif self.check_line(zip(*self.card.values())):
return True
return False
@staticmethod
def check_line(values):
"""
values is an iterable over either rows or columns
of the card. Should be iterable(str)
returns boolean
"""
for line in values:
if all(val=='X' for val in values):
return True
# then, your bingo card is an instance of this class
card = bingoCard()
card.check_win()
# False
random_draw_list
To make this work, I'd add it as a condition on your while
loop. You pop
elements off of it, but what happens when there's nothing else to pop
? You will get an IndexError
for attempting to pop
from an empty list. To make the game re-playable, I'd have the user choose to re-play, and generate that new list on each play, just as you re-create the bingo card on each play:
def generate_random_list():
return random.sample(range(1, 76), 75)
def main():
card = bingoCard()
# generate the draw list here, on each play of the game
# that way if you run out of balls, you can handle that by restarting
# the game
random_draw_list = generate_random_list()
print(f"This is your card:n {card}")
# you don't need the user input here, I think it might
# be better to include it elsewhere, before generating the
# card. Instead, keep going while there are numbers to draw
while random_draw_list:
number_drawn = card.draw(random_draw_list)
# this variable is better named balls_drawn
balls_drawn += 1
print(f"You drew {number_drawn}")
print(f"Total balls drawn: {balls_drawn}")
if check_win(card):
print(f"You won after drawing {balls_drawn} balls!")
break
# if you were to check for user input during the game,
# it would make more sense to do that after you've at least
# checked one round
keep_playing = input("Keep playing? (y/n)").strip().lower()
if keep_playing == 'n':
print("Ok, thanks for playing")
break
else:
print("Sorry, there are no more balls to draw")
# This is where you prompt the user to play:
if __name__ == "__main__":
while True:
play = input("Would you like to play bingo? (y/n)")
if play.strip().lower() == 'y':
main()
else:
break
$endgroup$
add a comment
|
$begingroup$
Initializing your bingo card
You don't need to initialize your dictionary with lists, since random.sample
returns a list by default. Just iterate through the string "BINGO"
. Also, the if
check doesn't need to happen, you know that you will be replacing the 3rd element of the list at the key 'N'
every time, so just have that be an instruction at the end:
def generate_card():
"""
Generates a bingo card and stores the numbers in a dictionary.
"""
# just start with a plain dictionary, random.sample
# returns a new list, so no need to pre-allocate lists
# you won't be using in the future
card = {}
# min and max are builtin functions, so use
# underscores to avoid name shadowing
_min = 1
_max = 15
for letter in 'BINGO':
card[letter] = random.sample(range(_min, _max), 5)
_min += 15
_max += 15
# You know which letter this needs to be, no need for the if block
card["N"][2] = "X" # free space!
return card
Checking the win condition
To check over the diagonals, you can use zip
for your keys and the indices to validate. For horizontals iterate over card.values()
and check all(item == 'X' for item in row)
. For columns, you can zip together the rows using argument unpacking:
# down to the right
if all(card[k][idx]=='X' for idx, key in enumerate(card)):
print('win!')
return True
# up to the right
elif all(card[k][idx]=='X' for idx, key in enumerate(reversed(card))):
print('win!')
return True
# horizontal condition
for row in card.values():
if all(item=='X' for item in row):
return True
# vertical condition
for column in zip(*card.values()):
if all(item=='X' for item in column):
return True
To show how this works:
import random
d = {k: random.sample(range(10), 3) for k in 'abcd'}
# {'a': [5, 3, 7], 'b': [1, 8, 7], 'c': [4, 5, 8], 'd': [2, 4, 9]}
# rows
for row in d.values():
print(row)
[5, 3, 7]
[1, 8, 7]
[4, 5, 8]
[2, 4, 9]
for col in zip(*d.values()):
print(col)
(5, 1, 4, 2)
(3, 8, 5, 4)
(7, 7, 8, 9)
Also, the return True
on all of the conditions means that the function will stop once a winning condition is hit, rather than going through all of the conditions when you really don't need to.
Looking at this a bit further, I'd probably refactor the row and column check into a different function:
def check_line(values):
"""
values is an iterable over either rows or columns
of the card. Should be iterable(str)
returns boolean
"""
for line in values:
if all(val=='X' for val in values):
return True
# the last two loops then look like:
elif check_line(card.values()):
return True
elif check_line(zip(*card.values())):
return True
return False
Class?
The fact that you're passing card
around to a lot of functions says (to me) that you could probably use a class here. The generate_card
method can be done on __init__
, and everything else takes a self
rather than a card
:
class bingoCard:
def __init__(self, _min=1, _max=15):
"""
Generates a bingo card and stores the numbers in a dictionary.
_min and _max are integers that default to 1 and 15
if a default game is desired
"""
# just start with a plain dictionary, random.sample
# returns a new list, so no need to pre-allocate lists
# you won't be using in the future
self.card = {}
for letter in 'BINGO':
self.card[letter] = random.sample(range(_min, _max), 5)
_min += 15
_max += 15
# You know which letter this needs to be, no need for the if block
self.card["N"][2] = "X" # free space!
# __init__ doesn't return anything
# this makes your card printable
def __repr__(self):
return 'n'.join('t'.join((letter, *map(str, values))) for letter, values in self.card.items())
def draw(self, random_draw_list):
"""
Pops a number off a list of random numbers. Using the pop method ensures no duplicate
numbers will be drawn.
Arguments:
self (dictionary): The card to to check for the number that was drawn.
list (list): The list of random numbers to be drawn from.
"""
number_drawn = random_draw_list.pop()
for letter in self.card:
# don't track an index here, use enumerate
for i, number in enumerate(self.card[letter]):
if number == number_drawn:
self.card[letter][i] = "X"
return number_drawn
def check_win(self):
# down to the right
if all(self.card[k][idx]=='X' for idx, key in enumerate(self.card)):
print('win!')
return True
# up to the right
elif all(self.card[k][idx]=='X' for idx, key in enumerate(reversed(self.card))):
print('win!')
return True
# horizontal condition
elif self.check_line(self.card.values()):
return True
# vertical condition
elif self.check_line(zip(*self.card.values())):
return True
return False
@staticmethod
def check_line(values):
"""
values is an iterable over either rows or columns
of the card. Should be iterable(str)
returns boolean
"""
for line in values:
if all(val=='X' for val in values):
return True
# then, your bingo card is an instance of this class
card = bingoCard()
card.check_win()
# False
random_draw_list
To make this work, I'd add it as a condition on your while
loop. You pop
elements off of it, but what happens when there's nothing else to pop
? You will get an IndexError
for attempting to pop
from an empty list. To make the game re-playable, I'd have the user choose to re-play, and generate that new list on each play, just as you re-create the bingo card on each play:
def generate_random_list():
return random.sample(range(1, 76), 75)
def main():
card = bingoCard()
# generate the draw list here, on each play of the game
# that way if you run out of balls, you can handle that by restarting
# the game
random_draw_list = generate_random_list()
print(f"This is your card:n {card}")
# you don't need the user input here, I think it might
# be better to include it elsewhere, before generating the
# card. Instead, keep going while there are numbers to draw
while random_draw_list:
number_drawn = card.draw(random_draw_list)
# this variable is better named balls_drawn
balls_drawn += 1
print(f"You drew {number_drawn}")
print(f"Total balls drawn: {balls_drawn}")
if check_win(card):
print(f"You won after drawing {balls_drawn} balls!")
break
# if you were to check for user input during the game,
# it would make more sense to do that after you've at least
# checked one round
keep_playing = input("Keep playing? (y/n)").strip().lower()
if keep_playing == 'n':
print("Ok, thanks for playing")
break
else:
print("Sorry, there are no more balls to draw")
# This is where you prompt the user to play:
if __name__ == "__main__":
while True:
play = input("Would you like to play bingo? (y/n)")
if play.strip().lower() == 'y':
main()
else:
break
$endgroup$
Initializing your bingo card
You don't need to initialize your dictionary with lists, since random.sample
returns a list by default. Just iterate through the string "BINGO"
. Also, the if
check doesn't need to happen, you know that you will be replacing the 3rd element of the list at the key 'N'
every time, so just have that be an instruction at the end:
def generate_card():
"""
Generates a bingo card and stores the numbers in a dictionary.
"""
# just start with a plain dictionary, random.sample
# returns a new list, so no need to pre-allocate lists
# you won't be using in the future
card = {}
# min and max are builtin functions, so use
# underscores to avoid name shadowing
_min = 1
_max = 15
for letter in 'BINGO':
card[letter] = random.sample(range(_min, _max), 5)
_min += 15
_max += 15
# You know which letter this needs to be, no need for the if block
card["N"][2] = "X" # free space!
return card
Checking the win condition
To check over the diagonals, you can use zip
for your keys and the indices to validate. For horizontals iterate over card.values()
and check all(item == 'X' for item in row)
. For columns, you can zip together the rows using argument unpacking:
# down to the right
if all(card[k][idx]=='X' for idx, key in enumerate(card)):
print('win!')
return True
# up to the right
elif all(card[k][idx]=='X' for idx, key in enumerate(reversed(card))):
print('win!')
return True
# horizontal condition
for row in card.values():
if all(item=='X' for item in row):
return True
# vertical condition
for column in zip(*card.values()):
if all(item=='X' for item in column):
return True
To show how this works:
import random
d = {k: random.sample(range(10), 3) for k in 'abcd'}
# {'a': [5, 3, 7], 'b': [1, 8, 7], 'c': [4, 5, 8], 'd': [2, 4, 9]}
# rows
for row in d.values():
print(row)
[5, 3, 7]
[1, 8, 7]
[4, 5, 8]
[2, 4, 9]
for col in zip(*d.values()):
print(col)
(5, 1, 4, 2)
(3, 8, 5, 4)
(7, 7, 8, 9)
Also, the return True
on all of the conditions means that the function will stop once a winning condition is hit, rather than going through all of the conditions when you really don't need to.
Looking at this a bit further, I'd probably refactor the row and column check into a different function:
def check_line(values):
"""
values is an iterable over either rows or columns
of the card. Should be iterable(str)
returns boolean
"""
for line in values:
if all(val=='X' for val in values):
return True
# the last two loops then look like:
elif check_line(card.values()):
return True
elif check_line(zip(*card.values())):
return True
return False
Class?
The fact that you're passing card
around to a lot of functions says (to me) that you could probably use a class here. The generate_card
method can be done on __init__
, and everything else takes a self
rather than a card
:
class bingoCard:
def __init__(self, _min=1, _max=15):
"""
Generates a bingo card and stores the numbers in a dictionary.
_min and _max are integers that default to 1 and 15
if a default game is desired
"""
# just start with a plain dictionary, random.sample
# returns a new list, so no need to pre-allocate lists
# you won't be using in the future
self.card = {}
for letter in 'BINGO':
self.card[letter] = random.sample(range(_min, _max), 5)
_min += 15
_max += 15
# You know which letter this needs to be, no need for the if block
self.card["N"][2] = "X" # free space!
# __init__ doesn't return anything
# this makes your card printable
def __repr__(self):
return 'n'.join('t'.join((letter, *map(str, values))) for letter, values in self.card.items())
def draw(self, random_draw_list):
"""
Pops a number off a list of random numbers. Using the pop method ensures no duplicate
numbers will be drawn.
Arguments:
self (dictionary): The card to to check for the number that was drawn.
list (list): The list of random numbers to be drawn from.
"""
number_drawn = random_draw_list.pop()
for letter in self.card:
# don't track an index here, use enumerate
for i, number in enumerate(self.card[letter]):
if number == number_drawn:
self.card[letter][i] = "X"
return number_drawn
def check_win(self):
# down to the right
if all(self.card[k][idx]=='X' for idx, key in enumerate(self.card)):
print('win!')
return True
# up to the right
elif all(self.card[k][idx]=='X' for idx, key in enumerate(reversed(self.card))):
print('win!')
return True
# horizontal condition
elif self.check_line(self.card.values()):
return True
# vertical condition
elif self.check_line(zip(*self.card.values())):
return True
return False
@staticmethod
def check_line(values):
"""
values is an iterable over either rows or columns
of the card. Should be iterable(str)
returns boolean
"""
for line in values:
if all(val=='X' for val in values):
return True
# then, your bingo card is an instance of this class
card = bingoCard()
card.check_win()
# False
random_draw_list
To make this work, I'd add it as a condition on your while
loop. You pop
elements off of it, but what happens when there's nothing else to pop
? You will get an IndexError
for attempting to pop
from an empty list. To make the game re-playable, I'd have the user choose to re-play, and generate that new list on each play, just as you re-create the bingo card on each play:
def generate_random_list():
return random.sample(range(1, 76), 75)
def main():
card = bingoCard()
# generate the draw list here, on each play of the game
# that way if you run out of balls, you can handle that by restarting
# the game
random_draw_list = generate_random_list()
print(f"This is your card:n {card}")
# you don't need the user input here, I think it might
# be better to include it elsewhere, before generating the
# card. Instead, keep going while there are numbers to draw
while random_draw_list:
number_drawn = card.draw(random_draw_list)
# this variable is better named balls_drawn
balls_drawn += 1
print(f"You drew {number_drawn}")
print(f"Total balls drawn: {balls_drawn}")
if check_win(card):
print(f"You won after drawing {balls_drawn} balls!")
break
# if you were to check for user input during the game,
# it would make more sense to do that after you've at least
# checked one round
keep_playing = input("Keep playing? (y/n)").strip().lower()
if keep_playing == 'n':
print("Ok, thanks for playing")
break
else:
print("Sorry, there are no more balls to draw")
# This is where you prompt the user to play:
if __name__ == "__main__":
while True:
play = input("Would you like to play bingo? (y/n)")
if play.strip().lower() == 'y':
main()
else:
break
edited 5 hours ago
answered 6 hours ago
C.NivsC.Nivs
3761 silver badge8 bronze badges
3761 silver badge8 bronze badges
add a comment
|
add a comment
|
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
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
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f229872%2fpython-bingo-game-that-stores-card-in-a-dictionary%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
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
Required, but never shown
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
Required, but never shown
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
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown