Proyecto1AVApsp/cliente_juego.py

220 lines
7.6 KiB
Python

#!/usr/bin/env python3
import tkinter as tk
from tkinter import messagebox, scrolledtext, simpledialog
import socket
import threading
import json
import time
import queue
SERVER_HOST = '127.0.0.1'
SERVER_PORT = 3333
class GameClient:
def __init__(self, root):
self.root = root
self.root.title("Minesweeper Multiplayer - Cliente Dedicado")
self.root.geometry("600x700")
# Conexión
self.sock = None
self.connected = False
self.msg_queue = queue.Queue()
# Estado UI
self.buttons = {}
self.game_phase = 'LOBBY'
self.build_ui()
# Loop de mensajes UI
self.root.after(100, self.process_queue)
def build_ui(self):
# Frame Superior (Conexión)
conn_frame = tk.Frame(self.root, pady=5)
conn_frame.pack(fill='x', padx=10, pady=5)
tk.Label(conn_frame, text="Host:").pack(side='left')
self.ent_host = tk.Entry(conn_frame, width=15)
self.ent_host.insert(0, SERVER_HOST)
self.ent_host.pack(side='left', padx=5)
tk.Label(conn_frame, text="Port:").pack(side='left')
self.ent_port = tk.Entry(conn_frame, width=6)
self.ent_port.insert(0, str(SERVER_PORT))
self.ent_port.pack(side='left', padx=5)
self.btn_connect = tk.Button(conn_frame, text="Conectar", command=self.connect)
self.btn_connect.pack(side='left', padx=10)
self.btn_start = tk.Button(conn_frame, text="Iniciar Juego", command=self.start_game, bg='#90ee90', state='disabled')
self.btn_start.pack(side='right', padx=10)
# Frame Stats
stats_frame = tk.Frame(self.root, pady=5)
stats_frame.pack(fill='x', padx=20)
self.lbl_round = tk.Label(stats_frame, text="Ronda: -", font=('Arial', 14))
self.lbl_round.pack(side='left')
self.lbl_lives = tk.Label(stats_frame, text="Vidas: -", font=('Arial', 14, 'bold'), fg='red')
self.lbl_lives.pack(side='right')
# Frame Juego
self.game_frame = tk.Frame(self.root, bg='#cccccc')
self.game_frame.pack(fill='both', expand=True, padx=20, pady=10)
# Logs
self.log_area = scrolledtext.ScrolledText(self.root, height=8)
self.log_area.pack(fill='x', padx=10, pady=10)
# Botones Control
ctrl_frame = tk.Frame(self.root)
ctrl_frame.pack(pady=5)
self.btn_done = tk.Button(ctrl_frame, text="¡Zona Limpia!", command=self.check_cleared, bg='gold', state='disabled')
self.btn_done.pack()
def log(self, text):
self.log_area.insert('end', f"> {text}\n")
self.log_area.see('end')
def connect(self):
host = self.ent_host.get()
try:
port = int(self.ent_port.get())
except ValueError:
messagebox.showerror("Error", "Puerto inválido")
return
try:
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.connect((host, port))
self.connected = True
threading.Thread(target=self.recv_loop, daemon=True).start()
self.log(f"Conectado a {host}:{port}")
self.btn_connect.config(state='disabled')
self.btn_start.config(state='normal')
except Exception as e:
messagebox.showerror("Error Conexión", str(e))
def send(self, data):
if not self.connected or not self.sock:
return
try:
msg = json.dumps(data) + '\n'
self.sock.sendall(msg.encode('utf-8'))
except Exception as e:
self.log(f"Error enviando: {e}")
def recv_loop(self):
while self.connected:
try:
data = self.sock.recv(4096)
if not data: break
text = data.decode('utf-8', errors='replace')
for line in text.split('\n'):
line = line.strip()
if not line: continue
try:
self.msg_queue.put(json.loads(line))
except: pass
except:
break
self.connected = False
self.msg_queue.put({"type": "DISCONNECT"})
def process_queue(self):
while not self.msg_queue.empty():
msg = self.msg_queue.get()
self.handle_message(msg)
self.root.after(100, self.process_queue)
def handle_message(self, msg):
mtype = msg.get('type')
if mtype == 'DISCONNECT':
self.log("Desconectado del servidor.")
self.btn_connect.config(state='normal')
self.btn_start.config(state='disabled')
return
if mtype == 'NEW_ROUND':
r = msg.get('round')
size = msg.get('grid_size')
self.lbl_round.config(text=f"Ronda: {r}")
self.log(f"--- NUEVA RONDA {r} ---")
self.game_phase = 'PLACING'
self.build_grid(size)
self.btn_done.config(state='disabled')
elif mtype == 'TURN_NOTIFY':
self.log(msg.get('msg', 'Es tu turno'))
elif mtype == 'BOMB_flash':
x, y = msg.get('x'), msg.get('y')
who = msg.get('who')
self.log(f"Bomba puesta por {who}")
btn = self.buttons.get((x,y))
if btn:
orig = btn.cget('bg')
btn.config(bg='orange', text='💣')
self.root.after(1000, lambda b=btn: b.config(bg=orig, text=''))
elif mtype == 'PHASE_PLAY':
self.game_phase = 'PLAYING'
self.log(msg.get('msg'))
self.btn_done.config(state='normal')
elif mtype == 'EXPLOSION':
x, y = msg.get('x'), msg.get('y')
lives = msg.get('lives')
who = msg.get('who')
self.log(f"EXPLOSIÓN de {who}!")
self.lbl_lives.config(text=f"Vidas: {lives}")
btn = self.buttons.get((x,y))
if btn: btn.config(bg='red', text='💥')
elif mtype == 'SAFE':
x, y = msg.get('x'), msg.get('y')
btn = self.buttons.get((x,y))
if btn: btn.config(bg='lightgreen', relief='sunken')
elif mtype == 'WARNING':
self.log(f"[AVISO] {msg.get('msg')}")
elif mtype == 'ROUND_WIN':
self.log(f"GANADOR: {msg.get('msg')}")
messagebox.showinfo("Ronda", msg.get('msg'))
elif mtype == 'GAME_WIN':
messagebox.showinfo("Victoria", "Juego Completado!")
def build_grid(self, size):
for child in self.game_frame.winfo_children():
child.destroy()
self.buttons = {}
for r in range(size):
self.game_frame.rowconfigure(r, weight=1)
self.game_frame.columnconfigure(r, weight=1)
for c in range(size):
btn = tk.Button(self.game_frame, bg='#dddddd')
btn.grid(row=r, column=c, sticky='nsew', padx=1, pady=1)
btn.config(command=lambda x=c, y=r: self.on_click(x,y))
self.buttons[(c,r)] = btn
def on_click(self, x, y):
if self.game_phase == 'PLACING':
self.send({"type": "PLACE_BOMB", "x": x, "y": y})
elif self.game_phase == 'PLAYING':
self.send({"type": "CLICK_CELL", "x": x, "y": y})
def start_game(self):
self.send({"type": "START_GAME"})
def check_cleared(self):
self.send({"type": "CHECK_DUNGEON_CLEARED"})
if __name__ == "__main__":
root = tk.Tk()
app = GameClient(root)
root.mainloop()