changed the packet code to be simpler and more flexible
This commit is contained in:
parent
58287a0649
commit
37350926b2
13 changed files with 136 additions and 76 deletions
5
NOTE.md
5
NOTE.md
|
@ -1,12 +1,11 @@
|
|||
A faire :
|
||||
- Limite des bateaux, taille des pseudo, ...
|
||||
|
||||
- Nom dans les options
|
||||
- Faire marcher le tchat
|
||||
- Sauvegarde / Quitter
|
||||
- Historique / Replay
|
||||
- Police d'écriture
|
||||
- Documenter
|
||||
|
||||
|
||||
- Voir si les event listener intégré à pyglet sont plus pratique que l'event propagation (?)
|
||||
- Faire une scène incluant par défaut les boutons "Retour" (?)
|
||||
|
||||
|
|
|
@ -296,14 +296,18 @@ class RoomCreate(Scene):
|
|||
port = int(self.input_port.text)
|
||||
|
||||
settings = PacketSettings(
|
||||
username=self.input_username.text,
|
||||
grid_width=int(self.input_width.text),
|
||||
grid_height=int(self.input_height.text),
|
||||
host_start=self.checkbox_host_start.state,
|
||||
boats_length=[size for size, quantity in self.boat_size_amount.items() for _ in range(quantity)]
|
||||
)
|
||||
|
||||
self.window.set_scene(RoomHost, port=port, settings=settings)
|
||||
self.window.set_scene(
|
||||
RoomHost,
|
||||
port=port,
|
||||
username=self.input_username.text,
|
||||
settings=settings
|
||||
)
|
||||
|
||||
def on_draw(self):
|
||||
self.batch_input_background.draw()
|
||||
|
|
|
@ -14,9 +14,10 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
class RoomHost(Scene):
|
||||
def __init__(self, window: "Window", port: int, settings: "PacketSettings", **kwargs):
|
||||
def __init__(self, window: "Window", port: int, username: str, settings: "PacketSettings", **kwargs):
|
||||
super().__init__(window, **kwargs)
|
||||
|
||||
self.username: str = username
|
||||
self.ip_address: str = "127.0.0.1"
|
||||
self.port: int = port
|
||||
|
||||
|
@ -59,7 +60,13 @@ class RoomHost(Scene):
|
|||
batch=self.batch_label
|
||||
)
|
||||
|
||||
self.thread_network = network.Host(window=self.window, daemon=True, port=self.port, settings=settings)
|
||||
self.thread_network = network.Host(
|
||||
window=self.window,
|
||||
daemon=True,
|
||||
port=self.port,
|
||||
username=self.username,
|
||||
settings=settings
|
||||
)
|
||||
self.thread_network.start()
|
||||
|
||||
self._refresh_ip_text()
|
||||
|
@ -70,10 +77,10 @@ class RoomHost(Scene):
|
|||
def _refresh_ip(self):
|
||||
while True:
|
||||
try:
|
||||
response = requests.get('https://api.ipify.org')
|
||||
response = requests.get('https://api.ipify.org', timeout=10)
|
||||
response.raise_for_status()
|
||||
break
|
||||
except requests.HTTPError:
|
||||
except (requests.HTTPError, requests.Timeout):
|
||||
if self.thread_ip.stopped: return
|
||||
|
||||
self.ip_address: str = response.content.decode('utf8')
|
||||
|
|
|
@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Any
|
|||
|
||||
from source.gui import scene
|
||||
from source.network import game_network
|
||||
from source.network.packet import PacketUsername
|
||||
from source.network.packet import PacketUsername, PacketSettings
|
||||
from source.network.packet.abc import Packet
|
||||
from source.utils import StoppableThread
|
||||
from source.utils.thread import in_pyglet_context
|
||||
|
@ -34,8 +34,9 @@ class Client(StoppableThread):
|
|||
|
||||
print(f"[Client] Connecté avec {connection}")
|
||||
|
||||
settings: Any = Packet.from_connection(connection)
|
||||
PacketUsername(username=self.username).send_connection(connection)
|
||||
settings: Any = PacketSettings.from_connection(connection)
|
||||
PacketUsername(username=self.username).instance_send_connection(connection)
|
||||
packet_username = PacketUsername.from_connection(connection)
|
||||
|
||||
game_scene = in_pyglet_context(
|
||||
self.window.set_scene,
|
||||
|
@ -45,7 +46,7 @@ class Client(StoppableThread):
|
|||
|
||||
boats_length=settings.boats_length,
|
||||
name_ally=self.username,
|
||||
name_enemy=settings.username,
|
||||
name_enemy=packet_username.username,
|
||||
grid_width=settings.grid_width,
|
||||
grid_height=settings.grid_height,
|
||||
my_turn=not settings.host_start
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import socket
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from source.gui import scene
|
||||
from source.network import game_network
|
||||
from source.network.packet.abc import Packet
|
||||
from source.utils import StoppableThread
|
||||
from source.utils.thread import in_pyglet_context
|
||||
from source.network.packet import PacketUsername
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from source.gui.window import Window
|
||||
|
@ -17,10 +17,11 @@ class Host(StoppableThread):
|
|||
The thread executed on the person who create a room.
|
||||
"""
|
||||
|
||||
def __init__(self, window: "Window", port: int, settings: "PacketSettings", **kw):
|
||||
def __init__(self, window: "Window", port: int, username: str, settings: "PacketSettings", **kw):
|
||||
super().__init__(**kw)
|
||||
|
||||
self.window = window
|
||||
self.username = username
|
||||
self.settings = settings
|
||||
self.port = port
|
||||
|
||||
|
@ -43,8 +44,9 @@ class Host(StoppableThread):
|
|||
|
||||
print(f"[Serveur] Connecté avec {address}")
|
||||
|
||||
self.settings.send_connection(connection)
|
||||
packet_username: Any = Packet.from_connection(connection)
|
||||
self.settings.instance_send_connection(connection)
|
||||
packet_username = PacketUsername.from_connection(connection)
|
||||
PacketUsername(username=self.username).instance_send_connection(connection)
|
||||
|
||||
game_scene = in_pyglet_context(
|
||||
self.window.set_scene,
|
||||
|
@ -53,7 +55,7 @@ class Host(StoppableThread):
|
|||
connection=connection,
|
||||
|
||||
boats_length=self.settings.boats_length,
|
||||
name_ally=self.settings.username,
|
||||
name_ally=self.username,
|
||||
name_enemy=packet_username.username,
|
||||
grid_width=self.settings.grid_width,
|
||||
grid_height=self.settings.grid_height,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import socket
|
||||
from typing import Any
|
||||
from typing import Type, Callable
|
||||
|
||||
from source.gui.scene import Game
|
||||
from source.network.packet.abc import Packet
|
||||
|
@ -21,7 +21,7 @@ def game_network(
|
|||
:param connection: the connection with the other player
|
||||
"""
|
||||
|
||||
game_methods = {
|
||||
game_methods: dict[Type["Packet"], Callable] = {
|
||||
packet.PacketChat: game_scene.network_on_chat,
|
||||
packet.PacketBoatPlaced: game_scene.network_on_boat_placed,
|
||||
packet.PacketBombPlaced: game_scene.network_on_bomb_placed,
|
||||
|
@ -29,13 +29,15 @@ def game_network(
|
|||
}
|
||||
|
||||
while True:
|
||||
data: Any = Packet.from_connection(connection)
|
||||
data_type: Type["Packet"] = Packet.type_from_connection(connection)
|
||||
|
||||
if data is None:
|
||||
if data_type is None:
|
||||
if thread.stopped: return # vérifie si le thread n'est pas censé s'arrêter
|
||||
continue
|
||||
|
||||
data = data_type.from_connection(connection)
|
||||
|
||||
if in_pyglet_context(
|
||||
game_methods[type(data)], # récupère la methode relié ce type de donnée
|
||||
game_methods[data_type], # récupère la methode relié ce type de donnée
|
||||
connection, data
|
||||
): return # Appelle la méthode. Si elle renvoie True, arrête le thread
|
||||
|
|
|
@ -9,8 +9,6 @@ class PacketBoatPlaced(Packet):
|
|||
A packet that signal that all the boat of the player have been placed
|
||||
"""
|
||||
|
||||
packet_size: int = 0
|
||||
|
||||
def to_bytes(self):
|
||||
return b""
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ class PacketBombPlaced(Packet):
|
|||
|
||||
position: Point2D = field()
|
||||
|
||||
packet_size: int = 2
|
||||
packet_format: str = ">BB"
|
||||
|
||||
def to_bytes(self):
|
||||
|
|
|
@ -15,7 +15,6 @@ class PacketBombState(Packet):
|
|||
position: Point2D = field()
|
||||
bomb_state: BombState = field()
|
||||
|
||||
packet_size: int = 3
|
||||
packet_format: str = ">BBb"
|
||||
|
||||
def to_bytes(self):
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import socket
|
||||
import struct
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
from source.network.packet.abc import Packet
|
||||
|
||||
|
@ -11,11 +14,27 @@ class PacketChat(Packet):
|
|||
|
||||
message: str = field()
|
||||
|
||||
packet_size: int = 256
|
||||
packet_format = ">I"
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
return self.message.encode("utf-8")
|
||||
message: bytes = self.message.encode("utf-8")
|
||||
message_len: int = len(message)
|
||||
|
||||
# envoie la taille du message, suivi des données du message
|
||||
return struct.pack(f"{self.packet_format}{message_len}s", message_len, message)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data: bytes):
|
||||
return cls(message=data.decode("utf-8"))
|
||||
def from_connection(cls, connection: socket.socket) -> "Packet":
|
||||
message_len, *_ = struct.unpack(
|
||||
cls.packet_format,
|
||||
connection.recv(struct.calcsize(cls.packet_format))
|
||||
)
|
||||
|
||||
format_: str = f">{message_len}s"
|
||||
|
||||
message, *_ = struct.unpack(
|
||||
format_,
|
||||
connection.recv(struct.calcsize(format_))
|
||||
)
|
||||
|
||||
return cls(message=message.decode("utf-8"))
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import socket
|
||||
import struct
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
|
@ -6,37 +7,45 @@ from source.network.packet.abc import Packet
|
|||
|
||||
@dataclass
|
||||
class PacketSettings(Packet):
|
||||
username: str = field()
|
||||
grid_width: int = field()
|
||||
grid_height: int = field()
|
||||
host_start: bool = field()
|
||||
boats_length: list = field()
|
||||
|
||||
packet_size: int = 51
|
||||
packet_format: str = ">16sBB?32B"
|
||||
packet_format: str = ">BB?I"
|
||||
|
||||
def to_bytes(self):
|
||||
boat_size = self.boats_length + ([0] * (32 - len(self.boats_length)))
|
||||
boats_len: int = len(self.boats_length)
|
||||
|
||||
return struct.pack(
|
||||
self.packet_format,
|
||||
f"{self.packet_format}{boats_len}B",
|
||||
|
||||
self.username.encode("utf-8"),
|
||||
self.grid_width,
|
||||
self.grid_height,
|
||||
self.host_start,
|
||||
|
||||
*boat_size
|
||||
boats_len,
|
||||
|
||||
*self.boats_length
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data: bytes):
|
||||
username, grid_width, grid_height, host_start, *boats_length = struct.unpack(cls.packet_format, data)
|
||||
def from_connection(cls, connection: socket.socket) -> "Packet":
|
||||
grid_width, grid_height, host_start, boats_len = struct.unpack(
|
||||
cls.packet_format,
|
||||
connection.recv(struct.calcsize(cls.packet_format))
|
||||
)
|
||||
|
||||
format_ = f">{boats_len}B"
|
||||
|
||||
boats_length = struct.unpack(
|
||||
format_,
|
||||
connection.recv(struct.calcsize(format_))
|
||||
)
|
||||
|
||||
return cls(
|
||||
username=username.replace(b"\x00", b"").decode("utf-8"),
|
||||
grid_width=grid_width,
|
||||
grid_height=grid_height,
|
||||
host_start=host_start,
|
||||
boats_length=list(filter(lambda value: value != 0, boats_length))
|
||||
boats_length=list(boats_length)
|
||||
)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import socket
|
||||
import struct
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from source.network.packet.abc import Packet
|
||||
|
@ -7,11 +9,26 @@ from source.network.packet.abc import Packet
|
|||
class PacketUsername(Packet):
|
||||
username: str = field()
|
||||
|
||||
packet_size: int = 16
|
||||
packet_format: str = ">I"
|
||||
|
||||
def to_bytes(self):
|
||||
return self.username.encode("utf-8")
|
||||
username = self.username.encode()
|
||||
username_len = len(username)
|
||||
|
||||
return struct.pack(f"{self.packet_format}{username_len}s", username_len, username)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data: bytes):
|
||||
return cls(username=data.decode("utf-8"))
|
||||
def from_connection(cls, connection: socket.socket) -> "PacketUsername":
|
||||
username_len, *_ = struct.unpack(
|
||||
cls.packet_format,
|
||||
connection.recv(struct.calcsize(cls.packet_format))
|
||||
)
|
||||
|
||||
format_: str = f">{username_len}s"
|
||||
|
||||
username, *_ = struct.unpack(
|
||||
format_,
|
||||
connection.recv(struct.calcsize(format_))
|
||||
)
|
||||
|
||||
return cls(username=username.decode("utf-8"))
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
import socket
|
||||
import struct
|
||||
from abc import abstractmethod, ABC
|
||||
from typing import Type, Optional
|
||||
|
||||
# TODO: struct.calcsize() au lieu de packet_size
|
||||
|
||||
|
||||
class Packet(ABC):
|
||||
packed_header: bytes
|
||||
packet_size: int
|
||||
packet_id: int = 0
|
||||
packet_format: str = ">"
|
||||
|
||||
def __init_subclass__(cls, **kwargs):
|
||||
cls.packet_header = Packet.packet_id.to_bytes(1, "big") # give a header to the newly created subclass
|
||||
|
@ -22,18 +21,22 @@ class Packet(ABC):
|
|||
"""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def from_bytes(cls, data: bytes) -> "Packet":
|
||||
def send_connection(self, connection: socket.socket):
|
||||
"""
|
||||
Convert a bytes object into a packet.
|
||||
:param data: the data to convert into a packet. Should be "packet_size" long.
|
||||
:return: a packet corresponding to the bytes.
|
||||
Send the packet directly into a socket.
|
||||
:param connection: the socket where to send the packet to.
|
||||
"""
|
||||
pass
|
||||
connection.send(self.packet_header + self.to_bytes())
|
||||
|
||||
def instance_send_connection(self, connection: socket.socket):
|
||||
"""
|
||||
Send the packet directly into a socket.
|
||||
:param connection: the socket where to send the packet to.
|
||||
"""
|
||||
connection.send(self.to_bytes())
|
||||
|
||||
@classmethod
|
||||
def cls_from_header(cls, packet_header: bytes) -> Type["Packet"]:
|
||||
def type_from_header(cls, packet_header: bytes) -> Type["Packet"]:
|
||||
"""
|
||||
Get a subclass from its packet header.
|
||||
:param packet_header: the header to find the corresponding subclass
|
||||
|
@ -44,31 +47,32 @@ class Packet(ABC):
|
|||
cls.__subclasses__()
|
||||
))
|
||||
|
||||
def send_connection(self, connection: socket.socket):
|
||||
@classmethod
|
||||
def from_bytes(cls, data: bytes) -> "Packet":
|
||||
"""
|
||||
Send the packet directly into a socket.
|
||||
:param connection: the socket where to send the packet to.
|
||||
Convert a bytes object into a packet.
|
||||
:param data: the data to convert into a packet. Should be "packet_size" long.
|
||||
:return: a packet corresponding to the bytes.
|
||||
"""
|
||||
connection.send(self.packet_header + self.to_bytes())
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def from_connection(cls, connection: socket.socket) -> Optional["Packet"]:
|
||||
def type_from_connection(cls, connection: socket.socket) -> Optional[Type["Packet"]]:
|
||||
try:
|
||||
packet_header = connection.recv(1)
|
||||
except socket.timeout:
|
||||
return None
|
||||
|
||||
if not packet_header: return None # ignore si le header est invalide
|
||||
return cls.type_from_header(packet_header)
|
||||
|
||||
@classmethod
|
||||
def from_connection(cls, connection: socket.socket) -> "Packet":
|
||||
"""
|
||||
Receive a packet from a socket.
|
||||
:param connection: the socket where to get the data from
|
||||
:return: the packet, or None if there was nothing in the socket to receive.
|
||||
"""
|
||||
|
||||
# get the packet type
|
||||
|
||||
packet_header: Optional[bytes] = None
|
||||
try:
|
||||
packet_header = connection.recv(1)
|
||||
except socket.timeout:
|
||||
pass
|
||||
|
||||
if not packet_header: return None # ignore si le header est invalide
|
||||
packet_type = cls.cls_from_header(packet_header)
|
||||
|
||||
# renvoie les données instanciées
|
||||
return packet_type.from_bytes(connection.recv(packet_type.packet_size))
|
||||
packet_size: int = struct.calcsize(cls.packet_format)
|
||||
return cls.from_bytes(connection.recv(packet_size))
|
||||
|
|
Loading…
Reference in a new issue