Proceso para Crear Mini-Programas en Python: Proyectos Prácticos

Python es un lenguaje versátil que permite crear desde aplicaciones simples hasta proyectos complejos. A continuación, presento una colección de proyectos funcionales que puedes implementar para practicar tus habilidades de programación.

Aplicaciones de Interfaz Gráfica

1. Calculadora

Este proyecto implementa una calculadora gráfica con operaciones básicas usando la biblioteca Tkinter. El diseño incluye una pantalla de visualización y botones organizados en una matriz.

El principio fundamental es separar la lógica de presentación de la lógica de negocio. La calculadora utiliza una varible de tipo StringVar para mostrar los resultados y mantiene una lista de operandos y operadores.

A continuación se presenta una versión optimizada del código:

import tkinter
from tkinter import messagebox
import math

class InterfazCalculadora:
    def __init__(self):
        self.ventana = tkinter.Tk()
        self.ventana.title("Calculadora Básica")
        self.ventana.geometry("280x450")
        self.ventana.resizable(False, False)
        
        self.expresion = tkinter.StringVar()
        self.expresion.set("0")
        self.historial_operaciones = []
        self.flag_operador = False
        
        self.construir_menu()
        self.construir_interfaz()
        self.ventana.mainloop()
    
    def construir_menu(self):
        menu_principal = tkinter.Menu(self.ventana)
        menu_archivo = tkinter.Menu(menu_principal, tearoff=0)
        menu_archivo.add_command(label="Estándar", command=self.funcion_vacia)
        menu_archivo.add_command(label="Científica", command=self.funcion_vacia)
        menu_archivo.add_separator()
        menu_archivo.add_command(label="Salir", command=self.ventana.quit)
        menu_principal.add_cascade(label="Ver", menu=menu_archivo)
        
        menu_ayuda = tkinter.Menu(menu_principal, tearoff=0)
        menu_ayuda.add_command(label="Acerca de", command=self.mostrar_acerca)
        menu_principal.add_cascade(label="Ayuda", menu=menu_ayuda)
        
        self.ventana.config(menu=menu_principal)
    
    def construir_interfaz(self):
        # Pantalla de visualización
        pantalla = tkinter.Entry(self.ventana, textvariable=self.expresion, 
                                font=('Arial', 24), justify='right', bd=10)
        pantalla.place(x=5, y=5, width=270, height=50)
        
        # Botones de memoria
        botones_memoria = ['MC', 'MR', 'MS', 'M+', 'M-']
        x_posiciones = [5, 60, 115, 170, 225]
        
        for btn, x in zip(botones_memoria, x_posiciones):
            tkinter.Button(self.ventana, text=btn, width=5, height=2,
                          command=self.funcion_vacia).place(x=x, y=60)
        
        # Botones de operaciones especiales
        operaciones_especiales = [
            ('←', self.eliminar_caracter),
            ('CE', self.limpiar_entrada),
            ('C', self.limpiar_todo),
            ('±', self.cambiar_signo),
            ('√', self.raiz_cuadrada)
        ]
        
        y_pos = 105
        for texto, comando in operaciones_especiales:
            x_inicial = 5
            tkinter.Button(self.ventana, text=texto, width=5, height=2,
                          command=comando).place(x=x_inicial, y=y_pos)
            x_inicial += 55
        
        # Botones numéricos y operadores
        self.botones = [
            ('7', 8), ('8', 8), ('9', 8), ('/', 8), ('%', 8),
            ('4', 9), ('5', 9), ('6', 9), ('*', 9), ('1/x', 9),
            ('1', 10), ('2', 10), ('3', 10), ('-', 10), ('=', 10),
            ('0', 11), ('.', 11), ('+', 11)
        ]
        
        self.crear_botones()
    
    def crear_botones(self):
        coordenadas = {
            8: [(5, 150), (60, 150), (115, 150), (170, 150), (225, 150)],
            9: [(5, 205), (60, 205), (115, 205), (170, 205), (225, 205)],
            10: [(5, 260), (60, 260), (115, 260), (170, 260), (225, 260)],
            11: [(5, 315), (115, 315), (170, 315)]
        }
        
        posiciones = [
            (0, 0), (1, 0), (2, 0), (3, 0), (4, 0),
            (0, 1), (1, 1), (2, 1), (3, 1), (4, 1),
            (0, 2), (1, 2), (2, 2), (3, 2), (4, 2),
            (0, 3), (1, 3), (2, 3)
        ]
        
        idx = 0
        for fila in [8, 9, 10, 11]:
            for col in range(5 if fila != 11 else 3):
                if idx < len(self.botones):
                    texto, cmd = self.botones[idx]
                    x, y = coordenadas[fila][col]
                    
                    if texto == '=':
                        tkinter.Button(self.ventana, text=texto, width=5, height=2,
                                      command=self.calcular_resultado).place(x=x, y=y, height=105)
                    elif texto == '0':
                        tkinter.Button(self.ventana, text=texto, width=10, height=2,
                                      command=lambda t=texto: self.insertar_numero(t)).place(x=x, y=y)
                    elif texto in ['/', '*', '-', '+', '%']:
                        tkinter.Button(self.ventana, text=texto, width=5, height=2,
                                      command=lambda t=texto: self.insertar_operador(t)).place(x=x, y=y)
                    else:
                        tkinter.Button(self.ventana, text=texto, width=5, height=2,
                                      command=lambda t=texto: self.insertar_numero(t)).place(x=x, y=y)
                    idx += 1
    
    def insertar_numero(self, num):
        if self.flag_operador:
            self.expresion.set("0")
            self.flag_operador = False
        
        valor_actual = self.expresion.get()
        if valor_actual == '0' or valor_actual == 'Error':
            self.expresion.set(num)
        else:
            self.expresion.set(valor_actual + num)
    
    def insertar_operador(self, op):
        valor = self.expresion.get()
        self.historial_operaciones.append(valor)
        self.historial_operaciones.append(op)
        self.flag_operador = True
    
    def calcular_resultado(self):
        valor_actual = self.expresion.get()
        self.historial_operaciones.append(valor_actual)
        
        try:
            expresion_completa = ''.join(self.historial_operaciones)
            resultado = eval(expresion_completa)
            self.expresion.set(str(resultado)[:15])
        except:
            self.expresion.set("Error")
        
        self.historial_operaciones.clear()
        self.flag_operador = True
    
    def eliminar_caracter(self):
        valor = self.expresion.get()
        if len(valor) > 1:
            self.expresion.set(valor[:-1])
        else:
            self.expresion.set("0")
    
    def limpiar_entrada(self):
        self.expresion.set("0")
    
    def limpiar_todo(self):
        self.expresion.set("0")
        self.historial_operaciones.clear()
    
    def cambiar_signo(self):
        valor = self.expresion.get()
        if valor != '0':
            if valor.startswith('-'):
                self.expresion.set(valor[1:])
            else:
                self.expresion.set('-' + valor)
    
    def raiz_cuadrada(self):
        try:
            valor = float(self.expresion.get())
            resultado = math.sqrt(valor)
            self.expresion.set(str(resultado)[:15])
        except:
            self.expresion.set("Error")
    
    def funcion_vacia(self):
        messagebox.showinfo("Información", "Función en desarrollo")
    
    def mostrar_acerca(self):
        messagebox.showinfo("Acerca de", "Calculadora v1.0\nCreada con Python y Tkinter")

# Instanciar la calculadora
calculadora = InterfazCalculadora()

2. Editor de Texto

Un bloc de notas básico implementado con Tkinter que permite crear, abrir, guardar y editar archivos de texto. Utiliza el módulo filedialog para los diálogos de archivo y pickle para almacenar preferencias.

El código proporciona las funcionalidades básicas de cualquier editor de texto: nuevo archivo, abrir, guardar, guardar como, deshacer, rehacer, cortar, copiar, pegar, buscar y seleccionar todo.

from tkinter import *
from tkinter.filedialog import *
from tkinter.messagebox import *

class EditorTexto:
    def __init__(self):
        self.raiz = Tk()
        self.raiz.title("Editor de Texto")
        self.raiz.geometry("600x400")
        self.nombre_archivo = None
        
        self.crear_menu()
        self.crear_area_texto()
        self.crear_barra_estado()
        
        self.raiz.mainloop()
    
    def crear_menu(self):
        barra_menu = Menu(self.raiz)
        
        # Menú Archivo
        menu_archivo = Menu(barra_menu, tearoff=0)
        menu_archivo.add_command(label="Nuevo", accelerator="Ctrl+N", command=self.nuevo_archivo)
        menu_archivo.add_command(label="Abrir", accelerator="Ctrl+O", command=self.abrir_archivo)
        menu_archivo.add_command(label="Guardar", accelerator="Ctrl+S", command=self.guardar_archivo)
        menu_archivo.add_command(label="Guardar como", accelerator="Ctrl+Shift+S", 
                                command=self.guardar_como)
        menu_archivo.add_separator()
        menu_archivo.add_command(label="Salir", command=self.raiz.quit)
        barra_menu.add_cascade(label="Archivo", menu=menu_archivo)
        
        # Menú Editar
        menu_editar = Menu(barra_menu, tearoff=0)
        menu_editar.add_command(label="Deshacer", accelerator="Ctrl+Z", command=self.deshacer)
        menu_editar.add_command(label="Rehacer", accelerator="Ctrl+Y", command=self.rehacer)
        menu_editar.add_separator()
        menu_editar.add_command(label="Cortar", accelerator="Ctrl+X", command=self.cortar)
        menu_editar.add_command(label="Copiar", accelerator="Ctrl+C", command=self.copiar)
        menu_editar.add_command(label="Pegar", accelerator="Ctrl+V", command=self.pegar)
        menu_editar.add_separator()
        menu_editar.add_command(label="Buscar", accelerator="Ctrl+B", command=self.buscar)
        menu_editar.add_command(label="Seleccionar todo", accelerator="Ctrl+A", 
                               command=self.seleccionar_todo)
        barra_menu.add_cascade(label="Editar", menu=menu_editar)
        
        # Menú Ayuda
        menu_ayuda = Menu(barra_menu, tearoff=0)
        menu_ayuda.add_command(label="Acerca de", command=self.mostrar_info)
        barra_menu.add_cascade(label="Ayuda", menu=menu_ayuda)
        
        self.raiz.config(menu=barra_menu)
    
    def crear_area_texto(self):
        frame_central = Frame(self.raiz)
        frame_central.pack(fill=BOTH, expand=YES)
        
       scroll_y = Scrollbar(frame_central)
        self.area_texto = Text(frame_central, wrap=NONE, undo=True, 
                               yscrollcommand=scroll_y.set, xscrollcommand=self.scroll_horizontal)
        scroll_y.config(command=self.area_texto.yview)
        scroll_y.pack(side=RIGHT, fill=Y)
        self.area_texto.pack(fill=BOTH, expand=YES)
        
        # Bindings de teclado
        self.area_texto.bind('<Control-N>', lambda e: self.nuevo_archivo())
        self.area_texto.bind('<Control-n>', lambda e: self.nuevo_archivo())
        self.area_texto.bind('<Control-O>', lambda e: self.abrir_archivo())
        self.area_texto.bind('<Control-o>', lambda e: self.abrir_archivo())
        self.area_texto.bind('<Control-S>', lambda e: self.guardar_archivo())
        self.area_texto.bind('<Control-s>', lambda e: self.guardar_archivo())
        self.area_texto.bind('<Control-A>', lambda e: self.seleccionar_todo())
        self.area_texto.bind('<Control-a>', lambda e: self.seleccionar_todo())
        
        # Menú contextual
        self.menu_contexto = Menu(self.area_texto, tearoff=0)
        self.menu_contexto.add_command(label="Cortar", command=self.cortar)
        self.menu_contexto.add_command(label="Copiar", command=self.copiar)
        self.menu_contexto.add_command(label="Pegar", command=self.pegar)
        self.area_texto.bind('<Button-3>', self.mostrar_menu_contexto)
    
    def scroll_horizontal(self, *args):
        pass  # Implementar si es necesario
    
    def crear_barra_estado(self):
        self.barra_estado = Label(self.raiz, text="Línea 1, Columna 1", 
                                  anchor=E, bd=1, relief=SUNKEN)
        self.barra_estado.pack(side=BOTTOM, fill=X)
        self.area_texto.bind('<KeyPress>', self.actualizar_estado)
        self.area_texto.bind('<Button-1>', self.actualizar_estado)
    
    def actualizar_estado(self, event=None):
        cursor_pos = self.area_texto.index(INSERT)
        fila, columna = cursor_pos.split('.')
        self.barra_estado.config(text=f"Línea {fila}, Columna {int(columna)+1}")
    
    def nuevo_archivo(self):
        self.nombre_archivo = None
        self.raiz.title("Sin título")
        self.area_texto.delete(1.0, END)
    
    def abrir_archivo(self):
        archivo = askopenfilename(defaultextension=".txt",
                                 filetypes=[("Archivos de texto", "*.txt"), 
                                           ("Todos los archivos", "*.*")])
        if archivo:
            self.nombre_archivo = archivo
            self.raiz.title(f"{basename(archivo)} - Editor de Texto")
            self.area_texto.delete(1.0, END)
            with open(archivo, 'r', encoding='utf-8') as f:
                self.area_texto.insert(1.0, f.read())
    
    def guardar_archivo(self):
        if self.nombre_archivo:
            try:
                with open(self.nombre_archivo, 'w', encoding='utf-8') as f:
                    contenido = self.area_texto.get(1.0, END)
                    f.write(contenido)
            except:
                showerror("Error", "No se pudo guardar el archivo")
        else:
            self.guardar_como()
    
    def guardar_como(self):
        archivo = asksaveasfilename(initialfile="sin_titulo.txt",
                                   defaultextension=".txt",
                                   filetypes=[("Archivos de texto", "*.txt"),
                                            ("Todos los archivos", "*.*")])
        if archivo:
            self.nombre_archivo = archivo
            self.guardar_archivo()
            self.raiz.title(f"{basename(archivo)} - Editor de Texto")
    
    def deshacer(self):
        try:
            self.area_texto.edit_undo()
        except:
            pass
    
    def rehacer(self):
        try:
            self.area_texto.edit_redo()
        except:
            pass
    
    def cortar(self):
        self.area_texto.event_generate("<<Cut>>")
    
    def copiar(self):
        self.area_texto.event_generate("<<Copy>>")
    
    def pegar(self):
        self.area_texto.event_generate("<<Paste>>")
    
    def buscar(self):
        ventana_busqueda = Toplevel(self.raiz)
        ventana_busqueda.title("Buscar")
        ventana_busqueda.geometry("300x80")
        
        Label(ventana_busqueda, text="Buscar:").grid(row=0, column=0, padx=5, pady=5)
        entrada = Entry(ventana_busqueda, width=30)
        entrada.grid(row=0, column=1, padx=5, pady=5)
        entrada.focus_set()
    
    def seleccionar_todo(self):
        self.area_texto.tag_add("sel", "1.0", END)
    
    def mostrar_menu_contexto(self, event):
        self.menu_contexto.tk_popup(event.x_root, event.y_root)
    
    def mostrar_info(self):
        showinfo("Acerca del Editor", "Editor de Texto v1.0\nDesarrollado con Python y Tkinter")

# Iniciar la aplicación
editor = EditorTexto()

3. Sistema de Autenticación

Este proyecto implementa un sistema de inicio de sesión y registro de usuarios. Utiliza pickle para la persistencia de datos de usuarios de forma serializada.

La aplicación permite a los usuarios iniciar sesión con credenciales existentes o registrarse como nuevos usuarios. Los datos se almacenan en un archivo pickle para su recuperación posterior.

import tkinter as tk
from tkinter import messagebox
import pickle
import os

class SistemaAutenticacion:
    def __init__(self):
        self.ventana_principal = tk.Tk()
        self.ventana_principal.title("Sistema de Acceso")
        self.ventana_principal.geometry("400x250")
        
        self.crear_interfaz_principal()
        self.ventana_principal.mainloop()
    
    def crear_interfaz_principal(self):
        # Fondo decorativo
        lienzo = tk.Canvas(self.ventana_principal, height=150, width=400)
        lienzo.pack()
        
        # Etiquetas
        tk.Label(self.ventana_principal, text="Usuario:").place(x=80, y=120)
        tk.Label(self.ventana_principal, text="Contraseña:").place(x=80, y=160)
        
        # Variables de texto
        self.variable_usuario = tk.StringVar()
        self.variable_usuario.set("")
        self.variable_password = tk.StringVar()
        
        # Campos de entrada
        self.campo_usuario = tk.Entry(self.ventana_principal, textvariable=self.variable_usuario)
        self.campo_usuario.place(x=150, y=120)
        
        self.campo_password = tk.Entry(self.ventana_principal, 
                                       textvariable=self.variable_password, show="*")
        self.campo_password.place(x=150, y=160)
        
        # Botones
        tk.Button(self.ventana_principal, text="Iniciar Sesión", 
                 command=self.iniciar_sesion).place(x=100, y=200)
        tk.Button(self.ventana_principal, text="Registrarse", 
                 command=self.mostrar_ventana_registro).place(x=210, y=200)
        
        # Bind para ENTER
        self.campo_password.bind('<Return>', lambda e: self.iniciar_sesion())
    
    def iniciar_sesion(self):
        usuario = self.variable_usuario.get()
        password = self.variable_password.get()
        
        try:
            with open('datos_usuarios.pkl', 'rb') as archivo:
                datos_usuarios = pickle.load(archivo)
        except FileNotFoundError:
            with open('datos_usuarios.pkl', 'wb') as archivo:
                datos_usuarios = {'admin': 'admin123'}
                pickle.dump(datos_usuarios, archivo)
        
        if usuario in datos_usuarios:
            if password == datos_usuarios[usuario]:
                messagebox.showinfo("Bienvenido", f"Hola {usuario}, acceso concedido")
            else:
                messagebox.showerror("Error", "Contraseña incorrecta")
        else:
            respuesta = messagebox.askyesno("Registro", 
                                           "Usuario no encontrado. ¿Desea registrarse?")
            if respuesta:
                self.mostrar_ventana_registro()
    
    def mostrar_formulario_registro(self):
        self.ventana_principal.withdraw()
        
        ventana_registro = tk.Toplevel(self.ventana_principal)
        ventana_registro.title("Nuevo Usuario")
        ventana_registro.geometry("350x220")
        
        tk.Label(ventana_registro, text="Nuevo Usuario:").place(x=20, y=30)
        nuevo_usuario = tk.StringVar()
        tk.Entry(ventana_registro, textvariable=nuevo_usuario).place(x=120, y=30)
        
        tk.Label(ventana_registro, text="Nueva Contraseña:").place(x=20, y=70)
        nueva_password = tk.StringVar()
        tk.Entry(ventana_registro, textvariable=nueva_password, show="*").place(x=120, y=70)
        
        tk.Label(ventana_registro, text="Confirmar:").place(x=20, y=110)
        confirm_password = tk.StringVar()
        tk.Entry(ventana_registro, textvariable=confirm_password, show="*").place(x=120, y=110)
        
        def registrar():
            if nueva_password.get() != confirm_password.get():
                messagebox.showerror("Error", "Las contraseñas no coinciden")
                return
            
            try:
                with open('datos_usuarios.pkl', 'rb') as archivo:
                    usuarios_existentes = pickle.load(archivo)
            except FileNotFoundError:
                usuarios_existentes = {}
            
            if nuevo_usuario.get() in usuarios_existentes:
                messagebox.showerror("Error", "El usuario ya existe")
                return
            
            usuarios_existentes[nuevo_usuario.get()] = nueva_password.get()
            
            with open('datos_usuarios.pkl', 'wb') as archivo:
                pickle.dump(usuarios_existentes, archivo)
            
            messagebox.showinfo("Éxito", "Usuario registrado correctamente")
            ventana_registro.destroy()
            self.ventana_principal.deiconify()
        
        tk.Button(ventana_registro, text="Registrarse", command=registrar).place(x=130, y=160)
        
        def cerrar():
            ventana_registro.destroy()
            self.ventana_principal.deiconify()
        
        ventana_registro.protocol("WM_DELETE_WINDOW", cerrar)
    
    def mostrar_ventana_registro(self):
        self.mostrar_formulario_registro()

# Ejecutar aplicación
if __name__ == "__main__":
    SistemaAutenticacion()

Desarrollo de Juegos

1. Juego 2048

El clásico juego 2048 implementado con Pygame. El objetivo es combinar números iguales hasta obtener 2048. El juego utiliza una matriz 4x4 y responde a las teclas de dirección.

La lógica principal maneja el movimiento de loseta en las cuatro direcciones, combinando valores iguales y generando nuevos números aleatorios.

import pygame
import random

pygame.init()

TAMAÑO_CELDA = 100
CUADRO_TAMANO = 4
ANCHO_PANTALLA = TAMAÑO_CELDA * CUADRO_TAMANO
ALTO_PANTALLA = TAMAÑO_CELDA * CUADRO_TAMANO

COLORES = [
    (238, 228, 218), (237, 224, 200), (242, 177, 121),
    (245, 149, 99), (246, 124, 95), (246, 94, 59),
    (237, 207, 114), (237, 204, 97), (237, 200, 80),
    (237, 197, 63), (237, 194, 46)
]

COLOR_TEXTO = (119, 110, 101)
COLOR_TEXTO_CLARO = (249, 246, 226)

class Juego2048:
    def __init__(self):
        self.pantalla = pygame.display.set_mode((ANCHO_PANTALLA, ALTO_PANTALLA))
        pygame.display.set_caption("2048")
        self.reloj = pygame.time.Clock()
        self.matriz = [[0] * CUADRO_TAMANO for _ in range(CUADRO_TAMANO)]
        self.puntuacion = 0
        self.agregar_numero()
        self.agregar_numero()
        self.ejecutar()
    
    def agregar_numero(self):
        espacios_vacios = [(i, j) for i in range(CUADRO_TAMANO) 
                          for j in range(CUADRO_TAMANO) if self.matriz[i][j] == 0]
        if espacios_vacios:
            x, y = random.choice(espacios_vacios)
            self.matriz[x][y] = 2 if random.random() < 0.9 else 4
            self.puntuacion += self.matriz[x][y]
    
    def comprimir(self, fila):
        nueva_fila = [num for num in fila if num != 0]
        nueva_fila += [0] * (len(fila) - len(nueva_fila))
        return nueva_fila
    
    def combinar(self, fila):
        for i in range(len(fila) - 1):
            if fila[i] == fila[i + 1] and fila[i] != 0:
                fila[i] *= 2
                self.puntuacion += fila[i]
                fila[i + 1] = 0
        return fila
    
    def mover_izquierda(self):
        movido = False
        for i in range(CUADRO_TAMANO):
            original = self.matriz[i][:]
            self.matriz[i] = self.comprimir(self.matriz[i])
            self.matriz[i] = self.combinar(self.matriz[i])
            self.matriz[i] = self.comprimir(self.matriz[i])
            if original != self.matriz[i]:
                movido = True
        return movido
    
    def mover_derecha(self):
        movido = False
        for i in range(CUADRO_TAMANO):
            original = self.matriz[i][:]
            self.matriz[i] = self.matriz[i][::-1]
            self.matriz[i] = self.comprimir(self.matriz[i])
            self.matriz[i] = self.combinar(self.matriz[i])
            self.matriz[i] = self.comprimir(self.matriz[i])
            self.matriz[i] = self.matriz[i][::-1]
            if original != self.matriz[i]:
                movido = True
        return movido
    
    def mover_arriba(self):
        movido = False
        for j in range(CUADRO_TAMANO):
            columna = [self.matriz[i][j] for i in range(CUADRO_TAMANO)]
            original = columna[:]
            columna = self.comprimir(columna)
            columna = self.combinar(columna)
            columna = self.comprimir(columna)
            for i in range(CUADRO_TAMANO):
                self.matriz[i][j] = columna[i]
            if original != columna:
                movido = True
        return movido
    
    def mover_abajo(self):
        movido = False
        for j in range(CUADRO_TAMANO):
            columna = [self.matriz[i][j] for i in range(CUADRO_TAMANO)]
            original = columna[:]
            columna = columna[::-1]
            columna = self.comprimir(columna)
            columna = self.combinar(columna)
            columna = self.comprimir(columna)
            columna = columna[::-1]
            for i in range(CUADRO_TAMANO):
                self.matriz[i][j] = columna[i]
            if original != columna:
                movido = True
        return movido
    
    def game_over(self):
        for i in range(CUADRO_TAMANO):
            for j in range(CUADRO_TAMANO):
                if self.matriz[i][j] == 0:
                    return False
                if j < CUADRO_TAMANO - 1 and self.matriz[i][j] == self.matriz[i][j + 1]:
                    return False
                if i < CUADRO_TAMANO - 1 and self.matriz[i][j] == self.matriz[i + 1][j]:
                    return False
        return True
    
    def dibujar(self):
        self.pantalla.fill((187, 173, 160))
        
        for i in range(CUADRO_TAMANO):
            for j in range(CUADRO_TAMANO):
                valor = self.matriz[i][j]
                x = j * TAMAÑO_CELDA + 5
                y = i * TAMAÑO_CELDA + 5
                ancho = TAMAÑO_CELDA - 10
                
                if valor == 0:
                    color = (205, 193, 180)
                else:
                    indice = min(int(pow(valor, 0.5) - 1), len(COLORES) - 1)
                    color = COLORES[indice]
                
                pygame.draw.rect(self.pantalla, color, (x, y, ancho, ancho), border_radius=8)
                
                if valor != 0:
                    fuente = pygame.font.SysFont('arial', 40, bold=True)
                    texto = fuente.render(str(valor), True, COLOR_TEXTO_CLARO if valor > 4 else COLOR_TEXTO)
                    rect = texto.get_rect(center=(x + ancho // 2, y + ancho // 2))
                    self.pantalla.blit(texto, rect)
        
        pygame.display.flip()
    
    def ejecutar(self):
        ejecutando = True
        
        while ejecutando:
            self.reloj.tick(30)
            
            for evento in pygame.event.get():
                if evento.type == pygame.QUIT:
                    ejecutando = False
                
                if evento.type == pygame.KEYDOWN:
                    movido = False
                    
                    if evento.key == pygame.K_LEFT:
                        movido = self.mover_izquierda()
                    elif evento.key == pygame.K_RIGHT:
                        movido = self.mover_derecha()
                    elif evento.key == pygame.K_UP:
                        movido = self.mover_arriba()
                    elif evento.key == pygame.K_DOWN:
                        movido = self.mover_abajo()
                    
                    if movido:
                        self.agregar_numero()
                        self.dibujar()
                        
                        if self.game_over():
                            print(f"Game Over! Puntuación: {self.puntuacion}")
                            ejecutando = False
            
            self.dibujar()
        
        pygame.quit()

if __name__ == "__main__":
    juego = Juego2048()

2. Juego de la Serpiente

El tradicional juego de la serpiente donde controlas una serpiente que debe comer alimentos para crecer. El juego termina cuando la serpiente choca con las paredes o consigo misma.

El código implementa la lógica de movimiento,colisión, generación de alimentos y puntuación.

import pygame
import random
import sys

pygame.init()

ANCHO = 640
ALTO = 480
TAMAÑO_BLOQUE = 20

NEGRO = (0, 0, 0)
BLANCO = (255, 255, 255)
VERDE = (0, 255, 0)
ROJO = (255, 0, 0)
AZUL = (0, 0, 255)

class Serpiente:
    def __init__(self):
        self.pantalla = pygame.display.set_mode((ANCHO, ALTO))
        pygame.display.set_caption("Serpiente Clásica")
        self.reloj = pygame.time.Clock()
        self.posicion = [[100, 100], [80, 100], [60, 100]]
        self.direccion = 'DERECHA'
        self.direccion_anterior = 'DERECHA'
        self.comida = None
        self.puntuacion = 0
        self.generar_comida()
        self.juego_activo = False
        self.juego_terminado = False
        self.mostrar_pantalla_inicio()
    
    def generar_comida(self):
        columna_vacia = ANCHO // TAMAÑO_BLOQUE
        fila_vacia = ALTO // TAMAÑO_BLOQUE
        
        posiciones_vacias = [(x, y) for x in range(columna_vacia) 
                            for y in range(fila_vacia)]
        
        posiciones_cuerpo = [(pos[0] // TAMAÑO_BLOQUE, pos[1] // TAMAÑO_BLOQUE) 
                            for pos in self.posicion]
        
        for pos in posiciones_cuerpo:
            if pos in posiciones_vacias:
                posiciones_vacias.remove(pos)
        
        if posiciones_vacias:
            x, y = random.choice(posiciones_vacias)
            self.comida = [x * TAMAÑO_BLOQUE, y * TAMAÑO_BLOQUE]
    
    def mover(self):
        cabeza = self.posicion[-1][:]
        movimientos = {
            'ARRIBA': [0, -TAMAÑO_BLOQUE],
            'ABAJO': [0, TAMAÑO_BLOQUE],
            'IZQUIERDA': [-TAMAÑO_BLOQUE, 0],
            'DERECHA': [TAMAÑO_BLOQUE, 0]
        }
        
        movimiento = movimientos[self.direccion]
        cabeza[0] += movimiento[0]
        cabeza[1] += movimiento[1]
        
        self.posicion.append(cabeza)
        
        if self.comida and cabeza == self.comida:
            self.puntuacion += 10
            self.generar_comida()
        else:
            self.posicion.pop(0)
    
    def verificar_colision(self):
        cabeza = self.posicion[-1]
        
        # Colisión con paredes
        if cabeza[0] < 0 or cabeza[0] >= ANCHO or cabeza[1] < 0 or cabeza[1] >= ALTO:
            return True
        
        # Colisión con el cuerpo
        for segmento in self.posicion[:-1]:
            if cabeza == segmento:
                return True
        
        return False
    
    def cambiar_direccion(self, nueva_direccion):
        opuesto = {
            'ARRIBA': 'ABAJO',
            'ABAJO': 'ARRIBA',
            'IZQUIERDA': 'DERECHA',
            'DERECHA': 'IZQUIERDA'
        }
        
        if nueva_direccion != opuesto.get(self.direccion_anterior):
            self.direccion = nueva_direccion
    
    def dibujar(self):
        self.pantalla.fill(NEGRO)
        
        # Dibujar comida
        if self.comida:
            pygame.draw.rect(self.pantalla, ROJO, 
                           (self.comida[0], self.comida[1], TAMAÑO_BLOQUE, TAMAÑO_BLOQUE))
        
        # Dibujar serpiente
        for i, segmento in enumerate(self.posicion):
            color = VERDE if i == len(self.posicion) - 1 else (0, 200, 0)
            pygame.draw.rect(self.pantalla, color,
                           (segmento[0], segmento[1], TAMAÑO_BLOQUE, TAMAÑO_BLOQUE))
            pygame.draw.rect(self.pantalla, NEGRO,
                           (segmento[0], segmento[1], TAMAÑO_BLOQUE, TAMAÑO_BLOQUE), 1)
        
        # Mostrar puntuación
        fuente = pygame.font.SysFont('arial', 24)
        texto = fuente.render(f'Puntuación: {self.puntuacion}', True, BLANCO)
        self.pantalla.blit(texto, (10, 10))
        
        pygame.display.flip()
    
    def mostrar_pantalla_inicio(self):
        fuente_titulo = pygame.font.SysFont('arial', 48)
        fuente_opciones = pygame.font.SysFont('arial', 24)
        
        while True:
            self.pantalla.fill(NEGRO)
            
            titulo = fuente_titulo.render("SNAKE", True, VERDE)
            texto_iniciar = fuente_opciones.render("Presiona ESPACIO para iniciar", True, BLANCO)
            texto_salir = fuente_opciones.render("Presiona ESC para salir", True, BLANCO)
            
            self.pantalla.blit(titulo, (ANCHO//2 - titulo.get_width()//2, ALTO//3))
            self.pantalla.blit(texto_iniciar, (ANCHO//2 - texto_iniciar.get_width()//2, ALTO//2))
            self.pantalla.blit(texto_salir, (ANCHO//2 - texto_salir.get_width()//2, ALTO//2 + 40))
            
            pygame.display.flip()
            
            for evento in pygame.event.get():
                if evento.type == pygame.QUIT:
                    pygame.quit()
                    sys.exit()
                
                if evento.type == pygame.KEYDOWN:
                    if evento.key == pygame.K_ESCAPE:
                        pygame.quit()
                        sys.exit()
                    elif evento.key == pygame.K_SPACE:
                        self.iniciar_juego()
    
    def iniciar_juego(self):
        self.posicion = [[100, 100], [80, 100], [60, 100]]
        self.direccion = 'DERECHA'
        self.puntuacion = 0
        self.generar_comida()
        self.juego_activo = True
        self.juego_terminado = False
        
        while self.juego_activo:
            self.reloj.tick(10)
            
            for evento in pygame.event.get():
                if evento.type == pygame.QUIT:
                    self.juego_activo = False
                    pygame.quit()
                    sys.exit()
                
                if evento.type == pygame.KEYDOWN:
                    self.direccion_anterior = self.direccion
                    
                    if evento.key == pygame.K_UP or evento.key == pygame.K_w:
                        self.cambiar_direccion('ARRIBA')
                    elif evento.key == pygame.K_DOWN or evento.key == pygame.K_s:
                        self.cambiar_direccion('ABAJO')
                    elif evento.key == pygame.K_LEFT or evento.key == pygame.K_a:
                        self.cambiar_direccion('IZQUIERDA')
                    elif evento.key == pygame.K_RIGHT or evento.key == pygame.K_d:
                        self.cambiar_direccion('DERECHA')
                    elif evento.key == pygame.K_ESCAPE:
                        self.juego_activo = False
            
            if not self.juego_terminado:
                self.mover()
                
                if self.verificar_colision():
                    self.mostrar_game_over()
                    self.juego_terminado = True
                    pygame.time.wait(2000)
                    self.mostrar_pantalla_inicio()
                    return
            
            self.dibujar()
    
    def mostrar_game_over(self):
        fuente = pygame.font.SysFont('arial', 48)
        texto = fuente.render("GAME OVER", True, ROJO)
        
        fuente_puntos = pygame.font.SysFont('arial', 24)
        puntos = fuente_puntos.render(f"Puntuación final: {self.puntuacion}", True, BLANCO)
        
        self.pantalla.blit(texto, (ANCHO//2 - texto.get_width()//2, ALTO//2 - 30))
        self.pantalla.blit(puntos, (ANCHO//2 - puntos.get_width()//2, ALTO//2 + 20))
        pygame.display.flip()

if __name__ == "__main__":
    juego = Serpiente()

3. Tetris

El clásico juego de Tetris implementado con Pygame. Las piezas caen desde la parte superior y el jugador debe organizarlas para completar líneas horizontales.

Las piezas tienen diferentes formas (I, J, L, O, S, T, Z) y pueden rotar. El juego detecta cuando las piezas tocan el fondo o堆积 otras piezas.

import pygame
import random

pygame.init()

ANCHO_CELDA = 30
COLUMNAS = 10
FILAS = 20
ANCHO_VENTANA = ANCHO_CELDA * COLUMNAS + 150
ALTO_VENTANA = ANCHO_CELDA * FILAS

COLOR_FONDO = (20, 20, 30)
COLOR_REJILLA = (40, 40, 50)
BLANCO = (255, 255, 255)
NEGRO = (0, 0, 0)

PALETA_COLORES = [
    (0, 255, 255), (0, 0, 255), (255, 165, 0),
    (255, 255, 0), (0, 255, 0), (128, 0, 128), (255, 0, 0)
]

class Pieza:
    FORMAS = {
        'I': [[1, 1, 1, 1]],
        'J': [[1, 0, 0], [1, 1, 1]],
        'L': [[0, 0, 1], [1, 1, 1]],
        'O': [[1, 1], [1, 1]],
        'S': [[0, 1, 1], [1, 1, 0]],
        'T': [[0, 1, 0], [1, 1, 1]],
        'Z': [[1, 1, 0], [0, 1, 1]]
    }
    
    def __init__(self):
        self.tipo = random.choice(list(self.FORMAS.keys()))
        self.forma = [fila[:] for fila in self.FORMAS[self.tipo]]
        self.color = random.choice(PALETA_COLORES)
        self.x = COLUMNAS // 2 - len(self.forma[0]) // 2
        self.y = 0
    
    def rotar(self):
        filas = len(self.forma)
        columnas = len(self.forma[0])
        rotada = [[self.forma[filas - 1 - j][i] for j in range(filas)] 
                 for i in range(columnas)]
        return rotada

class Tetris:
    def __init__(self):
        self.pantalla = pygame.display.set_mode((ANCHO_VENTANA, ALTO_VENTANA))
        pygame.display.set_caption("Tetris")
        self.reloj = pygame.time.Clock()
        self.tablero = [[None for _ in range(COLUMNAS)] for _ in range(FILAS)]
        self.pieza_actual = Pieza()
        self.puntuacion = 0
        self.nivel = 1
        self.lineas = 0
        self.game_over = False
        self.tiempo_caida = 500
        self.ultimo_movimiento = pygame.time.get_ticks()
        self.ejecutar()
    
    def colision(self, pieza, dx=0, dy=0, forma=None):
        if forma is None:
            forma = pieza.forma
        
        for y, fila in enumerate(forma):
            for x, valor in enumerate(fila):
                if valor:
                    nuevo_x = pieza.x + x + dx
                    nuevo_y = pieza.y + y + dy
                    
                    if nuevo_x < 0 or nuevo_x >= COLUMNAS:
                        return True
                    if nuevo_y >= FILAS:
                        return True
                    if nuevo_y >= 0 and self.tablero[nuevo_y][nuevo_x]:
                        return True
        return False
    
    def asegurar_pieza(self):
        for y, fila in enumerate(self.pieza_actual.forma):
            for x, valor in enumerate(fila):
                if valor:
                    pos_y = self.pieza_actual.y + y
                    pos_x = self.pieza_actual.x + x
                    
                    if pos_y < 0:
                        self.game_over = True
                        return
                    
                    self.tablero[pos_y][pos_x] = self.pieza_actual.color
        
        self.eliminar_lineas()
        self.pieza_actual = Pieza()
        
        if self.colision(self.pieza_actual):
            self.game_over = True
    
    def eliminar_lineas(self):
        lineas_eliminadas = 0
        
        for y in range(FILAS - 1, -1, -1):
            if all(self.tablero[y]):
                del self.tablero[y]
                self.tablero.insert(0, [None] * COLUMNAS)
                lineas_eliminadas += 1
        
        if lineas_eliminadas > 0:
            self.lineas += lineas_eliminadas
            self.puntuacion += lineas_eliminadas * 100 * lineas_eliminadas
            self.nivel = self.lineas // 10 + 1
            self.tiempo_caida = max(100, 500 - (self.nivel - 1) * 50)
    
    def mover_pieza(self, dx, dy):
        if not self.colision(self.pieza_actual, dx, dy):
            self.pieza_actual.x += dx
            self.pieza_actual.y += dy
            return True
        return False
    
    def rotar_pieza(self):
        forma_nueva = self.pieza_actual.rotar()
        
        if not self.colision(self.pieza_actual, forma=forma_nueva):
            self.pieza_actual.forma = forma_nueva
        elif not self.colision(self.pieza_actual, -1, forma=forma_nueva):
            self.pieza_actual.x -= 1
            self.pieza_actual.forma = forma_nueva
        elif not self.colision(self.pieza_actual, 1, forma=forma_nueva):
            self.pieza_actual.x += 1
            self.pieza_actual.forma = forma_nueva
    
    def caer_rapido(self):
        if self.mover_pieza(0, 1):
            self.ultimo_movimiento = pygame.time.get_ticks()
    
    def caer_automatico(self):
        ahora = pygame.time.get_ticks()
        if ahora - self.ultimo_movimiento > self.tiempo_caida:
            if not self.mover_pieza(0, 1):
                self.asegurar_pieza()
            self.ultimo_movimiento = ahora
    
    def dibujar(self):
        self.pantalla.fill(COLOR_FONDO)
        
        # Dibujar rejilla
        for x in range(COLUMNAS + 1):
            pygame.draw.line(self.pantalla, COLOR_REJILLA,
                           (x * ANCHO_CELDA, 0), (x * ANCHO_CELDA, ALTO_VENTANA))
        for y in range(FILAS + 1):
            pygame.draw.line(self.pantalla, COLOR_REJILLA,
                           (0, y * ANCHO_CELDA), (ANCHO_VENTANA - 150, y * ANCHO_CELDA))
        
        # Dibujar piezas en el tablero
        for y in range(FILAS):
            for x in range(COLUMNAS):
                if self.tablero[y][x]:
                    pygame.draw.rect(self.pantalla, self.tablero[y][x],
                                   (x * ANCHO_CELDA + 1, y * ANCHO_CELDA + 1,
                                    ANCHO_CELDA - 2, ANCHO_CELDA - 2))
        
        # Dibujar pieza actual
        if not self.game_over:
            for y, fila in enumerate(self.pieza_actual.forma):
                for x, valor in enumerate(fila):
                    if valor:
                        pos_x = (self.pieza_actual.x + x) * ANCHO_CELDA + 1
                        pos_y = (self.pieza_actual.y + y) * ANCHO_CELDA + 1
                        pygame.draw.rect(self.pantalla, self.pieza_actual.color,
                                       (pos_x, pos_y, ANCHO_CELDA - 2, ANCHO_CELDA - 2))
        
        # Panel lateral
        pygame.draw.rect(self.pantalla, (30, 30, 40),
                        (ANCHO_VENTANA - 150, 0, 150, ALTO_VENTANA))
        
        fuente = pygame.font.SysFont('arial', 20)
        
        texto_puntos = fuente.render(f'Puntos: {self.puntuacion}', True, BLANCO)
        texto_nivel = fuente.render(f'Nivel: {self.nivel}', True, BLANCO)
        texto_lineas = fuente.render(f'Líneas: {self.lineas}', True, BLANCO)
        
        self.pantalla.blit(texto_puntos, (ANCHO_VENTANA - 140, 50))
        self.pantalla.blit(texto_nivel, (ANCHO_VENTANA - 140, 100))
        self.pantalla.blit(texto_lineas, (ANCHO_VENTANA - 140, 150))
        
        if self.game_over:
            fuente_grande = pygame.font.SysFont('arial', 32)
            juego_terminado = fuente_grande.render("GAME OVER", True, (255, 0, 0))
            self.pantalla.blit(juego_terminado, 
                             (ANCHO_VENTANA//2 - 80, ALTO_VENTANA//2 - 20))
        
        pygame.display.flip()
    
    def ejecutar(self):
        while True:
            self.reloj.tick(30)
            
            for evento in pygame.event.get():
                if evento.type == pygame.QUIT:
                    pygame.quit()
                    return
                
                if evento.type == pygame.KEYDOWN and not self.game_over:
                    if evento.key == pygame.K_LEFT:
                        self.mover_pieza(-1, 0)
                    elif evento.key == pygame.K_RIGHT:
                        self.mover_pieza(1, 0)
                    elif evento.key == pygame.K_DOWN:
                        self.caer_rapido()
                    elif evento.key == pygame.K_UP:
                        self.rotar_pieza()
                    elif evento.key == pygame.K_SPACE:
                        while self.mover_pieza(0, 1):
                            pass
                        self.asegurar_pieza()
            
            if not self.game_over:
                self.caer_automatico()
            
            self.dibujar()

if __name__ == "__main__":
    juego = Tetris()

4. Juego de Conexión (Link Link)

Un juego de correspondencia donde debes conectar pares de iconos idénticos. La conexión no puede tener más de dos esquinas y no puede pasar a través de otras piezas.

La lógica verifica tres tipos de conexiones: línea recta, una esquina, y dos esquinas. El algoritmo busca el camino más corto entre dos piezas seleccionadas.

import tkinter as tk
from tkinter import messagebox
import random

class JuegoConexion:
    def __init__(self):
        self.ventana = tk.Tk()
        self.ventana.title("Juego de Conexión")
        self.ventana.geometry("500x550")
        
        self.TAMANO_TABLERO = 8
        self.TAMANO_CELDA = 50
        self.SELECT_DELAY = 0.3
        
        self.inicializar_juego()
        self.ventana.mainloop()
    
    def inicializar_juego(self):
        # Generar pares de elementos
        total_celdas = self.TAMANO_TABLERO * self.TAMANO_TABLERO
        num_pares = total_celdas // 2
        
        self.elementos = []
        for i in range(num_pares):
            self.elementos.extend([i, i])
        
        random.shuffle(self.elementos)
        
        # Crear matriz del juego
        self.tablero = [[None for _ in range(self.TAMANO_TABLERO)] 
                       for _ in range(self.TAMANO_TABLERO)]
        
        idx = 0
        for i in range(self.TAMANO_TABLERO):
            for j in range(self.TAMANO_TABLERO):
                self.tablero[i][j] = self.elementos[idx]
                idx += 1
        
        # Estado del juego
        self.seleccion_primera = None
        self.id_seleccion_1 = None
        self.id_seleccion_2 = None
        self.id_linea = None
        
        # Crear interfaz
        self.crear_interfaz()
    
    def crear_interfaz(self):
        # Lienzo para dibujar
        self.lienzo = tk.Canvas(self.ventana, width=450, height=450, bg='#2E8B57')
        self.lienzo.pack(pady=10)
        
        # Dibujar tablero
        self.dibujar_tablero()
        
        # Botones de control
        marco_botones = tk.Frame(self.ventana)
        marco_botones.pack(pady=10)
        
        tk.Button(marco_botones, text="Reiniciar", command=self.reiniciar,
                 width=15).pack(side=tk.LEFT, padx=5)
        tk.Button(marco_botones, text="Mezclar", command=self.mezclar,
                 width=15).pack(side=tk.LEFT, padx=5)
        
        # Etiqueta de estado
        self.etiqueta_estado = tk.Label(self.ventana, text="", font=('Arial', 12))
        self.etiqueta_estado.pack()
    
    def dibujar_tablero(self):
        self.lienzo.delete("all")
        self.botones_tablero = [[None for _ in range(self.TAMANO_TABLERO)] 
                                for _ in range(self.TAMANO_TABLERO)]
        
        for i in range(self.TAMANO_TABLERO):
            for j in range(self.TAMANO_TABLERO):
                x1 = j * self.TAMANO_CELDA + 25
                y1 = i * self.TAMANO_CELDA + 25
                x2 = x1 + self.TAMANO_CELDA - 2
                y2 = y1 + self.TAMANO_CELDA - 2
                
                if self.tablero[i][j] is not None:
                    color = self.obtener_color(self.tablero[i][j])
                    rect = self.lienzo.create_rectangle(x1, y1, x2, y2, 
                                                       fill=color, outline='white', width=2)
                    self.botones_tablero[i][j] = rect
                    
                    # Bind del clic
                    self.lienzo.tag_bind(rect, '<Button-1>', 
                                        lambda e, x=i, y=j: self.al_hacer_clic(x, y))
    
    def obtener_color(self, indice):
        colores = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4',
                  '#FFEAA7', '#DDA0DD', '#98D8C8', '#F7DC6F']
        return colores[indice % len(colores)]
    
    def al_hacer_clic(self, fila, columna):
        if self.tablero[fila][columna] is None:
            return
        
        if self.seleccion_primera is None:
            # Primera selección
            self.seleccion_primera = (fila, columna)
            self.id_seleccion_1 = self.botones_tablero[fila][columna]
            self.lienzo.itemconfig(self.id_seleccion_1, outline='yellow', width=4)
            self.etiqueta_estado.config(text="Selecciona la segunda ficha")
        else:
            # Segunda selección
            fila1, col1 = self.seleccion_primera
            fila2, columna2 = columna
            
            if (fila1, col1) == (fila, columna):
                return
            
            self.id_seleccion_2 = self.botones_tablero[fila][columna]
            self.lienzo.itemconfig(self.id_seleccion_2, outline='yellow', width=4)
            
            # Verificar si son del mismo tipo
            if self.tablero[fila1][col1] == self.tablero[fila][columna]:
                # Verificar si se pueden conectar
                if self.verificar_conexion(fila1, col1, fila, columna):
                    # Eliminar fichas después de delay
                    self.etiqueta_estado.config(text="¡Conexión válida!")
                    self.ventana.after(300, lambda: self.eliminar_fichas(
                        fila1, col1, fila, columna))
                else:
                    self.etiqueta_estado.config(text="No se pueden conectar")
                    self.ventana.after(300, self.limpiar_seleccion)
            else:
                self.etiqueta_estado.config(text="Tipos diferentes")
                self.ventana.after(300, self.limpiar_seleccion)
    
    def verificar_conexion(self, x1, y1, x2, y2):
        # Verificar conexión directa
        if self.conexion_directa(x1, y1, x2, y2):
            self.dibujar_linea(x1, y1, x2, y2, [])
            return True
        
        # Verificar conexión con una esquina
        if self.conexion_una_esquina(x1, y1, x2, y2):
            return True
        
        # Verificar conexión con dos esquinas
        if self.conexion_dos_esquinas(x1, y1, x2, y2):
            return True
        
        return False
    
    def conexion_directa(self, x1, y1, x2, y2):
        # Misma fila
        if x1 == x2:
            for y in range(min(y1, y2) + 1, max(y1, y2)):
                if self.tablero[x1][y] is not None:
                    return False
            return True
        
        # Misma columna
        if y1 == y2:
            for x in range(min(x1, x2) + 1, max(x1, x2)):
                if self.tablero[x][y1] is not None:
                    return False
            return True
        
        return False
    
    def conexion_una_esquina(self, x1, y1, x2, y2):
        # Esquina 1: (x1, y2)
        if self.tablero[x1][y2] is None:
            if self.conexion_directa(x1, y1, x1, y2) and self.conexion_directa(x1, y2, x2, y2):
                self.dibujar_linea(x1, y1, x1, y2, [(x2, y2)])
                return True
        
        # Esquina 2: (x2, y1)
        if self.tablero[x2][y1] is None:
            if self.conexion_directa(x1, y1, x2, y1) and self.conexion_directa(x2, y1, x2, y2):
                self.dibujar_linea(x1, y1, x2, y1, [(x2, y2)])
                return True
        
        return False
    
    def conexion_dos_esquinas(self, x1, y1, x2, y2):
        # Buscar fila vacía para conexión horizontal
        for y in range(self.TAMANO_TABLERO):
            if y == y1 or y == y2:
                continue
            
            if (self.tablero[x1][y] is None and self.tablero[x2][y] is None and
                self.conexion_directa(x1, y1, x1, y) and
                self.conexion_directa(x1, y, x2, y) and
                self.conexion_directa(x2, y, x2, y2)):
                self.dibujar_linea(x1, y1, x1, y, [(x1, y), (x2, y)])
                return True
        
        # Buscar columna vacía para conexión vertical
        for x in range(self.TAMANO_TABLERO):
            if x == x1 or x == x2:
                continue
            
            if (self.tablero[x][y1] is None and self.tablero[x][y2] is None and
                self.conexion_directa(x1, y1, x, y1) and
                self.conexion_directa(x, y1, x, y2) and
                self.conexion_directa(x, y2, x2, y2)):
                self.dibujar_linea(x1, y1, x, y1, [(x, y1), (x, y2)])
                return True
        
        return False
    
    def dibujar_linea(self, x1, y1, x2, y2, puntos_intermedios):
        coords = []
        
        # Convertir a coordenadas de píxeles
        coords.append(y1 * self.TAMANO_CELDA + 25 + self.TAMANO_CELDA // 2)
        coords.append(x1 * self.TAMANO_CELDA + 25 + self.TAMANO_CELDA // 2)
        
        for px, py in puntos_intermedios:
            coords.append(py * self.TAMANO_CELDA + 25 + self.TAMANO_CELDA // 2)
            coords.append(px * self.TAMANO_CELDA + 25 + self.TAMANO_CELDA // 2)
        
        coords.append(y2 * self.TAMANO_CELDA + 25 + self.TAMANO_CELDA // 2)
        coords.append(x2 * self.TAMANO_CELDA + 25 + self.TAMANO_CELDA // 2)
        
        self.id_linea = self.lienzo.create_line(coords, fill='red', width=3)
    
    def eliminar_fichas(self, x1, y1, x2, y2):
        # Eliminar línea
        if self.id_linea:
            self.lienzo.delete(self.id_linea)
            self.id_linea = None
        
        # Eliminar fichas
        self.tablero[x1][y1] = None
        self.tablero[x2][y2] = None
        
        # Redibujar sin las fichas eliminadas
        self.limpiar_seleccion()
        self.dibujar_tablero()
        
        # Verificar si ganó
        if self.verificar_victoria():
            messagebox.showinfo("¡Felicidades!", "¡Has completado el juego!")
    
    def limpiar_seleccion(self):
        if self.id_seleccion_1:
            self.lienzo.itemconfig(self.id_seleccion_1, outline='white', width=2)
        if self.id_seleccion_2:
            self.lienzo.itemconfig(self.id_seleccion_2, outline='white', width=2)
        
        self.seleccion_primera = None
        self.id_seleccion_1 = None
        self.id_seleccion_2 = None
        self.etiqueta_estado.config(text="")
    
    def verificar_victoria(self):
        for fila in self.tablero:
            for celda in fila:
                if celda is not None:
                    return False
        return True
    
    def reiniciar(self):
        self.inicializar_juego()
    
    def mezirar(self):
        elementos_no_nulos = [elem for fila in self.tablero for elem in fila if elem is not None]
        random.shuffle(elementos_no_nulos)
        
        idx = 0
        for i in range(self.TAMANO_TABLERO):
            for j in range(self.TAMANO_TABLERO):
                if self.tablero[i][j] is not None:
                    self.tablero[i][j] = elementos_no_nulos[idx]
                    idx += 1
        
        self.dibujar_tablero()
        self.limpiar_seleccion()

if __name__ == "__main__":
    juego = JuegoConexion()

Estos proyectos cubren diferentes aspectos del desarrollo en Python: desde aplicaciones de interfaz gráfica básicas hasta juegos interactivos. Cada proyecto puede expandirse y mejorarse según las necesidades específicas del desarrollador.

Etiquetas: Python tkinter pygame interfaz-gráfica juegos-python

Publicado el 6-30 16:34