I've been learning Python for a couple of months, and I decided to have a go at a small project that I felt able to complete on my own: a version of the card game UNO, using PyGame for the graphical interface. I've settled with a version that mostly works. I've had a great time building it, and certainly my child of 4 has a great time playing it :) It's nothing fancy: a two-player game against a dumb AI, but I guess it's nice for a start.
Now, the code is over 600 lines long, and as I was adding features, I knew there were far better ways to write it, some of which I won't have even heard of, but I wanted to get as far as possible. So I would be pleased to get some code reviews, the harsher the better.
Below I've left the full code. There are a number of comments in Spanish, but that's mostly for my own consumption. If anyone does take the time to review and destroy it, I will be eternally thankful.
import pygame
import random
import os
import sys
main_dir = os.path.split(os.path.abspath(__file__))[0]
os.environ['SDL_VIDEO_CENTERED'] = '1'
class Button: # Con esta clase crearemos una colección de botones con el color, coordenadas y texto que definamos
def __init__(self, x, y, w, h):
self.x = x
self.y = y
self.w = w
self.h = h
self.rect = (self.x, self.y, self.w, self.h)
self.color = []
self.text = []
def contains_point(self, point):
"""Return True if my sprite rectangle contains point pt """
(my_x, my_y) = self.x, self.y
my_width = self.w
my_height = self.h
(x, y) = point
return my_x <= x < my_x + my_width and my_y <= y < my_y + my_height
class Text:
def __init__(self, x, y, text):
self.font = pygame.font.SysFont("Fixedsys Excelsior", 48)
self.x = x
self.y = y
self.position = (self.x, self.y)
self.text = self.font.render(text.format(), True, (255, 255, 255))
class CardSprite: # Vamos a cargar una imagen que contiene todas las cartas.
# Esta imagen es un objeto diferente a la carta, por tanto tendrá su propia clase
def __init__(self): # Cargamos una lista de coordenadas donde están las cartas
self.sheet = pygame.image.load(os.path.join(main_dir, 'UNO', "Copia UNO.jpg"))
self.x = self.sheet.get_width() # 800
self.y = self.sheet.get_height() # 882
def load_grid_images(self):
sheet_rect = self.sheet.get_rect()
sheet_width, sheet_height = sheet_rect.size
sprite_rects = []
cardheighty = 0
cardwidthx = 0
cardsizex = self.x // 10
cardsizey = 125
for card in range(56): # El rango sería mejor definirlo en función del número de cartas del mazo, revisar
card_coordinates = [cardwidthx, cardheighty, cardsizex, cardsizey]
sprite_rects.append(card_coordinates)
cardwidthx += cardsizex
if cardwidthx >= self.x:
cardwidthx = 0
cardheighty += cardsizey
return self.images_at(sprite_rects)
def image_at(self, rectangle, colorkey=None):
"""Load a specific image from a specific rectangle."""
# Loads image from x, y, x+offset, y+offset.
rect = pygame.Rect(rectangle)
image = pygame.Surface(rect.size)
image.blit(self.sheet, (0, 0), rect)
if colorkey is not None:
if colorkey == -1:
colorkey = image.get_at((0, 0))
image.set_colorkey(colorkey, pygame.RLEACCEL)
return image
def images_at(self, rects, colorkey=None):
"""Load a whole bunch of images and return them as a list."""
return [self.image_at(rect, colorkey) for rect in rects]
class Card:
suits = ["Rojo", "Amarillo", "Verde", "Azul", "Comodín"] # suit es un atributo de clase
ranks = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0",
"pierdeturno", "cambiasentido", "robados", "eligecolor", "robacuatro"] # rank es otro atributo de clase
def __init__(self, suit=0,
rank=0): # __init__ crea una instancia de la clase Card; cada carta tiene un suit y un rank
self.suit = suit
self.rank = rank
self.position = []
self.image = None
def contains_point(self, point):
""" Return True if my sprite rectangle contains point pt """
(my_x, my_y) = self.position
my_width = self.image.get_width()
my_height = self.image.get_height()
(x, y) = point
return my_x <= x < my_x + my_width and my_y <= y < my_y + my_height
class Deck:
def __init__(self):
self.cards = [] # Creamos el atributo cards. Recordar que los atributos en __init__, aparte de self, son opcionales
for suit in range(4):
for rank in range(0, 10):
self.cards.append(Card(suit, rank))
for suit in range(4):
for rank in range(10, 13):
self.cards.append(Card(suit, rank))
for suit in range(4, 5):
for rank in range(13, 15):
self.cards.append(Card(suit, rank))
self.cards.append(Card(suit, rank))
sprite = CardSprite()
images = sprite.load_grid_images()
i = 0
for image in images:
self.cards[i].image = image
i += 1
questionmark = sprite.image_at([720, 750, 80, 125])
self.image = questionmark
self.position = (650, 256)
def shuffle(self):
rng = random.Random()
rng.shuffle(self.cards)
def remove(self, card):
if card in self.cards:
self.cards.remove(card) # Usamos el método remove
return True
return False
def pop(self):
return self.cards.pop() # Pop toma la última carta y la reparte
def is_empty(self):
return self.cards == [] # True si no quedan cartas en el mazo
def deal(self, hands, num_cards=999):
num_hands = len(hands)
for i in range(num_cards):
if self.is_empty():
break
card = self.pop()
hand = hands[i % num_hands]
hand.add(card)
def contains_point(self, point):
""" Return True if my sprite rectangle contains point pt """
(my_x, my_y) = self.position
my_width = self.image.get_width()
my_height = self.image.get_height()
(x, y) = point
return my_x <= x < my_x + my_width and my_y <= y < my_y + my_height
class Hand(Deck):
pass
def __init__(self, name=""):
super().__init__()
self.cards = []
self.name = name
if name == "AI":
self.status = 1 # jugador controlado por el ordenador
else:
self.status = 0 # jugador humano
def add(self, card):
self.cards.append(card)
class UNOGame:
def __init__(self):
self.all_buttons = []
self.hands = []
self.colors = ([234, 26, 39], [248, 224, 0], [0, 164, 78], [2, 149, 216], [255, 165, 0],
[0, 0, 0]) # Rojo, amarillo, verde, azul, naranja, negro
surface_sizex = 1280 # Ancho del tablero, en píxeles
surface_sizey = 640 # Alto del tablero, en píxeles
self.main_surface = pygame.display.set_mode((surface_sizex, surface_sizey)) # Creamos el tablero
self.play_area = (320, 264)
self.surface_color = (19, 136, 8) # Red/Green/Blue; la superficie del tablero será de color verde oscuro
self.deck = Deck()
self.deck.shuffle()
def place_cards(self):
# Definimos un patrón para pegar las cartas una junto a otra
[paste_x, paste_y] = [64, 480]
[paste_xAI, paste_yAI] = [64, 32]
# Definimos la posición que ocuparán las cartas sobre el tablero al empezar la partida
for hand in self.hands:
for card in hand.cards:
card.hidden = self.deck.image # Para el atributo hidden usamos la carta con el signo de interrogación,
# que anteriormente asignamos al atributo image del mazo
if hand.name == "AI":
card.position = [paste_xAI, paste_yAI]
paste_xAI += 85
else:
card.position = [paste_x, paste_y]
paste_x += 85
def place_buttons(self):
size = 45
x = self.play_area[0]
y = self.play_area[1]
# Definimos los botones para elegir entre los cuatro colores, así como el botón de pasar turno y el del color en juego
for n in range(4):
a_button = Button(x - 55, y + 145, size, size)
x += 50
self.all_buttons.append(a_button)
coordinates = [850, 275, 125, 25], [750, 325, 330, 25], [225, 400, 175, 30], [575, 400, 175, 30]
for c in coordinates:
a_button = Button(c[0], c[1], c[2], c[3])
self.all_buttons.append(a_button)
c = 0
for button in self.all_buttons:
button.color = self.colors[c]
c += 1
if c == 6:
break
self.all_buttons[4].text = self.font.render("Pasar turno".format(), True, (0, 0, 0))
self.all_buttons[5].text = self.font.render("El color en juego es el {0}".format(self.color_in_play), True,
(255, 255, 255))
self.all_buttons[6].text = self.font.render("Volver a jugar".format(), True, (0, 0, 0))
self.all_buttons[6].color = self.colors[0]
self.all_buttons[7].text = self.font.render("Salir del juego".format(), True, (0, 0, 0))
self.all_buttons[7].color = self.colors[3]
def blit_buttons(self, i=0, j=6):
for n in range(i, j):
button_color = self.all_buttons[n].color
button_rect = (self.all_buttons[n].x, self.all_buttons[n].y, self.all_buttons[n].w, self.all_buttons[n].h)
if n == 5:
self.all_buttons[n].text = self.font.render("El color en juego es el {0}".format(self.color_in_play),
True, (255, 255, 255))
button_text = self.all_buttons[n].text
self.main_surface.fill(button_color, button_rect)
try:
self.main_surface.blit(button_text, button_rect)
except:
TypeError
def update_cards(self):
# Ponemos las cartas sobre el tablero
for hand in self.hands:
for card in hand.cards:
if hand.name == "AI":
self.main_surface.blit(card.hidden,
card.position) # Si es una carta de la IA, elegimos la imagen del signo de interrogación
else:
self.main_surface.blit(card.image, card.position)
self.main_surface.blit(self.deck.image, self.deck.position)
self.main_surface.blit(self.card_in_play.image, self.play_area)
def update_surface(self):
# last_play = Text(600, 200, "La carta en juego es " + self.card_in_play.suits[self.color] + self.card_in_play.ranks[self.number])
# Pintamos la superficie
self.main_surface.fill(self.surface_color)
self.update_cards()
self.blit_buttons(4, 6)
pygame.display.flip()
def play_discard(self):
self.deck.cards = self.discard_deck.cards # Asignamos al mazo las cartas del mazo de descartes
self.deck.shuffle() # Barajamos el nuevo mazo
self.discard_deck.cards = [] # Vaciamos el mazo de descartes para reiniciar el ciclo
def start_UNO(self): # Creamos la partida del juego de cartas UNO, que es un tipo de juego de cartas
# Iniciamos el módulo pygame
pygame.mixer.pre_init(44100, -16, 2, 2048) # setup mixer to avoid sound lag
pygame.init()
pygame.mixer.init()
pygame.mixer.music.load(os.path.join(main_dir, 'UNO', "lobby.mp3"))
pygame.mixer.music.play(-1)
# Creamos una superficie de juego, donde colocar las cartas
pygame.display.set_caption('UNO')
self.main_surface.fill(self.surface_color)
self.font = pygame.font.SysFont("Fixedsys Excelsior", 32)
# Cargamos el logo del juego para la carga inicial
logo = pygame.image.load(os.path.join(main_dir, 'UNO', "UNO_logo_small.png"))
h = logo.get_height()
w = logo.get_width()
welcome = Text(345, 580, "Pulsa cualquier tecla para continuar")
surface_sizex = 1280 # Ancho del tablero, en píxeles
surface_sizey = 640 # Alto del tablero, en píxeles
self.main_surface.blit(logo, (surface_sizex // 2 - w // 2, surface_sizey // 2 - h // 2))
self.main_surface.blit(welcome.text, welcome.position)
pygame.display.flip()
while True:
event = pygame.event.poll()
if event.type == pygame.KEYDOWN: # Hemos pulsado una tecla
break
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
# Permitimos al jugador introducir su nombre
name = Text(100, 100, "Introduce tu nombre (pulsa enter cuando hayas terminado):")
self.main_surface.fill(self.surface_color)
self.main_surface.blit(name.text, name.position)
pygame.display.flip()
self.player_name = ''
font = pygame.font.SysFont("Fixedsys Excelsior", 48)
enter = 0
while enter == 0:
event = pygame.event.poll() # Buscar eventos y asignárselos a la variable event
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_BACKSPACE:
self.player_name = self.player_name[:-1]
elif event.key == pygame.K_RETURN:
enter = 1
else:
self.player_name += event.unicode
self.main_surface.fill(self.surface_color)
txt_surface = font.render(self.player_name, True, pygame.Color('dodgerblue2'))
self.main_surface.blit(txt_surface, (100, 200))
self.main_surface.blit(name.text, name.position)
pygame.display.flip()
self.main_surface.fill(self.surface_color)
tachan = Text(100, 300, "¡Bienvenido, " + self.player_name + "! La partida comenzará en unos segundos")
self.main_surface.blit(tachan.text, tachan.position)
pygame.display.flip()
self.names = [self.player_name, "AI"]
self.main_surface.fill(self.surface_color)
def play_UNO(self): # Iniciamos la partida de UNO
# Creamos las manos del juego
pygame.mixer.music.load(os.path.join(main_dir, 'UNO', "lobby.mp3"))
pygame.mixer.music.play(-1)
for name in self.names:
self.hands.append(Hand(name))
# Repartimos las cartas
self.deck.deal(self.hands, 7 * len(self.names)) # Tomamos el objeto mazo que pertenece al objeto juego (self),
# y repartimos siete cartas a cada jugador
# Colocamos las cartas sobre el tablero
self.place_cards()
# Sacamos una carta para empezar a jugar
self.card_in_play = self.deck.pop()
self.card_in_play.position = self.play_area
self.color = self.card_in_play.suit # Me interesa separar el color de la carta, para poder implementar el comodín eligecolor
self.number = self.card_in_play.rank
# Creamos algunas cositas más parabotones extra
self.color_in_play = self.card_in_play.suits[self.color]
turn = 0
pierdeturno = 0
self.has_drawn = 2 # Para cubrir el caso en que la carta inicial sea una carta de efectos
i = 0
self.place_buttons()
self.update_surface()
self.discard_deck = Deck() # Creamos un mazo de descartes
self.discard_deck.cards = [] # Vaciamos el mazo de descartes
while True:
# Comenzamos el bucle estableciendo los efectos en función de la carta en juego (self.card_in_play):
if self.card_in_play.rank == 10: # "pierdeturno": # El turno pasará al otro jugador
if self.has_drawn == 0:
pierdeturno += 1
self.has_drawn = 1
elif self.card_in_play.rank == 11: # "cambiasentido":
self.has_drawn = 1 # Esto es simplemente para poder simplificar la fórmula de efectos y expresarla como 10 <= rank <= 14
elif self.card_in_play.rank == 12: # "robados":
if self.has_drawn != 1: # No queremos que al comenzar el siguiente turno se vuelvan a robar dos cartas
for n in range(2):
if self.deck.is_empty(): # Si no quedan cartas en el mazo
self.play_discard()
self.hands[(i + 1) % len(self.hands)].cards.append(
self.deck.pop()) # Robo dos cartas y las añado a mi mano
self.has_drawn = 1
elif self.card_in_play.rank == 13: # "eligecolor":
if self.has_drawn != 1: # No queremos que al final de este turno se vuelvan a robar cuatro cartas
# Aquí hay que añadir un árbol de decisión según sea jugador IA o jugador humano, para elegir el color
if self.hands[i].status == 1: # si el jugador es IA
rng = random.Random()
self.color = rng.randrange(0, 4)
else: # si el jugador es humano
self.blit_buttons(0, 4)
pygame.display.flip()
has_picked_color = 0
while has_picked_color == 0:
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONDOWN: # Hemos hecho click
place_of_click = event.dict["pos"]
b = 0
for button in self.all_buttons[0:4]:
if button.contains_point(place_of_click):
has_picked_color = 1
self.color = b
b += 1
self.card_in_play.suit = self.color
self.color_in_play = self.card_in_play.suits[self.color]
self.has_drawn = 1
elif self.card_in_play.rank == 14: # "robacuatro":
if self.has_drawn != 1: # No queremos que al final de este turno se vuelvan a robar cuatro cartas
for n in range(4):
if self.deck.is_empty(): # Si no quedan cartas en el mazo
self.play_discard()
self.hands[(i + 1) % len(self.hands)].cards.append(
self.deck.pop()) # Robo cuatro cartas y las añado a mi mano
# Aquí hay que añadir un árbol de decisión según sea jugador IA o jugador humano, para elegir el color
if self.hands[i].status == 1: # si el jugador es IA
rng = random.Random()
self.color = rng.randrange(0, 4)
else: # si el jugador es humano
self.blit_buttons(0, 4)
pygame.display.flip()
has_picked_color = 0
while has_picked_color == 0:
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONDOWN: # Hemos hecho click
place_of_click = event.dict["pos"]
b = 0
for button in self.all_buttons[0:4]:
if button.contains_point(place_of_click):
has_picked_color = 1
self.color = b
b += 1
self.card_in_play.suit = self.color
self.color_in_play = self.card_in_play.suits[self.color]
self.has_drawn = 1
# Actualizamos las cartas
self.place_cards()
self.update_surface()
# Los efectos ya están consolidados (esperemos). Ahora hay que definir el cambio de turno
turn += 1
i = (turn + pierdeturno) % len(
self.hands) # 0 en el primer turno, salvo que la primera carta en juego sea pierdeturno
self.playable_cards = []
suits_in_hand = []
hand = self.hands[i].cards # Llamamos hand a la mano que está jugando, para que el código sea más legible
# Comienza el siguiente turno, propiamente dicho
# Determinamos si tenemos cartas en la mano que podamos jugar:
for card in hand:
suits_in_hand.append(
card.suit) # Anotamos el color de cada carta, para comprobar después si podemos usar el comodín robacuatro
if card.suit == self.color or card.rank == self.card_in_play.rank or card.rank == 13: # "eligecolor":
# Si la carta comparte color o número con la que está en juego, o si es el comodín de elegir color
self.playable_cards.append(card)
if self.color not in suits_in_hand: # Si no tenemos ninguna carta del mismo color que la que está en el área de juego
for card in hand:
if card.rank == 14: # "robacuatro":
self.playable_cards.append(card) # Lo añadimos a las cartas que podemos jugar
if self.hands[i].status == 1: # si el jugador es IA
self.AI_plays(hand)
else: # si el jugador es humano
self.human_plays(hand)
# Consideraciones de final de turno, a continuación
self.color = self.card_in_play.suit
self.color_in_play = self.card_in_play.suits[self.color]
if self.deck.is_empty(): # Si no quedan cartas en el mazo
self.play_discard()
self.place_cards()
self.update_surface()
if self.hands[i].is_empty(): # Si no quedan cartas en la mano
end_game = 0
self.main_surface.fill(self.surface_color)
if self.hands[i].name == self.player_name: # Ha ganado el jugador humano
pygame.mixer.music.load(os.path.join(main_dir, 'UNO', "win.mp3"))
pygame.mixer.music.play(-1)
win = Text(100, 300, "¡Enhorabuena, " + self.player_name + "! Has ganado la partida")
self.main_surface.blit(win.text, win.position)
else: # Ha ganado la IA
pygame.mixer.music.load(os.path.join(main_dir, 'UNO', "loss.mp3"))
pygame.mixer.music.play(-1)
loss = Text(100, 300, "¡Lo sentimos, " + self.player_name + "! El ordenador ha ganado la partida")
self.main_surface.blit(loss.text, loss.position)
self.blit_buttons(6, 8)
pygame.display.flip()
while end_game == 0:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONDOWN: # Hemos hecho click
place_of_click = event.dict["pos"]
for button in self.all_buttons[6:8]:
if button.contains_point(place_of_click):
if button == self.all_buttons[6]:
end_game = 1
self.deck = Deck()
self.deck.shuffle()
self.play_UNO()
else:
pygame.quit()
sys.exit()
pygame.display.flip()
def AI_plays(self, hand):
if not self.playable_cards: # No podemos jugar ninguna carta, tenemos que robar
drawn_card = self.deck.pop()
if drawn_card.suit == self.color or drawn_card.rank == self.card_in_play.rank or drawn_card.suit == 4: # "Comodín":
# Si la carta robada comparte color o número con la que está en juego, o si es un comodín
self.card_in_play = drawn_card # Jugamos la carta robada
self.has_drawn = 0
# hand.remove(drawn_card) ¡No es necesario, porque no llega a añadir la carta a la mano! Dará error
self.discard_deck.cards.append(drawn_card)
else:
hand.append(drawn_card) # Añadimos la carta robada a nuestra mano
else: # No nos hace falta robar, porque tenemos en la mano cartas que podemos jugar
rng = random.Random()
selected_card = self.playable_cards[
rng.randrange(0, len(self.playable_cards))] # La IA elige la carta que jugar
self.card_in_play = selected_card # La carta elegida será la próxima carta en juego
self.has_drawn = 0
self.card_in_play.position = self.play_area # Adjudicamos a la carta elegida la zona de juego
hand.remove(selected_card)
self.discard_deck.cards.append(selected_card)
def human_plays(self, hand):
self.has_drawn = 0
has_played = 0
has_playable_cards = 0
while self.has_drawn == 0:
if not self.playable_cards: # No podemos jugar ninguna carta, tenemos que robar
for event in pygame.event.get(): #event = pygame.event.poll() # Buscar eventos y asignárselos a la variable event
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONDOWN: # Hemos hecho click
place_of_click = event.dict["pos"]
if self.deck.contains_point(place_of_click):
drawn_card = self.deck.pop()
if drawn_card.suit == self.color or drawn_card.rank == self.card_in_play.rank or drawn_card.suit == 4: # "Comodín":
self.playable_cards.append(drawn_card)
has_playable_cards = 1
else:
pygame.display.flip()
self.has_drawn = 1
hand.append(drawn_card)
# Actualizamos las cartas
self.place_cards()
self.update_surface()
else:
has_playable_cards = 1
self.has_drawn = 1
while has_played == 0:
if has_playable_cards == 1: # Podemos jugar
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONDOWN: # Hemos hecho click
place_of_click = event.dict["pos"]
for card in self.playable_cards:
if card.contains_point(place_of_click):
self.card_in_play = card
hand.remove(card)
self.discard_deck.cards.append(card)
has_played = 1
self.has_drawn = 0
else: # Tenemos que pasar turno
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONDOWN: # Hemos hecho click
place_of_click = event.dict["pos"]
if self.all_buttons[4].contains_point(place_of_click):
has_played = 1
def main():
game = UNOGame()
game.start_UNO()
game.play_UNO()
# call the "main" function if running this script
if __name__ == '__main__': main()
1 Answer 1
Documentation
The PEP 8 style guide recommends adding docstrings for classes and functions. For example, you can convert the following comment:
class Button: # Con esta clase crearemos una colección de botones con el color, coordenadas y texto que definamos
to a docstring:
class Button():
"""
Con esta clase crearemos una colección de botones con el color,
coordenadas y texto que definamos
"""
This also makes the code more readable since the one very long line is split up into several lines. Converting to English like your other docstrings would be more consistent.
I was hoping to run the code and play the game, but I can't because it relies on several external files. You should add a header doctrsing which summarizes the purpose of the code and notifies the user of external files and where they should be located. Alternately, you could create a version of your code that does not rely on external files.
Comments
I can't really assess whether your comments are helpful or not because I am not fluent in Spanish. However, I'm pretty sure the following comment can be deleted since it simply repeats what the code is doing:
self.cards.remove(card) # Usamos el método remove
Good comments should explain why the code was written a certain way.
The following comments in the Hand
class:
if name == "AI":
self.status = 1 # jugador controlado por el ordenador
else:
self.status = 0 # jugador humano
would not be needed if you chose your variable names and types differently.
Every time the status
variable appears in your code, you need to explain
it with a comment. "status" is too generic. Also, since you only set the
variable to 1 and 0, it could be a boolean type instead of an integer type.
In fact, you may be able to combine the 2 variables into one boolean:
is_ai
Then checks like:
if self.hands[i].status == 1: # si el jugador es IA
are simplified as:
if self.hands[i].is_ai:
There is no need to compare against 1, and there is no need for the comment. Similar logic applies for lines like:
if hand.name == "AI":
The variable has_played
would be better as a boolean as well because
it more clearly demonstrates the intent. You only set it to 0 and 1,
so you may as well set it to False
and True
instead.
Simpler
I don't think pass
is needed here:
class Hand(Deck):
pass
In class Deck
, this line:
for suit in range(4, 5):
is simpler as:
suit = 4
Layout
This line:
if __name__ == '__main__': main()
is typically split into two lines:
if __name__ == '__main__':
main()
Explore related questions
See similar questions with these tags.