simplified the usage of socket thanks to the Packet class
This commit is contained in:
parent
4e376bc009
commit
c6ffbb9a58
16 changed files with 196 additions and 168 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
from enum import Enum
|
||||
|
||||
|
||||
class SocketType(Enum):
|
||||
CHAT = 0
|
||||
BOAT_PLACED = 1
|
||||
BOMB = 2
|
||||
BOMB_STATE = 3
|
|
@ -1,2 +1,4 @@
|
|||
from .game_network import game_network
|
||||
|
||||
from .Client import Client
|
||||
from .Host import Host
|
||||
|
|
45
source/network/game_network.py
Normal file
45
source/network/game_network.py
Normal 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)
|
|
@ -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"),
|
||||
)
|
|
@ -1,6 +0,0 @@
|
|||
from dataclasses import dataclass, field
|
||||
|
||||
|
||||
@dataclass
|
||||
class Chat:
|
||||
message: str = field()
|
18
source/network/packet/PacketBoatPlaced.py
Normal file
18
source/network/packet/PacketBoatPlaced.py
Normal 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))
|
25
source/network/packet/PacketBombPlaced.py
Normal file
25
source/network/packet/PacketBombPlaced.py
Normal 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))
|
|
@ -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))
|
||||
|
|
20
source/network/packet/PacketChat.py
Normal file
20
source/network/packet/PacketChat.py
Normal 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))
|
4
source/network/packet/__init__.py
Normal file
4
source/network/packet/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
from .PacketChat import PacketChat
|
||||
from .PacketBombPlaced import PacketBombPlaced
|
||||
from .PacketBombState import PacketBombState
|
||||
from .PacketBoatPlaced import PacketBoatPlaced
|
42
source/network/packet/abc/Packet.py
Normal file
42
source/network/packet/abc/Packet.py
Normal 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)
|
1
source/network/packet/abc/__init__.py
Normal file
1
source/network/packet/abc/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from .Packet import Packet
|
|
@ -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()
|
||||
|
|
Loading…
Reference in a new issue