fix: reset grid column and row configurations before re-configuring and update README.
This commit is contained in:
parent
afac3609cb
commit
f0dc49b62e
250
README.md
250
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
|
||||
|
||||
<table>
|
||||
|
|
|
|||
7
app.py
7
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')
|
||||
|
|
|
|||
Loading…
Reference in New Issue