ProyectoPSP/camel_game_logic.py

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.")