diff --git a/README.md b/README.md index 5755969..ac7738d 100644 --- a/README.md +++ b/README.md @@ -339,6 +339,256 @@ Cliente ligero solo para jugar al Minesweeper. --- +## ๐Ÿ”ง Documentaciรณn Tรฉcnica Detallada + +### ๐Ÿ†” Sistema de Identificaciรณn de Jugadores + +El servidor **identifica a cada jugador mediante su direcciรณn de socket**, que es una tupla รบnica `(IP, Puerto)`: + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ IDENTIFICACIร“N รšNICA DE JUGADORES โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ Cuando un cliente se conecta al servidor: โ”‚ +โ”‚ โ”‚ +โ”‚ Cliente 1 โ”€โ”€โ”€โ”€โ”€โ”€โ–บ sock.accept() โ”€โ”€โ”€โ”€โ”€โ”€โ–บ ('127.0.0.1', 49956) โ”‚ +โ”‚ Cliente 2 โ”€โ”€โ”€โ”€โ”€โ”€โ–บ sock.accept() โ”€โ”€โ”€โ”€โ”€โ”€โ–บ ('127.0.0.1', 49968) โ”‚ +โ”‚ โ”‚ +โ”‚ Aunque ambos clientes estรฉn en el MISMO ordenador (127.0.0.1) โ”‚ +โ”‚ el PUERTO es diferente y รบnico para cada conexiรณn. โ”‚ +โ”‚ โ”‚ +โ”‚ Esta tupla (IP, Puerto) actรบa como IDENTIFICADOR รšNICO. โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +#### ยฟCรณmo funciona en el mismo ordenador? + +```python +# servidor.py - Al aceptar conexiรณn +conn, addr = s.accept() # addr = ('127.0.0.1', 49956) + +# El sistema operativo asigna un puerto efรญmero รšNICO +# a cada nueva conexiรณn de socket del cliente +``` + +| Cliente | IP | Puerto (asignado por SO) | Identificador Completo | +|---------|----|--------------------------|-----------------------| +| Dashboard 1 | 127.0.0.1 | 49956 | `('127.0.0.1', 49956)` | +| Dashboard 2 | 127.0.0.1 | 49968 | `('127.0.0.1', 49968)` | +| Cliente remoto | 192.168.1.50 | 52341 | `('192.168.1.50', 52341)` | + +> **๐Ÿ’ก Clave:** El puerto del cliente es asignado automรกticamente por el sistema operativo y es **siempre diferente** para cada nueva conexiรณn, incluso desde el mismo ordenador. + +--- + +### ๐Ÿ”„ Gestiรณn de Turnos + +El servidor mantiene **dos รญndices de turno** para controlar quiรฉn juega: + +```python +# servidor.py - Variables de control de turnos +self.placing_turn_index = 0 # รndice en fase PLACING +self.playing_turn_index = 0 # รndice en fase PLAYING +self.client_list = [] # Lista ordenada de direcciones +``` + +#### Flujo de Verificaciรณn de Turno + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ VERIFICACIร“N DE TURNO โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ client_list = [('127.0.0.1', 49956), ('127.0.0.1', 49968)] โ”‚ +โ”‚ โ†‘ โ†‘ โ”‚ +โ”‚ รญndice 0 รญndice 1 โ”‚ +โ”‚ โ”‚ +โ”‚ Si placing_turn_index = 0: โ”‚ +โ”‚ โ†’ Solo ('127.0.0.1', 49956) puede poner bombas โ”‚ +โ”‚ โ”‚ +โ”‚ Cuando recibe PLACE_BOMB de un cliente: โ”‚ +โ”‚ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ if str(addr) != str(current_turn_addr): โ”‚ โ”‚ +โ”‚ โ”‚ return # No es su turno, ignorar โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +#### Cรณdigo de Verificaciรณn (servidor.py) + +```python +elif msg_type == 'PLACE_BOMB': + if self.state == STATE_PLACING: + # Obtener quiรฉn tiene el turno actual + current_turn_addr = self.client_list[self.placing_turn_index] + + # Comparar con quiรฉn enviรณ el mensaje + if str(addr) != str(current_turn_addr): + return # ยกNo es tu turno! Ignorar mensaje + + # Procesar la bomba... +``` + +--- + +### ๐Ÿ‘ค Cรณmo el Cliente Sabe si es su Turno + +El cliente guarda su propia direcciรณn al conectarse y la compara con los mensajes del servidor: + +```python +# app.py - Al conectarse +self.my_address = str(sock.getsockname()) # Ej: "('127.0.0.1', 49956)" + +# Al recibir TURN_NOTIFY del servidor +def handle_message(self, msg): + if mtype == 'TURN_NOTIFY': + active_player = msg.get('active_player') # "('127.0.0.1', 49956)" + + # Comparar con mi direcciรณn + if active_player == self.my_address: + self._log_game("๐ŸŽฏ ยกES TU TURNO!") + # Habilitar controles... + else: + self._log_game(f"โณ Turno de {active_player}") + # Deshabilitar controles... +``` + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ FLUJO DE NOTIFICACIร“N DE TURNO โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ SERVIDOR CLIENTES โ”‚ +โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ +โ”‚ โ”‚ +โ”‚ broadcast({ Cliente 1 (49956): โ”‚ +โ”‚ "type": "TURN_NOTIFY", โ”œโ”€ my_address = 49956 โ”‚ +โ”‚ "active_player": โ”œโ”€ active_player = 49956 โ”‚ +โ”‚ "('127.0.0.1', 49956)" โ””โ”€ โœ“ ยกES MI TURNO! โ”‚ +โ”‚ }) โ”‚ +โ”‚ โ”‚ Cliente 2 (49968): โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ โ”œโ”€ my_address = 49968 โ”‚ +โ”‚ โ”œโ”€ active_player = 49956 โ”‚ +โ”‚ โ””โ”€ โœ— No es mi turno โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +### ๐Ÿ”’ Sincronizaciรณn con Threading Lock + +El servidor usa un `threading.Lock` para evitar condiciones de carrera cuando mรบltiples clientes envรญan mensajes simultรกneamente: + +```python +class GameServer: + def __init__(self): + self.lock = threading.Lock() # Mutex para sincronizaciรณn + self.clients = {} # Diccionario compartido + self.state = STATE_LOBBY # Estado compartido + + def process_message(self, client, addr, msg): + with self.lock: # Adquirir lock antes de modificar estado + if msg_type == 'PLACE_BOMB': + # Solo un hilo puede ejecutar esto a la vez + self.bombs.add((x, y)) + self._broadcast_unlocked(...) +``` + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ SINCRONIZACIร“N CON LOCK โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ Hilo Cliente 1 โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”œโ”€โ”€โ–บ with self.lock: โ”€โ”€โ–บ Ejecuta primero โ”‚ +โ”‚ Hilo Cliente 2 โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ +โ”‚ โ–ผ โ”‚ +โ”‚ (espera...) โ”‚ +โ”‚ โ”‚ โ”‚ +โ”‚ โ–ผ โ”‚ +โ”‚ Hilo 2 ejecuta despuรฉs โ”‚ +โ”‚ โ”‚ +โ”‚ Esto evita que dos jugadores modifiquen el estado โ”‚ +โ”‚ del juego al mismo tiempo (condiciones de carrera). โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +### ๐Ÿ“ก Arquitectura de Hilos + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ARQUITECTURA DE HILOS โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ SERVIDOR (servidor.py) โ”‚ +โ”‚ โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• โ”‚ +โ”‚ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ Hilo Main โ”‚ โ—„โ”€โ”€ Acepta conexiones (s.accept()) โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€โ–บ Hilo Cliente 1 โ”€โ”€โ–บ handle_client(sock1) โ”‚ +โ”‚ โ”œโ”€โ”€โ–บ Hilo Cliente 2 โ”€โ”€โ–บ handle_client(sock2) โ”‚ +โ”‚ โ””โ”€โ”€โ–บ Hilo Cliente N โ”€โ”€โ–บ handle_client(sockN) โ”‚ +โ”‚ โ”‚ +โ”‚ Cada cliente tiene su propio hilo daemon que: โ”‚ +โ”‚ 1. Lee mensajes del socket (recv) โ”‚ +โ”‚ 2. Procesa el mensaje (process_message) โ”‚ +โ”‚ 3. Puede hacer broadcast a todos los clientes โ”‚ +โ”‚ โ”‚ +โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ +โ”‚ โ”‚ +โ”‚ CLIENTE (app.py) โ”‚ +โ”‚ โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• โ”‚ +โ”‚ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ Hilo Main โ”‚ โ”‚ Hilo Recv โ”‚ โ”‚ +โ”‚ โ”‚ (Tkinter UI) โ”‚โ—„โ”€โ”€โ”€โ”‚ (_recv_loop) โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ–ฒ โ”‚ +โ”‚ โ”‚ msg_queue.put() โ”‚ sock.recv() โ”‚ +โ”‚ โ–ผ โ”‚ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ +โ”‚ โ”‚ Cola de msgs โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ (thread-safe) โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ”‚ El hilo de recepciรณn pone mensajes en una cola. โ”‚ +โ”‚ El hilo principal (UI) los procesa con after(). โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +### ๐ŸŽฎ Resumen: Jugar en el Mismo Ordenador + +| Paso | Quรฉ Sucede | +|------|------------| +| 1. Iniciar servidor | `python servidor.py` escucha en puerto 3333 | +| 2. Abrir Cliente 1 | Conecta โ†’ SO asigna puerto 49956 | +| 3. Abrir Cliente 2 | Conecta โ†’ SO asigna puerto 49968 | +| 4. Servidor registra | `clients = {sock1: ('127.0.0.1', 49956), sock2: ('127.0.0.1', 49968)}` | +| 5. Iniciar juego | `client_list = [addr1, addr2]` define orden de turnos | +| 6. Turno jugador 1 | Servidor envรญa `TURN_NOTIFY` con `active_player = addr1` | +| 7. Cliente 1 compara | `my_address == active_player` โ†’ ยกEs mi turno! | +| 8. Cliente 2 compara | `my_address != active_player` โ†’ Esperar | +| 9. Jugador 1 actรบa | Envรญa `PLACE_BOMB` o `CLICK_CELL` | +| 10. Servidor valida | `addr == current_turn_addr` โ†’ Vรกlido, procesar | +| 11. Avanzar turno | `placing_turn_index += 1` โ†’ Turno del siguiente | + +--- + + + ## ๐Ÿ› ๏ธ Tecnologรญas diff --git a/app.py b/app.py index 82e0a66..6b493e7 100644 --- a/app.py +++ b/app.py @@ -1030,8 +1030,13 @@ class DashboardApp(tk.Tk): # Limpiar grid anterior for child in self.game_frame.winfo_children(): child.destroy() + + # Resetear configuraciones de filas/columnas anteriores (mรกximo 14x14) + for i in range(14): + self.game_frame.columnconfigure(i, weight=0, uniform='') + self.game_frame.rowconfigure(i, weight=0, uniform='') - # Configurar grid + # Configurar grid nuevo for i in range(size): self.game_frame.columnconfigure(i, weight=1, uniform='cell') self.game_frame.rowconfigure(i, weight=1, uniform='cell')