feat(vista): Refactoriza ventana principal (main)

Refactoriza la ventana principal para modularizar y
mejorar la estructura.

* Reestructura ventana principal con módulos.
* Integra clases modulares para cada pestaña.
* Corrige errores de inicialización y dependencias.
* Agrega view_scrapping.py con NavegadorPanel.
* Refactoriza musicReproductor.py.
* Modifica trafficMeter.py.
* Modifica getWeather.py.
* Elimina __main__.py.
* Agrega logica/T2/buscaMinas.py con la lógica del juego.
* Agrega vista/ventana_buscaMinas.py para minijuego.
* Corrige menores en Readme.md.
This commit is contained in:
BYolivia 2025-12-06 13:37:51 +01:00
parent 731050242a
commit 71ff931b5c
6 changed files with 536 additions and 117 deletions

View File

@ -9,10 +9,9 @@ python-vlc => pip install python-vlc
bs4 => pip install bs4
requests => pip install requests
## Como Ejecutar ##
> [!NOTE]
> Desde la carpeta anterior
@ -46,6 +45,6 @@ python -m ProyectoGlobal
4. ~~Scraping~~
5. ~~Juego de los camellos~~ / autos de choque / etc. (aplicar resolución de sincronización para evitar problemas de interbloqueos)
5. ~~Juego de los camellos / autos de choque / etc. (aplicar resolución de sincronización para evitar problemas de interbloqueos)~~
6. ~~Música de fondo (reproducción de mp3 o midi)~~

204
logica/T2/buscaMinas.py Normal file
View File

@ -0,0 +1,204 @@
# Módulo: logica/T2/buscaMinas.py
import random
class BuscaMinas:
"""
Lógica de un Buscaminas con regla de 'primer clic seguro' y banderas.
"""
def __init__(self, rows=16, cols=16, mines=40):
self.rows = rows
self.cols = cols
self.mines = mines
self.board = [] # El tablero real (minas: 'M', números: 0-8)
self.revealed = [] # El estado visible (True/False)
self.flagged = [] # Estado de la bandera (True/False)
self.first_click = True # Bandera para detectar el primer clic
self.game_over = False
self.won = False
self.setup_board()
def setup_board(self):
"""Inicializa un tablero vacío y el estado de juego, esperando el primer clic."""
self.board = [[0 for _ in range(self.cols)] for _ in range(self.rows)]
self.revealed = [[False for _ in range(self.cols)] for _ in range(self.rows)]
self.flagged = [[False for _ in range(self.cols)] for _ in range(self.rows)]
self.game_over = False
self.won = False
self.first_click = True
print(f"[Buscaminas] Tablero vacío {self.rows}x{self.cols} listo. Esperando primer clic seguro.")
def _place_mines_safely(self, start_r, start_c):
"""
Coloca las minas *después* del primer clic,
garantizando que la zona 3x3 alrededor de (start_r, start_c) esté libre.
"""
# 1. Definir posiciones prohibidas (3x3 alrededor del clic inicial)
safe_positions = set()
for dr in [-1, 0, 1]:
for dc in [-1, 0, 1]:
nr, nc = start_r + dr, start_c + dc
if 0 <= nr < self.rows and 0 <= nc < self.cols:
safe_positions.add((nr, nc))
available_positions = []
for r in range(self.rows):
for c in range(self.cols):
if (r, c) not in safe_positions:
available_positions.append((r, c))
# 2. Elegir la posición de las minas
mine_positions = random.sample(available_positions, self.mines)
# 3. Colocar Minas
for r, c in mine_positions:
self.board[r][c] = 'M'
# 4. Calcular números adyacentes
for r in range(self.rows):
for c in range(self.cols):
if self.board[r][c] != 'M':
self.board[r][c] = self._count_adjacent_mines(r, c)
print(f"[Buscaminas] Minas y números colocados. Zona de clic ({start_r},{start_c}) segura.")
def _count_adjacent_mines(self, r, c):
"""Cuenta el número de minas en las 8 casillas adyacentes."""
count = 0
for dr in [-1, 0, 1]:
for dc in [-1, 0, 1]:
if dr == 0 and dc == 0:
continue
nr, nc = r + dr, c + dc
if 0 <= nr < self.rows and 0 <= nc < self.cols:
if self.board[nr][nc] == 'M':
count += 1
return count
def toggle_flag(self, r, c):
"""
Alterna el estado de la bandera en una celda.
Solo se permite si la celda no ha sido revelada y el juego no ha terminado.
Retorna True si el estado del tablero se modificó, False en caso contrario.
"""
if self.game_over or self.revealed[r][c]:
return False
# Alternar la bandera
self.flagged[r][c] = not self.flagged[r][c]
return True
def reveal_cell(self, r, c):
"""
Revela una celda al ser clickeada (clic izquierdo).
Retorna: (str, bool) -> (Mensaje, ¿Juego terminado?)
"""
if self.game_over or self.revealed[r][c]:
return "", False
# IMPEDIR REVELAR SI HAY BANDERA COLOCADA
if self.flagged[r][c]:
return "❌ Debes quitar la bandera para revelar la celda.", False
# Si es el primer clic, colocamos las minas de forma segura
if self.first_click:
self._place_mines_safely(r, c)
self.first_click = False
self.revealed[r][c] = True
# Caso 1: Mina
if self.board[r][c] == 'M':
self.game_over = True
return "💥 ¡BOOM! Juego terminado.", True
# Caso 2: Cero (Expansión)
elif self.board[r][c] == 0:
self._expand_zeros(r, c)
# Caso 3: Número (Solo revelar)
# Verificar victoria después de la jugada
if self._check_win():
self.game_over = True
self.won = True
return "🏆 ¡Ganaste! Has revelado todas las casillas seguras.", True
return "", False
def _expand_zeros(self, r, c):
"""Algoritmo de inundación para revelar áreas vacías (BFS)."""
queue = [(r, c)]
while queue:
curr_r, curr_c = queue.pop(0)
for dr in [-1, 0, 1]:
for dc in [-1, 0, 1]:
nr, nc = curr_r + dr, curr_c + dc
if 0 <= nr < self.rows and 0 <= nc < self.cols:
if not self.revealed[nr][nc]:
# Si hay una bandera, no la quitamos, pero no la propagamos
if self.flagged[nr][nc]:
continue
self.revealed[nr][nc] = True
if self.board[nr][nc] == 0:
queue.append((nr, nc))
def _check_win(self):
"""Verifica si todas las casillas no-mina han sido reveladas."""
safe_cells_revealed = 0
total_safe_cells = self.rows * self.cols - self.mines
for r in range(self.rows):
for c in range(self.cols):
if self.revealed[r][c] and self.board[r][c] != 'M':
safe_cells_revealed += 1
return safe_cells_revealed == total_safe_cells
def get_board_state(self):
"""Retorna una matriz con el estado visible del juego (para la UI), incluyendo banderas y el resultado final."""
state = []
for r in range(self.rows):
row = []
for c in range(self.cols):
if self.revealed[r][c]:
# 1. Revelada: muestra el contenido real
row.append(str(self.board[r][c]))
elif self.flagged[r][c] and not self.game_over:
# 2. Bandera (juego en curso)
row.append('F')
elif self.game_over:
# 3. Analizar estado final (solo si el juego terminó)
if self.flagged[r][c]:
if self.board[r][c] == 'M':
# 3a. Bandera CORRECTA
row.append('F+')
else:
# 3b. Bandera INCORRECTA (marcada en celda segura)
row.append('F-')
elif self.board[r][c] == 'M':
# 3c. Mina sin marcar
row.append('M')
else:
# 3d. Celda segura, sin revelar y sin bandera
row.append(' ')
else:
# 4. Celda oculta normal (juego en curso)
row.append(' ')
state.append(row)
return state

View File

@ -1,106 +0,0 @@
import json
import os
# --- NOMBRE DEL ARCHIVO ---
NOMBRE_FICHERO = "radios.json"
def cargar_emisoras():
"""Carga las emisoras existentes desde el archivo, o retorna una lista vacía si no existe."""
if os.path.exists(NOMBRE_FICHERO):
try:
with open(NOMBRE_FICHERO, 'r', encoding='utf-8') as f:
return json.load(f)
except json.JSONDecodeError:
print(f"⚠️ Error al leer el archivo {NOMBRE_FICHERO}. Creando una lista nueva.")
return []
return []
def guardar_emisoras(emisoras):
"""Guarda la lista de emisoras en formato JSON en el archivo."""
try:
with open(NOMBRE_FICHERO, 'w', encoding='utf-8') as f:
# Usamos indent=4 para que el JSON sea legible
json.dump(emisoras, f, indent=4, ensure_ascii=False)
print(f"\n✅ ¡Éxito! El archivo '{NOMBRE_FICHERO}' ha sido guardado.")
except Exception as e:
print(f"\n❌ Error al intentar guardar el archivo: {e}")
def crear_nueva_emisora(emisoras):
"""Pide al usuario los datos de una nueva emisora y la añade a la lista."""
print("\n--- Crear Nueva Emisora ---")
# --- CAMPOS OBLIGATORIOS ---
nombre = input("▶️ Nombre de la radio (ej: Jazz Clásico): ").strip()
url = input("▶️ URL del Stream (ej: http://stream.com/live.mp3): ").strip()
if not nombre or not url:
print("❌ El nombre y la URL son obligatorios. Inténtalo de nuevo.")
return
# --- CAMPOS OPCIONALES ---
pais = input("▶️ País (ej: ES, DE) [Opcional]: ").strip()
genero = input("▶️ Género (ej: Pop, Noticias) [Opcional]: ").strip()
nueva_emisora = {
"nombre": nombre,
"url_stream": url,
"pais": pais if pais else None,
"genero": genero if genero else None
}
emisoras.append(nueva_emisora)
print(f"\n✅ Emisora '{nombre}' añadida a la lista en memoria.")
def mostrar_emisoras(emisoras):
"""Muestra la lista de emisoras actuales en memoria."""
if not emisoras:
print("\nLista de emisoras vacía. Utiliza la opción 1 para añadir una.")
return
print(f"\n--- Emisoras Actuales en Memoria ({len(emisoras)} en total) ---")
for i, e in enumerate(emisoras):
print(f"\nID: {i + 1}")
print(f" Nombre: {e.get('nombre', 'N/D')}")
print(f" URL: {e.get('url_stream', 'N/D')}")
print(f" País: {e.get('pais', 'N/D')}")
print(f" Género: {e.get('genero', 'N/D')}")
print("--------------------------------------------------")
def menu_principal():
"""Función principal que ejecuta el menú interactivo."""
# Cargar las emisoras al iniciar (si el archivo existe)
emisoras_en_memoria = cargar_emisoras()
while True:
print("\n" + "=" * 30)
print("📻 EDITOR DE CONFIGURACIÓN DE RADIOS 📻")
print("=" * 30)
print("1. Crear y Añadir Nueva Emisora")
print("2. Mostrar Emisoras en Memoria")
print(f"3. Guardar Lista en '{NOMBRE_FICHERO}'")
print("4. Salir (Sin guardar los cambios en memoria)")
print("-" * 30)
opcion = input("Elige una opción (1-4): ").strip()
if opcion == '1':
crear_nueva_emisora(emisoras_en_memoria)
elif opcion == '2':
mostrar_emisoras(emisoras_en_memoria)
elif opcion == '3':
guardar_emisoras(emisoras_en_memoria)
elif opcion == '4':
print("\nSaliendo del editor. ¡Hasta pronto!")
break
else:
print("Opción no válida. Por favor, selecciona un número del 1 al 4.")
if __name__ == "__main__":
menu_principal()

View File

@ -0,0 +1,80 @@
# Módulo: logica/auxiliar/add_audio.py
import json
import os
import threading
# 🔑 RUTA CONFIRMADA: Desde la raíz del proyecto
NOMBRE_FICHERO = os.path.join("res", "radios.json")
class RadioManager:
"""
Gestiona la lectura, escritura y adición de emisoras en el archivo radios.json.
Utiliza un Lock para asegurar la seguridad al escribir el archivo.
"""
def __init__(self, filename=NOMBRE_FICHERO):
self.filename = filename
# Bloqueo para operaciones de archivo
self._lock = threading.Lock()
self.emisoras_cargadas = self.cargar_emisoras()
# -------------------------------------------------------------
# GESTIÓN DE ARCHIVO
# -------------------------------------------------------------
def cargar_emisoras(self):
"""Carga las emisoras existentes desde el archivo."""
with self._lock:
if os.path.exists(self.filename):
try:
with open(self.filename, 'r', encoding='utf-8') as f:
return json.load(f)
except json.JSONDecodeError:
print(f"⚠️ Error al leer el archivo {self.filename}. Retornando lista vacía.")
return []
return []
def guardar_emisoras(self):
"""Guarda la lista de emisoras actual en memoria en formato JSON."""
with self._lock:
try:
with open(self.filename, 'w', encoding='utf-8') as f:
json.dump(self.emisoras_cargadas, f, indent=4, ensure_ascii=False)
print(f"\n✅ [RadioManager] Archivo '{self.filename}' guardado.")
return True
except Exception as e:
print(f"\n❌ [RadioManager] Error al intentar guardar el archivo: {e}")
return False
# -------------------------------------------------------------
# GESTIÓN DE DATOS EN MEMORIA
# -------------------------------------------------------------
def get_emisoras(self):
"""Retorna la lista de emisoras cargadas en memoria."""
return self.emisoras_cargadas
def add_radio(self, nombre, url, pais=None, genero=None):
"""
Añade una nueva emisora a la lista en memoria y guarda el archivo.
"""
if not nombre or not url:
print("❌ El nombre y la URL son obligatorios.")
return False
nueva_emisora = {
"nombre": nombre.strip(),
"url_stream": url.strip(),
"pais": pais.strip() if pais else None,
"genero": genero.strip() if genero else None
}
self.emisoras_cargadas.append(nueva_emisora)
print(f"✅ [RadioManager] Emisora '{nombre}' añadida a la lista en memoria.")
# Guarda inmediatamente
self.guardar_emisoras()
return True

View File

@ -14,8 +14,8 @@ from logica.T2.scraping import hacer_scraping
from logica.T2.musicReproductor import MusicReproductor
# --- Módulos de Vistas ---
# ❌ Eliminamos: from vista.central_panel.view_radio import RadioPanel
# 🔑 NUEVA IMPORTACIÓN DE VISTA MODULAR
# 🔑 IMPORTACIÓN DE LA VENTANA SECUNDARIA DEL BUSCAMINAS
from vista.ventana_buscaMinas import VentanaBuscaMinas
from vista.reproductor_controller import ReproductorController
from vista.config import *
@ -37,8 +37,10 @@ class PanelLateral(ttk.Frame):
self.controles_musica = None
self.entrada_superior = None
# 🔑 REFERENCIA A LA VENTANA NO-MODAL DEL BUSCAMINAS
self.buscaminas_window = None
# 🔑 INSTANCIA DE LÓGICA DE MÚSICA T2
# Inicializamos el objeto de la lógica de reproducción aquí
self.music_reproductor = MusicReproductor()
self.configurar_estilos_locales(root)
@ -57,7 +59,7 @@ class PanelLateral(ttk.Frame):
ttk.Separator(self, orient='horizontal').grid(row=4, column=0, sticky="ew", pady=(10, 0))
tk.Frame(self, height=1).grid(row=99, column=0, sticky="nsew")
# 🔑 LLAMADA AL NUEVO CONTROLADOR
# 🔑 LLAMADA AL CONTROLADOR DE MÚSICA
self.crear_controles_musica() # Fila 100
# -------------------------------------------------------------
@ -88,8 +90,9 @@ class PanelLateral(ttk.Frame):
acciones_aplicaciones = [
("Visual Code", abrir_vscode),
("Carrera 🏁", app2_comando),
("App3", lambda: accion_placeholder("App3"))
("Carrera de Camellos 🏁", app2_comando),
# 🔑 VINCULACIÓN DEL BOTÓN APP3 CON LA FUNCIÓN DE LANZAMIENTO
("Juego de Buscaminas 💣", self.manejar_app3)
]
self._crear_bloque_botones(self, titulo="Aplicaciones", acciones=acciones_aplicaciones, grid_row=2)
@ -150,11 +153,35 @@ class PanelLateral(ttk.Frame):
Llama al método 'manejar_inicio_carrera' del Panel Central.
"""
if self.panel_central:
print("Botón App2 presionado. Iniciando Carrera de Camellos en Panel Central...")
print("Botón Carrera presionado. Iniciando Carrera de Camellos en Panel Central...")
self.panel_central.manejar_inicio_carrera()
else:
messagebox.showerror("Error", "El Panel Central no está inicializado.")
# 🔑 FUNCIÓN PARA LANZAR LA VENTANA SECUNDARIA NO-MODAL
def manejar_app3(self):
"""
Lanza la ventana secundaria (Toplevel) del Buscaminas.
Asegura que solo haya una instancia abierta a la vez.
"""
# 1. Verificar si la ventana ya existe y está abierta
if self.buscaminas_window and self.buscaminas_window.winfo_exists():
# Si existe, la traemos al frente y le damos foco
self.buscaminas_window.lift()
print("❌ La ventana Buscaminas ya está abierta. Trayendo al frente.")
return
# 2. Crear y guardar la referencia de la nueva ventana
try:
# Creamos la instancia Toplevel, pasando la ventana principal (root) como parent
self.buscaminas_window = VentanaBuscaMinas(self.root)
print("✅ Ventana Buscaminas lanzada.")
except Exception as e:
messagebox.showerror("Error de Aplicación", f"No se pudo iniciar el Buscaminas: {e}")
print(f"Error al iniciar Buscaminas: {e}")
def manejar_navegacion(self, event=None):
"""
Obtiene el texto de la entrada superior y llama a la función de navegación (abrir navegador externo).
@ -209,7 +236,10 @@ class PanelLateral(ttk.Frame):
ttk.Label(frame_titulo, text=titulo, font=FUENTE_NEGOCIOS).pack(anchor="w", padx=5)
for texto_boton, comando in acciones:
ttk.Button(frame_seccion, text=texto_boton, command=comando, style='Green.TButton').pack(fill="x", pady=5)
# 🔑 CORRECCIÓN FINAL: Todos los botones de acción usarán el estilo verde ('Green.TButton').
style_to_use = 'Green.TButton'
ttk.Button(frame_seccion, text=texto_boton, command=comando, style=style_to_use).pack(fill="x", pady=5)
def set_panel_central_reference(self, panel_central_instance):
"""

212
vista/ventana_buscaMinas.py Normal file
View File

@ -0,0 +1,212 @@
# Módulo: vista/ventana_buscaMinas.py
import tkinter as tk
from tkinter import ttk, messagebox
# 🔑 Importamos la lógica del minijuego
from logica.T2.buscaMinas import BuscaMinas
# 🔑 Importamos la configuración de estilos
from vista.config import *
class VentanaBuscaMinas(tk.Toplevel):
"""
Ventana secundaria (tk.Toplevel) para el Buscaminas.
Es no-modal y permite interactuar con la VentanaPrincipal.
"""
def __init__(self, parent):
# Inicializar como ventana secundaria
super().__init__(parent)
self.title("💣 Buscaminas - App3")
self.geometry("500x600")
self.resizable(False, False)
# 1. Inicializar la lógica del juego (16x16, 40 minas)
self.game = BuscaMinas(rows=16, cols=16, mines=40)
self.buttons = {} # Almacena las referencias a los botones (celdas)
# Variables de control de UI
self.feedback_var = tk.StringVar(value="Haz clic para empezar...")
# 2. Configurar el layout principal
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(1, weight=1)
self.create_widgets()
self.protocol("WM_DELETE_WINDOW", self.on_close) # Manejar el cierre
def create_widgets(self):
"""Crea el marco superior de control y el marco del tablero."""
# --- Marco Superior de Control ---
control_frame = ttk.Frame(self, padding=10)
control_frame.grid(row=0, column=0, sticky="ew")
control_frame.grid_columnconfigure(0, weight=1)
control_frame.grid_columnconfigure(1, weight=1)
# Botón Nuevo Juego (Usamos ttk para botones fuera del tablero)
ttk.Button(control_frame, text="🔄 Nuevo Juego", command=self.reset_game, style='Action.TButton').grid(
row=0, column=0, padx=(0, 5), sticky="w")
# Feedback (Mensajes de éxito/derrota)
ttk.Label(control_frame, textvariable=self.feedback_var, font=FUENTE_NEGOCIOS).grid(
row=0, column=1, padx=(5, 0), sticky="e")
# --- Marco del Tablero ---
self.board_frame = ttk.Frame(self, padding=5, style='TFrame')
self.board_frame.grid(row=1, column=0, sticky="nsew")
self.draw_board()
def draw_board(self):
"""Crea la matriz de botones que representan las celdas del buscaminas."""
# Limpiar y reconfigurar el frame
for widget in self.board_frame.winfo_children():
widget.destroy()
self.buttons = {}
# Asegurar que los botones se expandan uniformemente
for i in range(self.game.rows):
self.board_frame.grid_rowconfigure(i, weight=1)
for j in range(self.game.cols):
self.board_frame.grid_columnconfigure(j, weight=1)
for r in range(self.game.rows):
for c in range(self.game.cols):
# USANDO tk.Button ESTÁNDAR para la configuración dinámica de colores
btn = tk.Button(
self.board_frame,
text="",
width=2,
height=1,
relief=tk.RAISED,
background='gray70',
activebackground='gray50',
# Clic Izquierdo (Button-1): revelar (usando 'command')
command=lambda row=r, col=c: self.handle_click(row, col)
)
btn.grid(row=r, column=c, sticky="nsew", padx=1, pady=1)
self.buttons[(r, c)] = btn
# VINCULAR CLIC DERECHO (Button-3) a handle_flag
btn.bind("<Button-3>", lambda event, row=r, col=c: self.handle_flag(row, col))
def handle_click(self, r, c):
"""Maneja el clic Izquierdo para revelar."""
if self.game.game_over:
return
message, game_over = self.game.reveal_cell(r, c)
# 1. Actualizar el tablero con el nuevo estado
self.update_ui_board()
# 2. Manejar el estado final del juego
if game_over:
self.feedback_var.set(message)
self.show_end_game()
else:
self.feedback_var.set("¡Cuidado! Minando...")
# Mostrar mensaje si la lógica impidió la revelación (ej: por bandera)
if message:
self.feedback_var.set(message)
def handle_flag(self, r, c):
"""
Maneja el clic Derecho para colocar/quitar la bandera.
"""
if self.game.game_over:
return
# Llama a la lógica para alternar la bandera
if self.game.toggle_flag(r, c):
self.update_ui_board() # Solo actualizamos si el estado de la bandera cambió
def update_ui_board(self):
"""Recorre el estado lógico y actualiza el texto, color y estado de los botones."""
state = self.game.get_board_state()
revealed_bg = 'lightgray'
for r in range(self.game.rows):
for c in range(self.game.cols):
text = state[r][c]
btn = self.buttons[(r, c)]
# Estado por defecto (oculto, levantado)
btn.config(text="", background='gray70', foreground='black', relief=tk.RAISED, state=tk.NORMAL)
if text == 'F':
# Bandera (juego en curso)
btn.config(text="🚩", foreground='blue', relief=tk.RAISED, state=tk.NORMAL)
# 🔑 NUEVO: Manejo de estado final de banderas
elif self.game.game_over:
btn.config(state=tk.DISABLED) # Deshabilitar todos los botones
if text == 'F+':
# Bandera Correcta (Mina marcada)
btn.config(text="", background='green', foreground='white', relief=tk.RAISED)
elif text == 'F-':
# Bandera Incorrecta (Celda segura marcada)
btn.config(text="", background='orange', foreground='white', relief=tk.RAISED)
elif text == 'M':
# Mina sin marcar (se revela al final)
btn.config(text="💣", background='black', foreground='white', relief=tk.SUNKEN)
# El resto de celdas se manejan a continuación si están reveladas.
if self.game.revealed[r][c]:
# Celda revelada
btn.config(relief=tk.SUNKEN, state=tk.DISABLED)
if text == 'M':
# Mina clickeada (rojo, mina detonadora)
btn.config(text="💣", background='red', foreground='white')
elif text != '0':
# Número
btn.config(
text=text,
background=revealed_bg,
foreground=self._get_number_color(int(text))
)
else:
# Cero
btn.config(text="", background=revealed_bg)
def show_end_game(self):
"""Muestra el estado final, revela el tablero completo y deshabilita clics."""
self.update_ui_board()
# Asegurar el deshabilitado final (redundante pero seguro)
for btn in self.buttons.values():
btn.config(state=tk.DISABLED)
def reset_game(self):
"""Reinicia el juego lógico y recrea el tablero visual."""
self.game.setup_board()
self.feedback_var.set("¡A jugar! Encontrando minas en 16x16...")
self.draw_board()
self.update_ui_board()
def _get_number_color(self, number):
"""Retorna un color basado en el número de la mina."""
colors = {
1: 'blue',
2: 'green',
3: 'red',
4: 'purple',
5: 'maroon',
6: 'turquoise',
7: 'black',
8: 'gray'
}
return colors.get(number, 'black')
def on_close(self):
"""Maneja el evento de cierre de la ventana Toplevel."""
print("[Minigame] Cerrando ventana Buscaminas.")
self.destroy()