#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Bataille Navale - Client LAN Se connecte au serveur, place les bateaux (manuel ou aléatoire), puis joue. """ import socket import json import argparse import threading import random import os import time ROWS = 10 COLS = 10 SHIPS_DEF = { "Carrier": 5, "Battleship": 4, "Cruiser": 3, "Submarine": 3, "Destroyer": 2 } def clear(): os.system("cls" if os.name == "nt" else "clear") def send_line(conn, data): conn.sendall((json.dumps(data) + "\n").encode()) def recv_loop(conn, handler): f = conn.makefile(mode="r", encoding="utf-8") try: while True: line = f.readline() if not line: break data = json.loads(line.strip()) handler(data) except Exception as e: print("Connection closed.", e) finally: try: f.close() except: pass class ClientGame: def __init__(self, host, port): self.sock = socket.create_connection((host, port)) self.lock = threading.Lock() self.player_idx = None self.my_board = [["." for _ in range(COLS)] for _ in range(ROWS)] self.enemy_board = [["." for _ in range(COLS)] for _ in range(ROWS)] # track hits/misses self.ships = {} # name -> list of [r,c] self.turn = None self.running = True threading.Thread(target=recv_loop, args=(self.sock, self.on_message), daemon=True).start() def on_message(self, data): t = data.get("type") if t == "welcome": self.player_idx = data.get("player_idx") print(f"[INFO] {data.get('msg')}") elif t == "start_place": print("[INFO] Place tes bateaux.") self.place_phase() elif t == "placed_ok": print("[INFO] Placement reçu par le serveur. En attente de l'adversaire...") elif t == "start_game": print("[INFO] Partie démarrée !") # draw boards then wait for turn messages self.draw_boards() elif t == "turn": self.turn = data.get("current") if self.turn == self.player_idx: print("\nC'est ton tour !") self.player_turn() else: print("\nTour de l'adversaire, attends...") elif t == "result": res = data.get("res") r = data.get("r"); c = data.get("c") if res == "miss": print(f"Ton tir en ({r},{c}) a manqué.") self.enemy_board[r][c] = "o" elif res == "hit": print(f"Ton tir en ({r},{c}) a touché un navire !") self.enemy_board[r][c] = "X" elif res == "sunk": ship = data.get("ship") print(f"Tu as coulé le navire {ship} !") self.enemy_board[r][c] = "X" self.draw_boards() elif t == "update": shot = data.get("opponent_shot") r = shot.get("r"); c = shot.get("c"); res = shot.get("res") if res == "miss": print(f"L'adversaire a tiré en ({r},{c}) : manqué.") self.my_board[r][c] = "o" else: print(f"L'adversaire a tiré en ({r},{c}) : {res.upper()} !") self.my_board[r][c] = "X" self.draw_boards() elif t == "end": winner = data.get("winner") reason = data.get("reason") if winner == self.player_idx: print("\n🎉 Tu as gagné !", reason) else: print("\n😢 Tu as perdu.", reason) self.running = False print("Partie terminée. Ferme le client (Ctrl+C) ou attend la fermeture.") elif t == "info": print("[INFO]", data.get("msg")) elif t == "error": print("[ERROR]", data.get("msg")) else: print("[MSG]", data) def draw_boards(self): clear() print("Ton plateau:\t\t\tPlateau adversaire (tes tirs)") header = " " + " ".join(str(i) for i in range(COLS)) print(header + "\t\t" + header) for r in range(ROWS): left = str(r) + " " + " ".join(self.my_board[r]) right = str(r) + " " + " ".join(self.enemy_board[r]) print(left + "\t\t" + right) print() def place_phase(self): # ask player if manual or random while True: choice = input("Placement manuel (m) ou aléatoire (a) ? [m/a] ").strip().lower() if choice in ("m", "a"): break if choice == "a": self.ships = self.random_place() self.apply_ships_to_board() send_line(self.sock, {"type": "place", "ships": self.ships}) print("Placement aléatoire envoyé.") return # manual placement print("Instructions: entre des positions sous la forme r,c (ex: 3,5).") print("Tu dois placer les navires suivants (nom : taille):") for name, size in SHIPS_DEF.items(): print(f" - {name} : {size}") placed = {} for name, size in SHIPS_DEF.items(): while True: print("\nTon plateau actuel:") self.draw_boards_single() print(f"Place {name} (taille {size}). Tu dois entrer {size} positions consécutives en ligne ou colonne.") s = input(f"Positions séparées par espace (ex: 2,3 2,4 2,5 ...): ").strip() try: parts = s.split() coords = [] for p in parts: rr, cc = map(int, p.split(",")) coords.append([rr, cc]) # basic validation: count & contiguous if len(coords) != size: print("Mauvais nombre de positions.") continue # bounds bad = False for rr, cc in coords: if not (0 <= rr < ROWS and 0 <= cc < COLS): bad = True; break if bad: print("Position hors limites.") continue rows = [p[0] for p in coords]; cols = [p[1] for p in coords] if not (len(set(rows)) == 1 or len(set(cols)) == 1): print("Les coordonnées doivent être alignées sur une ligne ou une colonne.") continue if len(set(rows)) == 1: sorted_cols = sorted(cols) if sorted_cols != list(range(min(cols), max(cols)+1)): print("Les colonnes doivent être consécutives.") continue else: sorted_rows = sorted(rows) if sorted_rows != list(range(min(rows), max(rows)+1)): print("Les lignes doivent être consécutives.") continue # check overlap overlap = False for rr, cc in coords: if self.my_board[rr][cc] != ".": overlap = True; break if overlap: print("Chevauchement avec un navire déjà placé.") continue # ok placed[name] = coords for rr, cc in coords: self.my_board[rr][cc] = name[0] # mark with first letter break except Exception as e: print("Erreur de format:", e) continue self.ships = placed send_line(self.sock, {"type": "place", "ships": self.ships}) print("Placement envoyé au serveur. En attente de l'adversaire...") def draw_boards_single(self): header = " " + " ".join(str(i) for i in range(COLS)) print(header) for r in range(ROWS): left = str(r) + " " + " ".join(self.my_board[r]) print(left) print() def random_place(self): board = [["." for _ in range(COLS)] for _ in range(ROWS)] ships = {} for name, size in SHIPS_DEF.items(): placed = False attempts = 0 while not placed and attempts < 200: attempts += 1 dir = random.choice(["H", "V"]) if dir == "H": r = random.randrange(0, ROWS) c = random.randrange(0, COLS - size + 1) coords = [[r, c + i] for i in range(size)] else: r = random.randrange(0, ROWS - size + 1) c = random.randrange(0, COLS) coords = [[r + i, c] for i in range(size)] # check overlap ok = True for rr, cc in coords: if board[rr][cc] != ".": ok = False; break if ok: for rr, cc in coords: board[rr][cc] = name[0] ships[name] = coords placed = True if not placed: raise RuntimeError("Impossible de placer tous les navires aléatoirement") self.my_board = board return ships def player_turn(self): # ask for coordinates to shoot self.draw_boards() while True: s = input("Tire en (r,c) ou 'q' pour abandonner: ").strip() if s.lower() == "q": send_line(self.sock, {"type": "forfeit"}) print("Tu as abandonné.") self.running = False return try: r, c = map(int, s.split(",")) if not (0 <= r < ROWS and 0 <= c < COLS): print("Hors limites.") continue # check local memory if already shot if self.enemy_board[r][c] != ".": print("Tu as déjà tiré à cet endroit.") continue send_line(self.sock, {"type": "shot", "r": r, "c": c}) break except Exception as e: print("Format invalide. Ex: 3,4") continue def close(self): try: self.sock.close() except: pass if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--host", default="127.0.0.1") parser.add_argument("--port", type=int, default=5000) args = parser.parse_args() print(f"Connexion au serveur {args.host}:{args.port} ...") try: game = ClientGame(args.host, args.port) except Exception as e: print("Impossible de se connecter:", e) raise SystemExit(1) try: while game.running: time.sleep(0.3) except KeyboardInterrupt: print("Fermeture client.") finally: game.close()