diff --git a/P1.py b/P1.py new file mode 100644 index 0000000..e3d731b --- /dev/null +++ b/P1.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +ASCII animations playground +Usage: python ascii_anim.py +Press Ctrl+C to quit an animation. +""" + +import os +import sys +import time +import shutil +import math + +def clear(): + # Clear terminal in a cross-platform way + os.system('cls' if os.name == 'nt' else 'clear') + +def spinner(text="Chargement", delay=0.08, rounds=80): + frames = ['|', '/', '-', '\\'] + for i in range(rounds): + sys.stdout.write(f"\r{frames[i % len(frames)]} {text} ") + sys.stdout.flush() + time.sleep(delay) + print() + +def marquee(text="Hello ASCII!", speed=0.05, loops=3): + cols = shutil.get_terminal_size((80, 20)).columns + padding = " " * cols + s = padding + text + padding + for _ in range(loops): + for i in range(len(text) + cols): + window = s[i:i+cols] + sys.stdout.write("\r" + window) + sys.stdout.flush() + time.sleep(speed) + print() + +def bouncing(text="BOUNCE", speed=0.03, loops=6): + cols = max(20, shutil.get_terminal_size((80, 20)).columns - len(text)) + pos = 0 + direction = 1 + start = time.time() + steps = int(loops * (cols * 2)) + for _ in range(steps): + sys.stdout.write("\r" + " " * pos + text) + sys.stdout.flush() + pos += direction + if pos >= cols or pos <= 0: + direction *= -1 + time.sleep(speed) + print() + +def wave(text="~wave~", amplitude=3, wavelength=4, speed=0.08, cycles=6): + cols = shutil.get_terminal_size((80, 20)).columns + base_x = 0 + for t in range(int(cycles * (wavelength * 4))): + line = [" "] * cols + for i, ch in enumerate(text): + x = (i + base_x) % cols + y_offset = int(amplitude * math.sin((i + t) / wavelength)) + # create a vertical effect by shifting with newlines — simple 1-line version: + # we emulate vertical movement by offsetting with spaces (approx) + pos = x + line[pos] = ch + sys.stdout.write("\r" + "".join(line)) + sys.stdout.flush() + base_x += 1 + time.sleep(speed) + print() + +def typing(text="Ceci est une animation de typing...", speed=0.04): + for ch in text: + sys.stdout.write(ch) + sys.stdout.flush() + time.sleep(speed) + print() + +def demo_all(): + try: + clear() + print("Demo ASCII animations — Ctrl+C pour quitter\n") + print("1) Spinner") + spinner("Traitement...", delay=0.06, rounds=60) + print("\n2) Marquee") + marquee(">>> Salut ! Ceci est une annonce en défilement <<<", speed=0.03, loops=4) + print("\n3) Bouncing") + bouncing(" <3 ", speed=0.01, loops=8) + print("\n4) Wave (approx.)") + wave("WAVEEE", amplitude=2, wavelength=3, speed=0.04, cycles=5) + print("\n5) Typing") + typing("Voilà — animation typing terminée !", speed=0.03) + except KeyboardInterrupt: + print("\nInterrompu. À bientôt!") + +if __name__ == "__main__": + try: + clear() + print("Choisis une animation :") + print(" 1) Spinner") + print(" 2) Marquee (défilement)") + print(" 3) Bouncing") + print(" 4) Wave (approx.)") + print(" 5) Typing") + print(" 0) Demo toutes") + choice = input("Numéro > ").strip() + clear() + if choice == "1": + spinner("Chargement...", delay=0.06, rounds=1000) + elif choice == "2": + marquee(">>> Salut ! Ceci est une annonce en défilement <<<", speed=0.03, loops=999) + elif choice == "3": + bouncing(" <3 ", speed=0.02, loops=999) + elif choice == "4": + wave("WAVEEE", amplitude=2, wavelength=3, speed=0.05, cycles=999) + elif choice == "5": + while True: + typing("Tape... (Ctrl+C pour sortir)", speed=0.03) + time.sleep(0.8) + else: + demo_all() + except KeyboardInterrupt: + print("\nBye 👋") diff --git a/P2.py b/P2.py new file mode 100644 index 0000000..cd0abd3 --- /dev/null +++ b/P2.py @@ -0,0 +1,34 @@ +import os +import time + +frames = [ +r""" + o + /|\ + / \ +""", +r""" + \o/ + | + / \ +""", +r""" + o/ + /| + / \ +""", +r""" + \o + |\ + / \ +""" +] + +def clear(): + os.system('cls' if os.name == 'nt' else 'clear') + +while True: + for frame in frames: + clear() + print(frame) + time.sleep(0.2) diff --git a/P3.py b/P3.py new file mode 100644 index 0000000..351c623 --- /dev/null +++ b/P3.py @@ -0,0 +1,39 @@ +import cv2 +import os +import time + +# Ensemble de caractères par intensité +ASCII_CHARS = "@%#*+=-:. " + +def resize_frame(frame, new_width=120): + height, width = frame.shape + ratio = height / width / 1.65 # correction pour terminal + new_height = int(new_width * ratio) + resized = cv2.resize(frame, (new_width, new_height)) + return resized + +def gray_to_ascii(gray_frame): + ascii_frame = "" + for row in gray_frame: + for pixel in row: + ascii_frame += ASCII_CHARS[pixel // 25] + ascii_frame += "\n" + return ascii_frame + +def play_video(path): + cap = cv2.VideoCapture(path) + while cap.isOpened(): + ret, frame = cap.read() + if not ret: + break + gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) + resized = resize_frame(gray) + ascii_frame = gray_to_ascii(resized) + + os.system("cls" if os.name == "nt" else "clear") + print(ascii_frame) + time.sleep(0.03) # ~30 FPS + cap.release() + +if __name__ == "__main__": + play_video("media1.mp4") # remplace par ton fichier diff --git a/P4.py b/P4.py new file mode 100644 index 0000000..2f7bb0a --- /dev/null +++ b/P4.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Connect Four (Puissance 4) — jeu console pour 2 joueurs +Usage: python connect4.py +Auteur: généré par ChatGPT pour Le J +""" + +import os +import time + +ROWS = 6 +COLS = 7 +EMPTY = "." + +# Symbols for players +PLAYER_SYMBOLS = ["X", "O"] + +def clear(): + os.system("cls" if os.name == "nt" else "clear") + +def make_board(): + return [[EMPTY for _ in range(COLS)] for _ in range(ROWS)] + +def print_board(board): + clear() + # header with column numbers + print(" " + " ".join(str(i+1) for i in range(COLS))) + for row in board: + print(" |" + "|".join(row) + "|") + print("-" * (COLS * 2 + 3)) + +def is_valid_col(board, col): + return 0 <= col < COLS and board[0][col] == EMPTY + +def drop_piece(board, col, symbol, animate=True): + # find the lowest empty row + for r in range(ROWS-1, -1, -1): + if board[r][col] == EMPTY: + if animate: + # small drop animation (textual) + for step in range(r+1): + # draw a temporary board with the piece at current step + temp = [row[:] for row in board] + temp[step][col] = symbol + print_board(temp) + time.sleep(0.03) + # final position + board[r][col] = symbol + return r, col + return None + +def check_direction(board, start_r, start_c, dr, dc, symbol): + count = 0 + r, c = start_r, start_c + # go backwards + while 0 <= r < ROWS and 0 <= c < COLS and board[r][c] == symbol: + count += 1 + r -= dr + c -= dc + # go forward (excluding start) + r, c = start_r + dr, start_c + dc + while 0 <= r < ROWS and 0 <= c < COLS and board[r][c] == symbol: + count += 1 + r += dr + c += dc + return count + +def is_winner(board, r, c, symbol): + # check horizontal (0,1), vertical (1,0), diag up-right (1,1), diag down-right (1,-1) + directions = [(0,1), (1,0), (1,1), (1,-1)] + for dr, dc in directions: + if check_direction(board, r, c, dr, dc, symbol) >= 4: + return True + return False + +def board_full(board): + return all(cell != EMPTY for cell in board[0]) + +def ask_column(player): + while True: + choice = input(f"Joueur {player+1} ({PLAYER_SYMBOLS[player]}), choisis une colonne (1-{COLS}) ou 'q' pour quitter: ").strip() + if choice.lower() == 'q': + return "q" + if not choice.isdigit(): + print("Entrée invalide — entre un numéro.") + continue + col = int(choice) - 1 + if col < 0 or col >= COLS: + print(f"Choisis une colonne entre 1 et {COLS}.") + continue + return col + +def main(): + print("=== Connect Four (Puissance 4) — 2 joueurs ===") + print("Règles rapides : place ton jeton dans une colonne ; il tombe tout en bas ou sur le dernier jeton.") + print("Le premier qui aligne 4 jetons de suite (horizontale, verticale ou diagonale) gagne.") + print("Appuie sur Entrée pour commencer...") + input() + while True: + board = make_board() + current = 0 # joueur 0 commence + winner = None + + print_board(board) + while True: + col = ask_column(current) + if col == "q": + print("Partie quittée. À plus !") + return + if not is_valid_col(board, col): + print("Colonne pleine ou invalide, choisis-en une autre.") + time.sleep(0.7) + continue + + # drop piece (with animation) + r, c = drop_piece(board, col, PLAYER_SYMBOLS[current], animate=True) + print_board(board) + + # check win + if is_winner(board, r, c, PLAYER_SYMBOLS[current]): + winner = current + break + + # check draw + if board_full(board): + break + + # next player + current = 1 - current + + if winner is not None: + print(f"🎉 Joueur {winner+1} ({PLAYER_SYMBOLS[winner]}) a gagné ! Bravo !") + else: + print("Match nul — le tableau est plein.") + + # play again? + again = input("Rejouer ? (o/n) : ").strip().lower() + if again not in ("o", "oui", "y", "yes"): + print("Merci d'avoir joué — à la prochaine !") + break + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + print("\nInterrompu — bye !") diff --git a/P5.py b/P5.py new file mode 100644 index 0000000..e69de29 diff --git a/battleship_client.py b/battleship_client.py new file mode 100644 index 0000000..c1944f3 --- /dev/null +++ b/battleship_client.py @@ -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() diff --git a/battleship_server.py b/battleship_server.py new file mode 100644 index 0000000..77e3a82 --- /dev/null +++ b/battleship_server.py @@ -0,0 +1,261 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Bataille Navale - Serveur LAN (arbitre) +Lance le serveur, attend 2 clients, échange messages JSON ligne par ligne. +""" + +import socket +import threading +import json +import time + +HOST = "0.0.0.0" +PORT = 5000 + +# Board constants +ROWS = 10 +COLS = 10 +SHIPS_DEF = { + "Carrier": 5, + "Battleship": 4, + "Cruiser": 3, + "Submarine": 3, + "Destroyer": 2 +} + +# Helper: send JSON line +def send_line(conn, data): + try: + conn.sendall((json.dumps(data) + "\n").encode()) + except Exception as e: + print("Send error:", e) + +# Helper: read JSON line from file-like socket +def recv_line(f): + line = f.readline() + if not line: + return None + return json.loads(line.strip()) + +class Player: + def __init__(self, conn, addr, idx): + self.conn = conn + self.f = conn.makefile(mode="r", encoding="utf-8") + self.addr = addr + self.idx = idx + self.ready = False + self.board_ships = None # dict of ship_id -> list of (r,c) + self.hits_received = set() # positions hit by opponent + + def close(self): + try: + self.f.close() + except: + pass + try: + self.conn.close() + except: + pass + +class GameServer: + def __init__(self, host, port): + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.bind((host, port)) + self.sock.listen(4) + print(f"[SERVER] Listening on {host}:{port}") + self.players = [] + self.lock = threading.Lock() + self.turn = 0 # player index whose turn it is + + def accept_players(self): + while len(self.players) < 2: + conn, addr = self.sock.accept() + with self.lock: + idx = len(self.players) + player = Player(conn, addr, idx) + self.players.append(player) + print(f"[SERVER] Player {idx+1} connected from {addr}") + threading.Thread(target=self.handle_player, args=(player,), daemon=True).start() + + # when two players connected start handshake + self.broadcast({"type": "info", "msg": "2 players connected. Place your ships."}) + # ask for placements + self.broadcast({"type": "start_place", "ships": SHIPS_DEF}) + + def broadcast(self, data): + with self.lock: + for p in list(self.players): + try: + send_line(p.conn, data) + except: + pass + + def handle_player(self, player): + try: + send_line(player.conn, {"type": "welcome", "player_idx": player.idx, "msg": f"You are player {player.idx+1}"}) + while True: + data = recv_line(player.f) + if data is None: + print(f"[SERVER] Player {player.idx+1} disconnected") + break + self.process_message(player, data) + except Exception as e: + print("Player handler error:", e) + finally: + player.close() + + def process_message(self, player, data): + t = data.get("type") + if t == "place": + # expects 'ships': {name: [[r,c],...], ...} + ships = data.get("ships") + if not self.validate_placement(ships): + send_line(player.conn, {"type": "error", "msg": "Invalid placement"}) + return + player.board_ships = ships + player.ready = True + send_line(player.conn, {"type": "placed_ok"}) + print(f"[SERVER] Player {player.idx+1} placed ships") + # If both ready, start game + if all(p.ready for p in self.players) and len(self.players) >= 2: + time.sleep(0.2) + self.start_game() + elif t == "shot": + # only process if both ready + if not all(p.ready for p in self.players): + send_line(player.conn, {"type": "error", "msg": "Both players not ready"}) + return + if player.idx != self.turn: + send_line(player.conn, {"type": "error", "msg": "Not your turn"}) + return + r = data.get("r") + c = data.get("c") + self.handle_shot(player.idx, r, c) + elif t == "forfeit": + # opponent wins + winner = 1 - player.idx + self.end_game(winner, reason=f"Player {player.idx+1} forfeited") + else: + send_line(player.conn, {"type": "error", "msg": "Unknown message type"}) + + def validate_placement(self, ships): + # ships is a dict name->list of [r,c] + if not isinstance(ships, dict): + return False + occupied = set() + for name, coords in ships.items(): + if name not in SHIPS_DEF: + return False + if not isinstance(coords, list) or len(coords) != SHIPS_DEF[name]: + return False + # each coord valid and not overlapping, must form straight contiguous line + rows = [p[0] for p in coords] + cols = [p[1] for p in coords] + if any(not (0 <= rr < ROWS and 0 <= cc < COLS) for rr, cc in coords): + return False + # contiguous line check: either same row or same col + if not (len(set(rows)) == 1 or len(set(cols)) == 1): + return False + # ensure consecutive + if len(set(rows)) == 1: + sorted_cols = sorted(cols) + if sorted_cols != list(range(min(cols), max(cols)+1)): + return False + else: + sorted_rows = sorted(rows) + if sorted_rows != list(range(min(rows), max(rows)+1)): + return False + for p in coords: + if tuple(p) in occupied: + return False + occupied.add(tuple(p)) + return True + + def start_game(self): + # tell players game starts and whose turn + self.turn = 0 + for p in self.players: + send_line(p.conn, {"type": "start_game", "your_idx": p.idx}) + self.notify_turn() + + def notify_turn(self): + # notify both players whose turn it is + for p in self.players: + send_line(p.conn, {"type": "turn", "current": self.turn}) + + def handle_shot(self, shooter_idx, r, c): + shooter = self.players[shooter_idx] + target = self.players[1 - shooter_idx] + pos = (r, c) + # check bounds + if not (0 <= r < ROWS and 0 <= c < COLS): + send_line(shooter.conn, {"type": "error", "msg": "Shot out of bounds"}) + return + # if already shot there? check by looking at hits_received of target + if pos in target.hits_received: + send_line(shooter.conn, {"type": "error", "msg": "Position already shot"}) + return + # mark hit + target.hits_received.add(pos) + # determine result + hit_ship = None + for name, coords in target.board_ships.items(): + coords_t = [tuple(p) for p in coords] + if pos in coords_t: + hit_ship = name + break + if hit_ship is None: + # miss + send_line(shooter.conn, {"type": "result", "res": "miss", "r": r, "c": c}) + send_line(target.conn, {"type": "update", "opponent_shot": {"r": r, "c": c, "res": "miss"}}) + # switch turn + self.turn = 1 - self.turn + self.notify_turn() + else: + # check if ship sunk + coords_t = [tuple(p) for p in target.board_ships[hit_ship]] + sunk = all(p in target.hits_received for p in coords_t) + if sunk: + send_line(shooter.conn, {"type": "result", "res": "sunk", "ship": hit_ship, "r": r, "c": c}) + send_line(target.conn, {"type": "update", "opponent_shot": {"r": r, "c": c, "res": "sunk", "ship": hit_ship}}) + # check win: all ships coords in hits_received? + all_coords = [] + for coords in target.board_ships.values(): + all_coords.extend([tuple(p) for p in coords]) + if set(all_coords).issubset(target.hits_received): + # shooter wins + self.end_game(shooter_idx, reason="All ships sunk") + return + else: + # shooter keeps the turn after hit (classic rule variations exist; we keep shooter on turn) + self.notify_turn() + else: + send_line(shooter.conn, {"type": "result", "res": "hit", "r": r, "c": c}) + send_line(target.conn, {"type": "update", "opponent_shot": {"r": r, "c": c, "res": "hit"}}) + # shooter keeps turn + self.notify_turn() + + def end_game(self, winner_idx, reason=""): + print(f"[SERVER] Game ended. Winner: Player {winner_idx+1}. Reason: {reason}") + for p in self.players: + send_line(p.conn, {"type": "end", "winner": winner_idx, "reason": reason}) + # close sockets after a short pause + time.sleep(1.0) + for p in self.players: + try: + p.close() + except: + pass + # reset server for new game? here we exit + print("[SERVER] Shutting down.") + try: + self.sock.close() + except: + pass + exit(0) + + +if __name__ == "__main__": + server = GameServer(HOST, PORT) + server.accept_players() diff --git a/media1.mp4 b/media1.mp4 new file mode 100644 index 0000000..f69cae2 Binary files /dev/null and b/media1.mp4 differ