177 lines
6.8 KiB
Python
177 lines
6.8 KiB
Python
import threading
|
|
import time
|
|
import random
|
|
import tkinter as tk
|
|
from tkinter import messagebox
|
|
|
|
# Definición de los recursos (Zonas de la Pista)
|
|
NUM_LOCKS = 5
|
|
# Crea una lista de RLock que representan las zonas de la pista (recursos)
|
|
TRACK_LOCKS = [threading.RLock() for _ in range(NUM_LOCKS)]
|
|
|
|
class Camel(threading.Thread):
|
|
def __init__(self, name, track_length, root_app, status_label, emoji):
|
|
super().__init__()
|
|
self.name = name
|
|
self.track_length = track_length
|
|
self.distance = 0
|
|
self.is_running = True
|
|
self.root_app = root_app
|
|
self.status_label = status_label
|
|
self.emoji = emoji
|
|
# No necesitamos self.winner, usamos root_app.winner_name
|
|
|
|
def run(self):
|
|
# La simulación de la carrera
|
|
while self.distance < self.track_length and self.is_running:
|
|
|
|
# --- Lógica de Sincronización (Adquisición Ordenada de Locks) ---
|
|
|
|
# El camello necesita dos zonas adyacentes para avanzar
|
|
lock_id_1 = self.distance % NUM_LOCKS
|
|
lock_id_2 = (self.distance + 1) % NUM_LOCKS
|
|
|
|
# Regla para PREVENIR DEADLOCK: Adquirir el lock con el ID más bajo primero.
|
|
lock_a = TRACK_LOCKS[min(lock_id_1, lock_id_2)]
|
|
lock_b = TRACK_LOCKS[max(lock_id_1, lock_id_2)]
|
|
|
|
# Intento de adquisición ordenada de los recursos
|
|
with lock_a:
|
|
self.update_ui(f"{self.name} asegurando zona {min(lock_id_1, lock_id_2)}...", message_only=True)
|
|
time.sleep(random.uniform(0.01, 0.05))
|
|
|
|
with lock_b:
|
|
|
|
# Movimiento: incrementar la distancia (simula el avance)
|
|
move_distance = random.randint(1, 3)
|
|
self.distance += move_distance
|
|
|
|
if self.distance > self.track_length:
|
|
self.distance = self.track_length
|
|
|
|
# Comprobar si ha ganado DENTRO de la sección crítica
|
|
if self.distance >= self.track_length and self.root_app.winner_name is None:
|
|
|
|
# El primero en llegar establece el ganador y detiene a todos
|
|
self.root_app.winner_name = self.name
|
|
self.is_running = False
|
|
self.root_app.after(0, lambda: self.root_app.show_winner(self.name))
|
|
|
|
# Detener el resto de hilos
|
|
for thread in self.root_app.camel_threads:
|
|
if thread.name != self.name:
|
|
thread.is_running = False
|
|
|
|
# Actualizar la UI con la nueva posición después de liberarse de los locks
|
|
self.update_ui(f"{self.name} avanzando.")
|
|
time.sleep(random.uniform(0.1, 0.5))
|
|
|
|
# --- Lógica al finalizar el hilo ---
|
|
if self.distance >= self.track_length:
|
|
self.update_ui(f"¡{self.name} ha cruzado la meta!", final=True)
|
|
else:
|
|
self.update_ui(f"{self.name} detenido.", final=True)
|
|
|
|
|
|
def update_ui(self, message, final=False, message_only=False):
|
|
"""Actualiza la interfaz de usuario de forma segura."""
|
|
|
|
# 1. Crear la representación visual de la pista
|
|
# 40 espacios representan la pista visual en la etiqueta
|
|
track_display_length = 40
|
|
|
|
# Calcular la posición del camello en la pista virtual (0 a track_display_length)
|
|
position_on_display = int((self.distance / self.track_length) * track_display_length)
|
|
|
|
# Asegurarse de que no se salga del inicio
|
|
if position_on_display < 0:
|
|
position_on_display = 0
|
|
|
|
# Construir la cadena de la pista
|
|
|
|
# Espacios de pista antes del camello
|
|
pre_camel_track = "░" * position_on_display
|
|
|
|
# Espacios de pista después del camello
|
|
post_camel_length = track_display_length - len(pre_camel_track) - len(self.emoji)
|
|
if post_camel_length < 0:
|
|
post_camel_length = 0
|
|
|
|
post_camel_track = "░" * post_camel_length
|
|
|
|
# Ensamblar la pista visual
|
|
track_str = pre_camel_track + self.emoji + post_camel_track
|
|
|
|
# Si el camello ha ganado, el emoji siempre debe estar al final
|
|
if final and self.distance >= self.track_length:
|
|
track_str = "░" * (track_display_length - len(self.emoji)) + self.emoji
|
|
|
|
# 2. Actualizar el mensaje de actividad general (Estado 1)
|
|
self.root_app.after(0, self.root_app.update_activity_status, message)
|
|
|
|
if message_only:
|
|
return
|
|
|
|
# 3. Mensaje a mostrar en la etiqueta de su camello
|
|
display_message = f"{self.name}\n[{track_str}]"
|
|
|
|
if final and self.distance >= self.track_length:
|
|
display_message = f"🎉 ¡HA GANADO {self.name}! 🎉\n[{track_str}]"
|
|
|
|
elif final:
|
|
display_message = f"🛑 {self.name} detenido antes de meta."
|
|
|
|
|
|
# 4. Actualizar la UI
|
|
self.root_app.after(0, self.status_label.config, {'text': display_message})
|
|
|
|
|
|
def start_camel_game(root_app, track_length=50):
|
|
"""Inicializa y comienza la carrera de camellos."""
|
|
|
|
camel_data = [
|
|
{"name": "Sahara Runner", "emoji": "🐪"},
|
|
{"name": "Dune Master", "emoji": "🐫"},
|
|
{"name": "Oasis King", "emoji": "👑"},
|
|
{"name": "Mirage Express", "emoji": "🏎️"}
|
|
]
|
|
|
|
root_app.camel_threads = []
|
|
root_app.winner_name = None
|
|
root_app.clear_camel_game_area()
|
|
root_app.track_length = track_length
|
|
|
|
# Etiqueta que define el inicio y la meta
|
|
tk.Label(
|
|
root_app.frame_game,
|
|
text=f"INICIO \t\t\t\t\t\t\t\t\t\t META",
|
|
font=("Consolas", 12, "bold"),
|
|
fg='#006400',
|
|
bg="#D4EDDA"
|
|
).pack(fill='x', padx=10, pady=(5, 0))
|
|
|
|
|
|
# Crear las etiquetas de estado para cada camello en la interfaz
|
|
for data in camel_data:
|
|
# Usamos tk.Label con anchor 'w' para que se alineen bien
|
|
status_label = tk.Label(
|
|
root_app.frame_game,
|
|
text=f"{data['name']} | 🏁 Iniciando...",
|
|
anchor='w',
|
|
justify=tk.LEFT,
|
|
font=("Consolas", 12),
|
|
bg="#F0F0F0",
|
|
padx=10,
|
|
pady=5,
|
|
relief=tk.RAISED
|
|
)
|
|
status_label.pack(fill='x', padx=10, pady=5)
|
|
|
|
camel = Camel(data['name'], track_length, root_app, status_label, data['emoji'])
|
|
root_app.camel_threads.append(camel)
|
|
|
|
# Iniciar todos los hilos (camellos)
|
|
for camel in root_app.camel_threads:
|
|
camel.start()
|
|
|
|
root_app.after(0, root_app.update_activity_status, "¡LA CARRERA HA COMENZADO! Hilos competidores activos.") |