TP03
This commit is contained in:
299
battleship_client.py
Normal file
299
battleship_client.py
Normal file
@@ -0,0 +1,299 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user