TP03
This commit is contained in:
123
P1.py
Normal file
123
P1.py
Normal file
@@ -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 👋")
|
||||||
34
P2.py
Normal file
34
P2.py
Normal file
@@ -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)
|
||||||
39
P3.py
Normal file
39
P3.py
Normal file
@@ -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
|
||||||
147
P4.py
Normal file
147
P4.py
Normal file
@@ -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 !")
|
||||||
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()
|
||||||
261
battleship_server.py
Normal file
261
battleship_server.py
Normal file
@@ -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()
|
||||||
BIN
media1.mp4
Normal file
BIN
media1.mp4
Normal file
Binary file not shown.
Reference in New Issue
Block a user