docs: Add comprehensive email client documentation and bomb duplicate detection explanation
- Add complete email architecture section with IMAP/SMTP protocol details - Document credential system with Base64 encoding flow - Explain email reading, sending, and IMAP server sync - Add multiple recipient validation and attachment handling - Document visual indicators and error handling - Add bomb duplicate detection with set() data structure - Include 15+ diagrams, code examples, and technical explanations - Expand README from 698 to 1600+ lines
This commit is contained in:
parent
f1906ec18e
commit
1006498c94
220
README.md
220
README.md
|
|
@ -492,6 +492,226 @@ def handle_message(self, msg):
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### 💣 Detección de Bombas Duplicadas
|
||||||
|
|
||||||
|
El servidor usa un **Set de Python** para almacenar las coordenadas de las bombas, lo que garantiza que no haya duplicados.
|
||||||
|
|
||||||
|
#### **Estructura de Datos: `self.bombs = set()`**
|
||||||
|
|
||||||
|
```python
|
||||||
|
# servidor.py - Línea 32
|
||||||
|
class GameServer:
|
||||||
|
def __init__(self):
|
||||||
|
self.bombs = set() # Set de tuplas (x, y)
|
||||||
|
```
|
||||||
|
|
||||||
|
Un `set()` en Python **no permite elementos duplicados**. Si intentas agregar la misma tupla `(x, y)` dos veces, solo se guarda una vez.
|
||||||
|
|
||||||
|
#### **Validación al Colocar Bomba (servidor.py líneas 222-246)**
|
||||||
|
|
||||||
|
```python
|
||||||
|
elif msg_type == 'PLACE_BOMB':
|
||||||
|
if self.state == STATE_PLACING:
|
||||||
|
# 1. Verificar que es el turno del jugador
|
||||||
|
current_turn_addr = self.client_list[self.placing_turn_index]
|
||||||
|
if str(addr) != str(current_turn_addr):
|
||||||
|
return # ❌ No es su turno, ignorar
|
||||||
|
|
||||||
|
x, y = msg['x'], msg['y']
|
||||||
|
|
||||||
|
# 2. ⭐ VERIFICAR SI YA HAY BOMBA EN ESA CASILLA
|
||||||
|
if (x, y) in self.bombs:
|
||||||
|
return # ❌ Ya hay bomba aquí, ignorar clic
|
||||||
|
|
||||||
|
# 3. ✅ Agregar bomba al set
|
||||||
|
self.bombs.add((x, y))
|
||||||
|
self.current_player_bombs_placed += 1
|
||||||
|
|
||||||
|
# 4. Mostrar flash de bomba a todos (1 segundo)
|
||||||
|
self._broadcast_unlocked({
|
||||||
|
"type": "BOMB_flash",
|
||||||
|
"x": x, "y": y,
|
||||||
|
"who": str(addr)
|
||||||
|
})
|
||||||
|
|
||||||
|
# 5. Si el jugador completó sus bombas, pasar al siguiente
|
||||||
|
if self.current_player_bombs_placed >= self.bombs_to_place_per_player:
|
||||||
|
self.placing_turn_index += 1
|
||||||
|
self.next_placement_turn()
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **Flujo Completo de Validación**
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ VALIDACIÓN DE COLOCACIÓN DE BOMBA │
|
||||||
|
├─────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ Jugador 1 hace clic en casilla (2, 1) │
|
||||||
|
│ │ │
|
||||||
|
│ ├──► Mensaje enviado: {"type": "PLACE_BOMB", "x": 2, "y": 1} │
|
||||||
|
│ │ │
|
||||||
|
│ └──► SERVIDOR recibe mensaje │
|
||||||
|
│ │ │
|
||||||
|
│ ├─► VALIDACIÓN 1: ¿Es el turno de este jugador? │
|
||||||
|
│ │ ├─ current_turn_addr = client_list[placing_index] │
|
||||||
|
│ │ └─ if addr != current_turn_addr: return ❌ │
|
||||||
|
│ │ │
|
||||||
|
│ ├─► VALIDACIÓN 2: ¿Ya hay bomba en (2, 1)? │
|
||||||
|
│ │ ├─ if (2, 1) in self.bombs: │
|
||||||
|
│ │ │ return # ❌ Ignorar clic │
|
||||||
|
│ │ └─ self.bombs = {(0,0), (1,1)} → NO contiene (2,1) │
|
||||||
|
│ │ ✅ VÁLIDO │
|
||||||
|
│ │ │
|
||||||
|
│ ├─► AGREGAR BOMBA: │
|
||||||
|
│ │ └─ self.bombs.add((2, 1)) │
|
||||||
|
│ │ → self.bombs = {(0,0), (1,1), (2,1)} │
|
||||||
|
│ │ │
|
||||||
|
│ ├─► BROADCAST a todos: │
|
||||||
|
│ │ └─ {"type": "BOMB_flash", "x": 2, "y": 1} │
|
||||||
|
│ │ │
|
||||||
|
│ └─► INCREMENTAR contador: │
|
||||||
|
│ └─ current_player_bombs_placed += 1 │
|
||||||
|
│ │
|
||||||
|
│ ───────────────────────────────────────────────────────────────── │
|
||||||
|
│ │
|
||||||
|
│ Jugador 1 hace clic OTRA VEZ en (2, 1) por error │
|
||||||
|
│ │ │
|
||||||
|
│ └──► SERVIDOR recibe: {"type": "PLACE_BOMB", "x": 2, "y": 1} │
|
||||||
|
│ │ │
|
||||||
|
│ ├─► VALIDACIÓN 1: ✅ Es su turno │
|
||||||
|
│ │ │
|
||||||
|
│ ├─► VALIDACIÓN 2: ¿Ya hay bomba en (2, 1)? │
|
||||||
|
│ │ └─ if (2, 1) in self.bombs: │
|
||||||
|
│ │ return # ❌ SÍ EXISTE, IGNORAR │
|
||||||
|
│ │ │
|
||||||
|
│ └─► 🚫 NO se procesa el clic │
|
||||||
|
│ 🚫 NO se envía BOMB_flash │
|
||||||
|
│ 🚫 NO se incrementa contador │
|
||||||
|
│ │
|
||||||
|
│ Resultado: El jugador puede hacer clic múltiples veces en la │
|
||||||
|
│ misma casilla, pero solo la primera vez cuenta. │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **¿Por qué usar un Set?**
|
||||||
|
|
||||||
|
| Estructura | Ventaja | Operación `in` |
|
||||||
|
|------------|---------|----------------|
|
||||||
|
| **`set()`** | ✅ No permite duplicados automáticamente | O(1) - Instantáneo |
|
||||||
|
| `list()` | ❌ Permite duplicados, necesita validación manual | O(n) - Lento |
|
||||||
|
| `dict()` | ✅ Claves únicas, pero más memoria | O(1) - Instantáneo |
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Ejemplo de eficiencia
|
||||||
|
|
||||||
|
# ❌ CON LISTA (Lento)
|
||||||
|
bombs_list = [(0,0), (1,1), (2,2)]
|
||||||
|
if (2, 1) in bombs_list: # Revisa TODA la lista: O(n)
|
||||||
|
return
|
||||||
|
|
||||||
|
# ✅ CON SET (Rápido)
|
||||||
|
bombs_set = {(0,0), (1,1), (2,2)}
|
||||||
|
if (2, 1) in bombs_set: # Hash lookup instantáneo: O(1)
|
||||||
|
return
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **Ejemplo Práctico: Ronda 1 con 2 Jugadores**
|
||||||
|
|
||||||
|
```
|
||||||
|
Ronda 1: Grid 3×3, cada jugador coloca 3 bombas
|
||||||
|
|
||||||
|
Estado inicial:
|
||||||
|
self.bombs = set() # Vacío
|
||||||
|
self.bombs_to_place_per_player = 3
|
||||||
|
|
||||||
|
┌───────────────────────────────────────┐
|
||||||
|
│ TURNO JUGADOR 1 │
|
||||||
|
├───────────────────────────────────────┤
|
||||||
|
│ Clic en (0, 0): │
|
||||||
|
│ ├─ (0, 0) in bombs? → NO │
|
||||||
|
│ └─ bombs.add((0, 0)) │
|
||||||
|
│ → bombs = {(0,0)} │
|
||||||
|
│ │
|
||||||
|
│ Clic en (1, 1): │
|
||||||
|
│ ├─ (1, 1) in bombs? → NO │
|
||||||
|
│ └─ bombs.add((1, 1)) │
|
||||||
|
│ → bombs = {(0,0), (1,1)} │
|
||||||
|
│ │
|
||||||
|
│ Clic en (0, 0) por error: │
|
||||||
|
│ ├─ (0, 0) in bombs? → ✅ SÍ │
|
||||||
|
│ └─ return (IGNORADO) ❌ │
|
||||||
|
│ → bombs = {(0,0), (1,1)} │
|
||||||
|
│ → contador NO aumenta │
|
||||||
|
│ │
|
||||||
|
│ Clic en (2, 2): │
|
||||||
|
│ ├─ (2, 2) in bombs? → NO │
|
||||||
|
│ └─ bombs.add((2, 2)) │
|
||||||
|
│ → bombs = {(0,0), (1,1), (2,2)} │
|
||||||
|
│ → contador = 3 ✅ │
|
||||||
|
│ → TURNO COMPLETADO │
|
||||||
|
└───────────────────────────────────────┘
|
||||||
|
|
||||||
|
┌───────────────────────────────────────┐
|
||||||
|
│ TURNO JUGADOR 2 │
|
||||||
|
├───────────────────────────────────────┤
|
||||||
|
│ Clic en (0, 1): │
|
||||||
|
│ ├─ (0, 1) in bombs? → NO │
|
||||||
|
│ └─ bombs.add((0, 1)) │
|
||||||
|
│ → bombs = {(0,0),(1,1),(2,2), │
|
||||||
|
│ (0,1)} │
|
||||||
|
│ │
|
||||||
|
│ Clic en (1, 1) (donde J1 puso): │
|
||||||
|
│ ├─ (1, 1) in bombs? → ✅ SÍ │
|
||||||
|
│ └─ return (IGNORADO) ❌ │
|
||||||
|
│ → NO puede poner bomba encima │
|
||||||
|
│ │
|
||||||
|
│ Clic en (1, 2): │
|
||||||
|
│ └─ bombs.add((1, 2)) │
|
||||||
|
│ → bombs = {..., (1,2)} │
|
||||||
|
│ │
|
||||||
|
│ Clic en (2, 0): │
|
||||||
|
│ └─ bombs.add((2, 0)) │
|
||||||
|
│ → bombs = {(0,0),(1,1),(2,2), │
|
||||||
|
│ (0,1),(1,2),(2,0)} │
|
||||||
|
│ → 6 bombas total (3 por jugador)│
|
||||||
|
└───────────────────────────────────────┘
|
||||||
|
|
||||||
|
Grid final:
|
||||||
|
0 1 2
|
||||||
|
┌───┬───┬───┐
|
||||||
|
│ 💣│ 💣│ │ 0
|
||||||
|
├───┼───┼───┤
|
||||||
|
│ │ 💣│ 💣│ 1
|
||||||
|
├───┼───┼───┤
|
||||||
|
│ 💣│ │ 💣│ 2
|
||||||
|
└───┴───┴───┘
|
||||||
|
|
||||||
|
Total: 6 bombas únicas, sin duplicados
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **Código del Cliente (app.py líneas 1310-1330)**
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_button_click(x, y):
|
||||||
|
"""Cuando el jugador hace clic en una casilla"""
|
||||||
|
if self.game_phase == 'PLACING':
|
||||||
|
# Solo permite clic si es tu turno
|
||||||
|
if self.my_turn:
|
||||||
|
# Enviar al servidor
|
||||||
|
self.game_client.send({"type": "PLACE_BOMB", "x": x, "y": y})
|
||||||
|
|
||||||
|
# ⚠️ IMPORTANTE: El cliente NO valida duplicados localmente
|
||||||
|
# El servidor se encarga de toda la validación
|
||||||
|
# Si el servidor ignora el mensaje (duplicado),
|
||||||
|
# simplemente no recibirás BOMB_flash
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ventaja de validar en servidor:** Un jugador malicioso no puede modificar el cliente para hacer trampas. El servidor tiene la **única fuente de verdad**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### 🔒 Sincronización con Threading Lock
|
### 🔒 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:
|
El servidor usa un `threading.Lock` para evitar condiciones de carrera cuando múltiples clientes envían mensajes simultáneamente:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue