It's my first time here, so please accept my apologies in advance if something is wrong with my approach to asking.
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. 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-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. 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 :).
It's my first time here, so please accept my apologies in advance if something is wrong with my approach to asking.
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 :)
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.
Python code problem - unable to exit game normallyfor UNO Game
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. But I have one particular problem that I have been unable to get my head around: in lines 383 - 387, when I start the game loop, I have been unable to get the "closing the game pressing on X" working. So, the code is as follows:
while True:
event = pygame.event.poll()
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
I've tried several ways, and it must be a silly thing, but I can't get it to work. The game runs, but I can't close the window normally while I'm playing the game, nothing happens. Similar code in lines 307-313 (setting up the game) works like a charm.
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:
event = pygame.event.poll() # Buscar eventos y asignárselos a la variable event
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
# 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()
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. But I have one particular problem that I have been unable to get my head around: in lines 383 - 387, when I start the game loop, I have been unable to get the "closing the game pressing on X" working. So, the code is as follows:
while True:
event = pygame.event.poll()
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
I've tried several ways, and it must be a silly thing, but I can't get it to work. The game runs, but I can't close the window normally while I'm playing the game, nothing happens. Similar code in lines 307-313 (setting up the game) works like a charm.
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:
event = pygame.event.poll() # Buscar eventos y asignárselos a la variable event
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
# 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():
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.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.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()
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.
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()