698 lines
34 KiB
Markdown
698 lines
34 KiB
Markdown
<p align="center">
|
||
<img src="https://img.shields.io/badge/Python-3.8+-blue?style=for-the-badge&logo=python&logoColor=white" alt="Python">
|
||
<img src="https://img.shields.io/badge/Tkinter-GUI-green?style=for-the-badge&logo=python&logoColor=white" alt="Tkinter">
|
||
<img src="https://img.shields.io/badge/TCP/IP-Multiplayer-orange?style=for-the-badge&logo=socketdotio&logoColor=white" alt="Sockets">
|
||
<img src="https://img.shields.io/badge/License-Educational-purple?style=for-the-badge" alt="License">
|
||
</p>
|
||
|
||
<h1 align="center">💣 Minesweeper Multiplayer + Dashboard</h1>
|
||
|
||
<p align="center">
|
||
<strong>Un proyecto completo de programación de servicios y procesos</strong><br>
|
||
<em>Juego de buscaminas competitivo en red + Panel de control integral</em>
|
||
</p>
|
||
|
||
<p align="center">
|
||
<a href="#-características">Características</a> •
|
||
<a href="#-arquitectura">Arquitectura</a> •
|
||
<a href="#-instalación">Instalación</a> •
|
||
<a href="#-uso">Uso</a> •
|
||
<a href="#-mecánicas-del-juego">Mecánicas</a> •
|
||
<a href="#-tecnologías">Tecnologías</a>
|
||
</p>
|
||
|
||
---
|
||
|
||
## 📸 Vista Previa
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||
│ 🎮 MINESWEEPER MULTIPLAYER DASHBOARD │
|
||
├──────────────┬──────────────────────────────────────┬───────────────────────┤
|
||
│ │ │ ┌─────────────────┐ │
|
||
│ ACCIONES │ ÁREA DE RESULTADOS │ │ 💣 MINESWEEPER │ │
|
||
│ ────────── │ │ │ MULTIPLAYER │ │
|
||
│ │ ┌────────────────────────────┐ │ ├─────────────────┤ │
|
||
│ > Wallapop │ │ 📊 Monitor Sistema │ │ │ Ronda: 3 │ │
|
||
│ > Scraping │ │ 📈 CPU: 45% │ │ │ 💣 Bombas: 9 │ │
|
||
│ > API Tiempo │ │ 💾 RAM: 2.1GB │ │ │ ❤️ Vidas: 2 │ │
|
||
│ │ └────────────────────────────┘ │ ├─────────────────┤ │
|
||
│ APPS │ │ │ ┌───┬───┬───┐ │ │
|
||
│ ────────── │ Tabs: [Resultados][Navegador] │ │ │ ▢ │ ▢ │ ✓ │ │ │
|
||
│ > VS Code │ [Correos][Bloc][Tareas] │ │ ├───┼───┼───┤ │ │
|
||
│ > Camellos │ [Alarmas][Enlaces] │ │ │ 💥│ ▢ │ ▢ │ │ │
|
||
│ │ │ │ └───┴───┴───┘ │ │
|
||
│ BATCH │ ┌────────────────────────────┐ │ ├─────────────────┤ │
|
||
│ ────────── │ │ 📝 Panel de Notas │ │ │ [Iniciar Juego] │ │
|
||
│ > Backups │ │ │ │ │ [Zona Despejada]│ │
|
||
│ │ └────────────────────────────┘ │ └─────────────────┘ │
|
||
└──────────────┴──────────────────────────────────────┴───────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## ✨ Características
|
||
|
||
### 🎮 **Juego Minesweeper Multijugador**
|
||
| Característica | Descripción |
|
||
|----------------|-------------|
|
||
| 🔥 **Competitivo** | 2+ jugadores compiten en tiempo real |
|
||
| 💣 **Colocación estratégica** | Cada jugador coloca bombas para el rival |
|
||
| 🔄 **Por turnos** | Sistema de turnos para colocar y buscar |
|
||
| 📈 **Dificultad progresiva** | 5 rondas con grids y bombas crecientes |
|
||
| ❤️ **Sistema de vidas** | 3 vidas por partida, ¡no las pierdas! |
|
||
|
||
### 📊 **Dashboard Integral**
|
||
- 📡 **Monitor del sistema** en tiempo real (CPU, RAM, hilos)
|
||
- 🌤️ **API del tiempo** para Jávea (OpenWeather)
|
||
- 🛒 **Análisis de Wallapop** - Scraping de anuncios
|
||
- ⏰ **Sistema de alarmas** programables
|
||
- 📝 **Bloc de notas** integrado
|
||
- 🔗 **Gestor de enlaces** rápidos
|
||
- 🎲 **Minijuego de camellos** con animaciones
|
||
|
||
---
|
||
|
||
## 🏗️ Arquitectura
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ ARQUITECTURA DEL SISTEMA │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
|
||
┌──────────────────────────┐
|
||
│ SERVIDOR TCP │
|
||
│ servidor.py │
|
||
│ ┌──────────────────┐ │
|
||
│ │ GameServer │ │
|
||
│ │ - Estado juego │ │
|
||
│ │ - Broadcast │ │
|
||
│ │ - Turnos │ │
|
||
│ └──────────────────┘ │
|
||
│ Puerto 3333 │
|
||
└────────────┬─────────────┘
|
||
│
|
||
┌────────────┴───────────────┐
|
||
│ Protocolo JSON │
|
||
│ sobre TCP/IP │
|
||
└────────────┬───────────────┘
|
||
┌─────────────────────┼─────────────────────┐
|
||
│ │ │
|
||
▼ ▼ ▼
|
||
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
|
||
│ CLIENTE 1 │ │ CLIENTE 2 │ │ CLIENTE N │
|
||
│ │ │ │ │ │
|
||
│ ┌────────────┐ │ │ ┌────────────┐ │ │ ┌────────────┐ │
|
||
│ │ app.py │ │ │ │ app.py │ │ │ │ cliente_ │ │
|
||
│ │ Dashboard │ │ │ │ Dashboard │ │ │ │ juego.py │ │
|
||
│ │ + Juego │ │ │ │ + Juego │ │ │ │ Standalone │ │
|
||
│ └────────────┘ │ │ └────────────┘ │ │ └────────────┘ │
|
||
└──────────────────┘ └──────────────────┘ └──────────────────┘
|
||
```
|
||
|
||
### 📁 Estructura del Proyecto
|
||
|
||
```
|
||
Proyecto1AVApsp/
|
||
│
|
||
├── 📄 servidor.py # Servidor TCP del juego (371 líneas)
|
||
│ └── GameServer # Gestiona estado, turnos y broadcasts
|
||
│
|
||
├── 📄 app.py # Dashboard principal (2566 líneas)
|
||
│ ├── DashboardApp # Aplicación Tkinter completa
|
||
│ └── GameClient # Cliente TCP integrado
|
||
│
|
||
├── 📄 cliente_juego.py # Cliente standalone (220 líneas)
|
||
│ └── GameClient # Versión ligera para jugar
|
||
│
|
||
├── 📄 requirements.txt # Dependencias Python
|
||
└── 📄 README.md # Este archivo
|
||
```
|
||
|
||
---
|
||
|
||
## 🚀 Instalación
|
||
|
||
### Requisitos Previos
|
||
- **Python 3.8+**
|
||
- **pip** (gestor de paquetes)
|
||
|
||
### Paso 1: Clonar el Repositorio
|
||
```bash
|
||
git clone https://github.com/MarcosFerrandiz/Proyecto1AVApsp.git
|
||
cd Proyecto1AVApsp
|
||
```
|
||
|
||
### Paso 2: Crear Entorno Virtual (Recomendado)
|
||
```bash
|
||
python -m venv .venv
|
||
|
||
# Linux/macOS
|
||
source .venv/bin/activate
|
||
|
||
# Windows
|
||
.venv\Scripts\activate
|
||
```
|
||
|
||
### Paso 3: Instalar Dependencias
|
||
```bash
|
||
pip install -r requirements.txt
|
||
```
|
||
|
||
<details>
|
||
<summary>📦 <strong>Ver dependencias detalladas</strong></summary>
|
||
|
||
| Paquete | Versión | Uso |
|
||
|---------|---------|-----|
|
||
| `psutil` | ≥5.9.0 | Monitor de recursos del sistema |
|
||
| `matplotlib` | ≥3.5.0 | Gráficos en tiempo real |
|
||
| `pillow` | ≥9.0.0 | Procesamiento de imágenes |
|
||
| `pygame` | ≥2.1.0 | Reproducción de audio (opcional) |
|
||
| `requests` | ≥2.32.0 | Peticiones HTTP (API, Scraping) |
|
||
| `beautifulsoup4` | ≥4.12.0 | Parsing HTML (Scraping) |
|
||
|
||
</details>
|
||
|
||
---
|
||
|
||
## 🎯 Uso
|
||
|
||
### 🖥️ Iniciar el Servidor
|
||
```bash
|
||
python servidor.py
|
||
```
|
||
> El servidor escuchará en `0.0.0.0:3333`
|
||
|
||
### 🎮 Opción A: Dashboard Completo
|
||
```bash
|
||
python app.py
|
||
```
|
||
Incluye el juego integrado + todas las funcionalidades del panel.
|
||
|
||
### 🎮 Opción B: Cliente Standalone
|
||
```bash
|
||
python cliente_juego.py
|
||
```
|
||
Cliente ligero solo para jugar al Minesweeper.
|
||
|
||
### 🔌 Conectar al Juego
|
||
1. Introduce el **Host** del servidor (por defecto: `127.0.0.1`)
|
||
2. Verifica el **Puerto** (por defecto: `3333`)
|
||
3. Pulsa **"Conectar"**
|
||
4. ¡Espera a otro jugador y pulsa **"Iniciar Juego"**!
|
||
|
||
---
|
||
|
||
## 💣 Mecánicas del Juego
|
||
|
||
### 🔄 Flujo de una Partida
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ FLUJO DEL JUEGO │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌─────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||
│ │ LOBBY │────▶│ COLOCACIÓN │────▶│ BÚSQUEDA │ │
|
||
│ │ │ │ DE BOMBAS │ │ (PLAYING) │ │
|
||
│ └─────────┘ └─────────────┘ └──────┬──────┘ │
|
||
│ │ │ │
|
||
│ │ ┌───────────────────────────┘ │
|
||
│ │ │ │
|
||
│ │ ▼ │
|
||
│ │ ┌─────────┐ ┌──────────┐ ┌────────────┐ │
|
||
│ │ │ ¿BOMBA? │───▶│ EXPLOSIÓN│───▶│ ¿VIDAS=0? │ │
|
||
│ │ └────┬────┘ └──────────┘ └─────┬──────┘ │
|
||
│ │ │ NO │ │
|
||
│ │ ▼ SÍ ▼ │
|
||
│ │ ┌─────────┐ ┌──────────┐ │
|
||
│ │ │ SAFE │ │ GAME OVER│ │
|
||
│ │ │(casilla │ └──────────┘ │
|
||
│ │ │ segura) │ │
|
||
│ │ └────┬────┘ │
|
||
│ │ │ │
|
||
│ │ ▼ │
|
||
│ │ ┿━━━━━━━━━━━◀────────────────────────┓ │
|
||
│ │ ┃ SIGUIENTE TURNO ┃ │
|
||
│ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ │
|
||
│ │ │
|
||
│ └───────────────────▶ (Nueva Partida) │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 📊 Progresión de Dificultad
|
||
|
||
| Ronda | Tamaño Grid | Bombas/Jugador | Total Bombas* |
|
||
|:-----:|:-----------:|:--------------:|:-------------:|
|
||
| 1️⃣ | 3×3 | 3 | 6 |
|
||
| 2️⃣ | 5×5 | 5 | 10 |
|
||
| 3️⃣ | 8×8 | 9 | 18 |
|
||
| 4️⃣ | 11×11 | 12 | 24 |
|
||
| 5️⃣+ | 14×14 | 15 | 30 |
|
||
|
||
*\*Para 2 jugadores*
|
||
|
||
### 🎯 Fases del Juego
|
||
|
||
#### 1. 💣 Fase de Colocación (`PLACING`)
|
||
- Cada jugador coloca bombas **por turnos**
|
||
- Las bombas se muestran brevemente (1 segundo) a todos
|
||
- ¡Memoriza dónde pones TUS bombas y las del rival!
|
||
|
||
#### 2. 🔍 Fase de Búsqueda (`PLAYING`)
|
||
- Excava casillas por turnos
|
||
- **Casilla segura** → Se marca en verde ✅
|
||
- **Bomba** → ¡EXPLOSIÓN! Pierdes 1 vida 💔
|
||
|
||
### 🏆 Condiciones de Victoria
|
||
|
||
| Condición | Resultado |
|
||
|-----------|-----------|
|
||
| Rival pierde todas las vidas | **¡GANASTE!** 🎉 |
|
||
| Todas las casillas seguras reveladas | **¡Zona despejada!** |
|
||
| Superas la ronda 5 | **¡Victoria total!** 🏆 |
|
||
|
||
---
|
||
|
||
## 📡 Protocolo de Comunicación
|
||
|
||
### Mensajes JSON Cliente → Servidor
|
||
|
||
```javascript
|
||
// Iniciar partida
|
||
{ "type": "START_GAME" }
|
||
|
||
// Colocar bomba (fase PLACING)
|
||
{ "type": "PLACE_BOMB", "x": 2, "y": 1 }
|
||
|
||
// Excavar casilla (fase PLAYING)
|
||
{ "type": "CLICK_CELL", "x": 3, "y": 4 }
|
||
|
||
// Verificar zona despejada
|
||
{ "type": "CHECK_DUNGEON_CLEARED" }
|
||
```
|
||
|
||
### Mensajes JSON Servidor → Clientes
|
||
|
||
```javascript
|
||
// Nueva ronda
|
||
{
|
||
"type": "NEW_ROUND",
|
||
"round": 1,
|
||
"grid_size": 3,
|
||
"total_bombs_per_player": 3
|
||
}
|
||
|
||
// Notificación de turno
|
||
{
|
||
"type": "TURN_NOTIFY",
|
||
"active_player": "('127.0.0.1', 54321)",
|
||
"msg": "Turno de ... para poner bombas."
|
||
}
|
||
|
||
// Flash de bomba (visible 1 segundo)
|
||
{
|
||
"type": "BOMB_flash",
|
||
"x": 1, "y": 2,
|
||
"who": "('127.0.0.1', 54321)"
|
||
}
|
||
|
||
// Explosión
|
||
{
|
||
"type": "EXPLOSION",
|
||
"x": 1, "y": 2,
|
||
"who": "...",
|
||
"lives": 2
|
||
}
|
||
|
||
// Casilla segura
|
||
{ "type": "SAFE", "x": 0, "y": 0 }
|
||
|
||
// Game Over
|
||
{
|
||
"type": "GAME_OVER",
|
||
"loser": "...",
|
||
"msg": "💀 ¡... ha perdido todas sus vidas!"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🔧 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>
|
||
<tr>
|
||
<td align="center" width="150">
|
||
<img src="https://raw.githubusercontent.com/devicons/devicon/master/icons/python/python-original.svg" width="48" height="48" alt="Python" />
|
||
<br><strong>Python 3</strong>
|
||
<br><em>Lenguaje base</em>
|
||
</td>
|
||
<td align="center" width="150">
|
||
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/d5/Virtualbox_logo.png/64px-Virtualbox_logo.png" width="48" height="48" alt="Tkinter" />
|
||
<br><strong>Tkinter</strong>
|
||
<br><em>Interfaz gráfica</em>
|
||
</td>
|
||
<td align="center" width="150">
|
||
<img src="https://raw.githubusercontent.com/devicons/devicon/master/icons/socketio/socketio-original.svg" width="48" height="48" alt="Sockets" />
|
||
<br><strong>TCP Sockets</strong>
|
||
<br><em>Comunicación en red</em>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td align="center">
|
||
<img src="https://matplotlib.org/stable/_static/logo_light.svg" width="48" height="48" alt="Matplotlib" />
|
||
<br><strong>Matplotlib</strong>
|
||
<br><em>Gráficos</em>
|
||
</td>
|
||
<td align="center">
|
||
<img src="https://raw.githubusercontent.com/devicons/devicon/master/icons/json/json-original.svg" width="48" height="48" alt="JSON" />
|
||
<br><strong>JSON</strong>
|
||
<br><em>Protocolo mensajes</em>
|
||
</td>
|
||
<td align="center">
|
||
<img src="https://www.vectorlogo.zone/logos/pocoo_flask/pocoo_flask-icon.svg" width="48" height="48" alt="Requests" />
|
||
<br><strong>Requests</strong>
|
||
<br><em>APIs HTTP</em>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
|
||
### 🧵 Conceptos de PSP Aplicados
|
||
|
||
| Concepto | Implementación |
|
||
|----------|----------------|
|
||
| **Procesos** | Lanzamiento de aplicaciones externas (VS Code, Firefox) |
|
||
| **Threads** | Servidor multihilo, cliente con hilos de recepción |
|
||
| **Sockets TCP** | Comunicación cliente-servidor en red |
|
||
| **Servicios** | API OpenWeather, scraping de Wallapop |
|
||
| **Sincronización** | Locks para acceso concurrente a estado compartido |
|
||
|
||
---
|
||
|
||
## 🎨 Características del Dashboard
|
||
|
||
### 📊 Monitor del Sistema
|
||
- Gráfico de CPU en línea temporal
|
||
- Gráfico de memoria como área
|
||
- Contador de hilos del proceso
|
||
|
||
### 🌤️ API del Tiempo (Jávea)
|
||
- Temperatura actual y sensación térmica
|
||
- Humedad y velocidad del viento
|
||
- Descripción del clima
|
||
|
||
### 🛒 Análisis Wallapop
|
||
- Extracción de información de anuncios
|
||
- Headers personalizados para API
|
||
- Resultados formateados
|
||
|
||
### ⏰ Sistema de Alarmas
|
||
- Programación en minutos
|
||
- Notificaciones visuales
|
||
- Gestión de alarmas activas
|
||
|
||
---
|
||
|
||
## 🤝 Contribuir
|
||
|
||
1. **Fork** del proyecto
|
||
2. Crea tu **Feature Branch** (`git checkout -b feature/NuevaFuncion`)
|
||
3. **Commit** tus cambios (`git commit -m 'Add: Nueva función'`)
|
||
4. **Push** a la rama (`git push origin feature/NuevaFuncion`)
|
||
5. Abre un **Pull Request**
|
||
|
||
---
|
||
|
||
## 📝 Licencia
|
||
|
||
Este proyecto es de carácter **educativo** y fue desarrollado como parte del módulo de **Programación de Servicios y Procesos**.
|
||
|
||
---
|
||
|
||
<p align="center">
|
||
<strong>Desarrollado con ❤️ por Marcos Ferrandiz</strong>
|
||
</p>
|
||
|
||
<p align="center">
|
||
<em>Proyecto 1º Evaluación - PSP (Programación de Servicios y Procesos)</em>
|
||
</p>
|
||
|
||
---
|
||
|
||
<p align="center">
|
||
<img src="https://img.shields.io/badge/Estado-Completado-success?style=flat-square" alt="Estado">
|
||
<img src="https://img.shields.io/badge/Versión-1.0-blue?style=flat-square" alt="Versión">
|
||
<img src="https://img.shields.io/badge/Made%20with-Python-yellow?style=flat-square&logo=python" alt="Python">
|
||
</p>
|