simplified the usage of socket thanks to the Packet class

This commit is contained in:
Faraphel 2023-02-22 23:19:37 +01:00
parent 4e376bc009
commit c6ffbb9a58
16 changed files with 196 additions and 168 deletions

View file

@ -7,8 +7,7 @@ from source.gui.scene.abc import Scene
from source.gui import widget, texture
from source.gui.widget.grid import GameGridAlly, GameGridEnemy
from source import core
from source.network.SocketType import SocketType
from source.network.packet.Bomb import Bomb
from source.network.packet import PacketChat, PacketBombPlaced, PacketBoatPlaced
from source.type import Point2D
if TYPE_CHECKING:
@ -56,7 +55,7 @@ class Game(Scene):
)
def board_ally_ready():
connection.send(SocketType.BOAT_PLACED.value.to_bytes(1, "big"))
PacketBoatPlaced().send_connection(connection)
self.grid_ally.add_listener("on_all_boats_placed", board_ally_ready)
@ -76,8 +75,7 @@ class Game(Scene):
)
def board_enemy_bomb(cell: Point2D):
connection.send(SocketType.BOMB.value.to_bytes(1, "big"))
connection.send(Bomb(x=cell[0], y=cell[1]).to_bytes())
PacketBombPlaced(position=cell).send_connection(connection)
self.grid_enemy.add_listener("on_request_place_bomb", board_enemy_bomb)
@ -159,8 +157,7 @@ class Game(Scene):
self.chat_log.text += "\n" + text
self.chat_log.label.y = self.chat_log.y + self.chat_log.label.content_height
connection.send(SocketType["CHAT"].value.to_bytes(1, "big"))
connection.send(text.encode())
PacketChat(message=text).send_connection(connection)
self.chat_input.add_listener("on_enter", send_chat)
@ -205,5 +202,3 @@ class Game(Scene):
self.batch_grid_cursor.draw()
self.batch_label.draw()
self.grid_enemy.draw() # DEBUG

View file

@ -1,15 +1,7 @@
import socket
from queue import Queue
from typing import TYPE_CHECKING
import pyglet.clock
from source.core.enums import BombState
from source.core.error import PositionAlreadyShot, InvalidBombPosition
from source.gui import scene
from source.network.SocketType import SocketType
from source.network.packet.Bomb import Bomb
from source.network.packet.PacketBombState import PacketBombState
from source.network import game_network
from source.utils import StoppableThread
if TYPE_CHECKING:
@ -34,51 +26,4 @@ class Client(StoppableThread):
print(f"[Client] Connecté avec {connection}")
def create_game_scene(dt: float, queue: Queue):
game_scene = self.window.set_scene(scene.Game, connection=connection)
queue.put(game_scene)
queue = Queue()
pyglet.clock.schedule_once(create_game_scene, 0, queue)
game_scene = queue.get()
while True:
data = None
try: data = connection.recv(1)
except socket.timeout: pass
if not data:
if self._stop: return # vérifie si le thread n'est pas censé s'arrêter
continue
socket_type = SocketType(int.from_bytes(data, "big"))
match socket_type:
case SocketType.CHAT: print(connection.recv(1024).decode())
case SocketType.BOAT_PLACED: print("adversaire à posé ses bateaux")
case SocketType.BOMB:
bomb = Bomb.from_bytes(connection.recv(2))
try: bomb_state = game_scene.grid_ally.board.bomb((bomb.x, bomb.y))
except (InvalidBombPosition, PositionAlreadyShot): pass # TODO: gérer les erreurs
connection.send(SocketType.BOMB_STATE.value.to_bytes(1, "big"))
packet_bomb_state = PacketBombState(
x=bomb.x,
y=bomb.y,
bomb_state=bomb_state
)
connection.send(packet_bomb_state.to_bytes())
case SocketType.BOMB_STATE:
packet_bomb_state = PacketBombState.from_bytes(connection.recv(3))
touched = packet_bomb_state.bomb_state in [BombState.TOUCHED, BombState.SUNKEN, BombState.WON]
pyglet.clock.schedule_once(
lambda dt: game_scene.grid_enemy.place_bomb((packet_bomb_state.x, packet_bomb_state.y),
touched),
0
)
game_network(self, self.window, connection)

View file

@ -1,15 +1,7 @@
import socket
from queue import Queue
from typing import TYPE_CHECKING
import pyglet
from source.core.enums import BombState
from source.core.error import InvalidBombPosition, PositionAlreadyShot
from source.gui import scene
from source.network.SocketType import SocketType
from source.network.packet.Bomb import Bomb
from source.network.packet.PacketBombState import PacketBombState
from source.network import game_network
from source.utils import StoppableThread
if TYPE_CHECKING:
@ -38,56 +30,9 @@ class Host(StoppableThread):
connection, address = server.accept() # accepte la première connexion entrante
break # sort de la boucle
except socket.timeout: # en cas de timeout
if self._stop: return # vérifie si le thread n'est pas censé s'arrêter
if self.stopped: return # vérifie si le thread n'est pas censé s'arrêter
# sinon, réessaye
print(f"[Serveur] Connecté avec {address}")
def create_game_scene(dt: float, queue: Queue):
game_scene = self.window.set_scene(scene.Game, connection=connection)
queue.put(game_scene)
queue = Queue()
pyglet.clock.schedule_once(create_game_scene, 0, queue)
game_scene = queue.get()
while True:
data = None
try: data = connection.recv(1)
except socket.timeout: pass
if not data:
if self._stop: return # vérifie si le thread n'est pas censé s'arrêter
continue
socket_type = SocketType(int.from_bytes(data, "big"))
match socket_type:
case SocketType.CHAT: print(connection.recv(1024).decode())
case SocketType.BOAT_PLACED: print("adversaire à posé ses bateaux")
case SocketType.BOMB:
bomb = Bomb.from_bytes(connection.recv(2))
try: bomb_state = game_scene.grid_ally.board.bomb((bomb.x, bomb.y))
except (InvalidBombPosition, PositionAlreadyShot): pass # TODO: gérer les erreurs
connection.send(SocketType.BOMB_STATE.value.to_bytes(1, "big"))
packet_bomb_state = PacketBombState(
x=bomb.x,
y=bomb.y,
bomb_state=bomb_state
)
connection.send(packet_bomb_state.to_bytes())
case SocketType.BOMB_STATE:
packet_bomb_state = PacketBombState.from_bytes(connection.recv(3))
touched = packet_bomb_state.bomb_state in [BombState.TOUCHED, BombState.SUNKEN, BombState.WON]
pyglet.clock.schedule_once(
lambda dt: game_scene.grid_enemy.place_bomb((packet_bomb_state.x, packet_bomb_state.y),
touched),
0
)
game_network(self, self.window, connection)

View file

@ -1,8 +0,0 @@
from enum import Enum
class SocketType(Enum):
CHAT = 0
BOAT_PLACED = 1
BOMB = 2
BOMB_STATE = 3

View file

@ -1,2 +1,4 @@
from .game_network import game_network
from .Client import Client
from .Host import Host

View file

@ -0,0 +1,45 @@
import socket
from typing import Any
from source.core.enums import BombState
from source.core.error import InvalidBombPosition, PositionAlreadyShot
from source.gui import scene
from source.network.packet.abc import Packet
from source.network import packet
from source.gui.window import Window
from source.utils import StoppableThread
from source.utils.thread import in_pyglet_context
def game_network(thread: "StoppableThread", window: "Window", connection: socket.socket):
game_scene = in_pyglet_context(window.set_scene, scene.Game, connection=connection)
while True:
data: Any = Packet.from_connection(connection)
if data is None:
if thread.stopped: return # vérifie si le thread n'est pas censé s'arrêter
continue
match type(data):
case packet.PacketChat:
print(data.message)
case packet.PacketBoatPlaced:
print("adversaire à posé ses bateaux")
case packet.PacketBombPlaced:
try:
bomb_state = game_scene.grid_ally.board.bomb(data.position)
except (InvalidBombPosition, PositionAlreadyShot):
bomb_state = BombState.ERROR
packet.PacketBombState(position=data.position, bomb_state=bomb_state).send_connection(connection)
case packet.PacketBombState:
if data.bomb_state is BombState.ERROR: continue
touched = data.bomb_state in [BombState.TOUCHED, BombState.SUNKEN, BombState.WON]
in_pyglet_context(game_scene.grid_enemy.place_bomb, data.position, touched)

View file

@ -1,20 +0,0 @@
from dataclasses import dataclass, field
@dataclass
class Bomb:
x: int = field()
y: int = field()
def to_bytes(self) -> bytes:
return (
self.x.to_bytes(1, "big") +
self.y.to_bytes(1, "big")
)
@classmethod
def from_bytes(cls, data: bytes):
return cls(
x=int.from_bytes(data[0:1], "big"),
y=int.from_bytes(data[1:2], "big"),
)

View file

@ -1,6 +0,0 @@
from dataclasses import dataclass, field
@dataclass
class Chat:
message: str = field()

View file

@ -0,0 +1,18 @@
from dataclasses import dataclass
import socket
from source.network.packet.abc import Packet
@dataclass
class PacketBoatPlaced(Packet):
def to_bytes(self):
return b""
@classmethod
def from_bytes(cls, data: bytes):
return cls()
@classmethod
def from_connection(cls, connection: socket.socket) -> "PacketBoatPlaced":
return cls.from_bytes(connection.recv(0))

View file

@ -0,0 +1,25 @@
import socket
from dataclasses import dataclass, field
from source.network.packet.abc import Packet
from source.type import Point2D
@dataclass
class PacketBombPlaced(Packet):
position: Point2D = field()
def to_bytes(self):
x, y = self.position
return x.to_bytes(1, "big") + y.to_bytes(1, "big")
@classmethod
def from_bytes(cls, data: bytes):
return cls(position=(
int.from_bytes(data[0:1], "big"),
int.from_bytes(data[1:2], "big"),
))
@classmethod
def from_connection(cls, connection: socket.socket) -> "PacketBombPlaced":
return cls.from_bytes(connection.recv(2))

View file

@ -1,25 +1,35 @@
import socket
from dataclasses import dataclass, field
from source.core.enums import BombState
from source.network.packet.abc import Packet
from source.type import Point2D
@dataclass
class PacketBombState:
x: int = field()
y: int = field()
class PacketBombState(Packet):
position: Point2D = field()
bomb_state: BombState = field()
def to_bytes(self) -> bytes:
def to_bytes(self):
x, y = self.position
return (
self.x.to_bytes(1, "big") +
self.y.to_bytes(1, "big") +
x.to_bytes(1, "big") +
y.to_bytes(1, "big") +
self.bomb_state.value.to_bytes()
)
@classmethod
def from_bytes(cls, data: bytes):
return cls(
x=int.from_bytes(data[0:1], "big"),
y=int.from_bytes(data[1:2], "big"),
position=(
int.from_bytes(data[0:1], "big"),
int.from_bytes(data[1:2], "big"),
),
bomb_state=BombState.from_bytes(data[2:3])
)
@classmethod
def from_connection(cls, connection: socket.socket) -> "PacketBombState":
return cls.from_bytes(connection.recv(3))

View file

@ -0,0 +1,20 @@
import socket
from dataclasses import dataclass, field
from source.network.packet.abc import Packet
@dataclass
class PacketChat(Packet):
message: str = field()
def to_bytes(self):
return self.message.encode("utf-8")
@classmethod
def from_bytes(cls, data: bytes):
return cls(message=data.decode("utf-8"))
@classmethod
def from_connection(cls, connection: socket.socket) -> "PacketChat":
return cls.from_bytes(connection.recv(256))

View file

@ -0,0 +1,4 @@
from .PacketChat import PacketChat
from .PacketBombPlaced import PacketBombPlaced
from .PacketBombState import PacketBombState
from .PacketBoatPlaced import PacketBoatPlaced

View file

@ -0,0 +1,42 @@
import socket
from abc import abstractmethod, ABC
from typing import Type, Optional
class Packet(ABC):
packed_header: bytes
packet_id: int = 0
def __init_subclass__(cls, **kwargs):
cls.packet_header = Packet.packet_id.to_bytes(1, "big")
Packet.packet_id = Packet.packet_id + 1
@classmethod
def cls_from_header(cls, packet_header: bytes) -> Type["Packet"]:
return next(filter(
lambda subcls: subcls.packet_header == packet_header,
cls.__subclasses__()
))
@abstractmethod
def to_bytes(self) -> bytes:
pass
@classmethod
@abstractmethod
def from_bytes(cls, data: bytes) -> "Packet":
pass
def send_connection(self, connection: socket.socket) -> None:
connection.send(self.packet_header)
connection.send(self.to_bytes())
@classmethod
def from_connection(cls, connection: socket.socket) -> Optional[Type["Packet"]]:
packet_header: Optional[bytes] = None
try: packet_header = connection.recv(1)
except socket.timeout: pass
if not packet_header: return None
return cls.cls_from_header(packet_header).from_connection(connection)

View file

@ -0,0 +1 @@
from .Packet import Packet

View file

@ -1,4 +1,8 @@
from queue import Queue
from threading import Thread
from typing import Any, Callable
import pyglet
class StoppableThread(Thread):
@ -9,7 +13,13 @@ class StoppableThread(Thread):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._stop = False
self.stopped = False
def stop(self) -> None:
self._stop = True
self.stopped = True
def in_pyglet_context(func: Callable, *args, **kwargs) -> Any:
queue = Queue()
pyglet.clock.schedule_once(lambda dt: queue.put(func(*args, **kwargs)), 0)
return queue.get()