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 import widget, texture
|
||||||
from source.gui.widget.grid import GameGridAlly, GameGridEnemy
|
from source.gui.widget.grid import GameGridAlly, GameGridEnemy
|
||||||
from source import core
|
from source import core
|
||||||
from source.network.SocketType import SocketType
|
from source.network.packet import PacketChat, PacketBombPlaced, PacketBoatPlaced
|
||||||
from source.network.packet.Bomb import Bomb
|
|
||||||
from source.type import Point2D
|
from source.type import Point2D
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -56,7 +55,7 @@ class Game(Scene):
|
||||||
)
|
)
|
||||||
|
|
||||||
def board_ally_ready():
|
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)
|
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):
|
def board_enemy_bomb(cell: Point2D):
|
||||||
connection.send(SocketType.BOMB.value.to_bytes(1, "big"))
|
PacketBombPlaced(position=cell).send_connection(connection)
|
||||||
connection.send(Bomb(x=cell[0], y=cell[1]).to_bytes())
|
|
||||||
|
|
||||||
self.grid_enemy.add_listener("on_request_place_bomb", board_enemy_bomb)
|
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.text += "\n" + text
|
||||||
self.chat_log.label.y = self.chat_log.y + self.chat_log.label.content_height
|
self.chat_log.label.y = self.chat_log.y + self.chat_log.label.content_height
|
||||||
|
|
||||||
connection.send(SocketType["CHAT"].value.to_bytes(1, "big"))
|
PacketChat(message=text).send_connection(connection)
|
||||||
connection.send(text.encode())
|
|
||||||
|
|
||||||
self.chat_input.add_listener("on_enter", send_chat)
|
self.chat_input.add_listener("on_enter", send_chat)
|
||||||
|
|
||||||
|
@ -205,5 +202,3 @@ class Game(Scene):
|
||||||
self.batch_grid_cursor.draw()
|
self.batch_grid_cursor.draw()
|
||||||
|
|
||||||
self.batch_label.draw()
|
self.batch_label.draw()
|
||||||
|
|
||||||
self.grid_enemy.draw() # DEBUG
|
|
||||||
|
|
|
@ -1,15 +1,7 @@
|
||||||
import socket
|
import socket
|
||||||
from queue import Queue
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import pyglet.clock
|
from source.network import game_network
|
||||||
|
|
||||||
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.utils import StoppableThread
|
from source.utils import StoppableThread
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -34,51 +26,4 @@ class Client(StoppableThread):
|
||||||
|
|
||||||
print(f"[Client] Connecté avec {connection}")
|
print(f"[Client] Connecté avec {connection}")
|
||||||
|
|
||||||
def create_game_scene(dt: float, queue: Queue):
|
game_network(self, self.window, connection)
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,15 +1,7 @@
|
||||||
import socket
|
import socket
|
||||||
from queue import Queue
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import pyglet
|
from source.network import game_network
|
||||||
|
|
||||||
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.utils import StoppableThread
|
from source.utils import StoppableThread
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -38,56 +30,9 @@ class Host(StoppableThread):
|
||||||
connection, address = server.accept() # accepte la première connexion entrante
|
connection, address = server.accept() # accepte la première connexion entrante
|
||||||
break # sort de la boucle
|
break # sort de la boucle
|
||||||
except socket.timeout: # en cas de timeout
|
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
|
# sinon, réessaye
|
||||||
|
|
||||||
print(f"[Serveur] Connecté avec {address}")
|
print(f"[Serveur] Connecté avec {address}")
|
||||||
|
|
||||||
def create_game_scene(dt: float, queue: Queue):
|
game_network(self, self.window, connection)
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
|
@ -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 .Client import Client
|
||||||
from .Host import Host
|
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 dataclasses import dataclass, field
|
||||||
|
|
||||||
from source.core.enums import BombState
|
from source.core.enums import BombState
|
||||||
|
from source.network.packet.abc import Packet
|
||||||
|
from source.type import Point2D
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PacketBombState:
|
class PacketBombState(Packet):
|
||||||
x: int = field()
|
position: Point2D = field()
|
||||||
y: int = field()
|
|
||||||
bomb_state: BombState = field()
|
bomb_state: BombState = field()
|
||||||
|
|
||||||
def to_bytes(self) -> bytes:
|
def to_bytes(self):
|
||||||
|
x, y = self.position
|
||||||
|
|
||||||
return (
|
return (
|
||||||
self.x.to_bytes(1, "big") +
|
x.to_bytes(1, "big") +
|
||||||
self.y.to_bytes(1, "big") +
|
y.to_bytes(1, "big") +
|
||||||
self.bomb_state.value.to_bytes()
|
self.bomb_state.value.to_bytes()
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_bytes(cls, data: bytes):
|
def from_bytes(cls, data: bytes):
|
||||||
return cls(
|
return cls(
|
||||||
x=int.from_bytes(data[0:1], "big"),
|
position=(
|
||||||
y=int.from_bytes(data[1:2], "big"),
|
int.from_bytes(data[0:1], "big"),
|
||||||
|
int.from_bytes(data[1:2], "big"),
|
||||||
|
),
|
||||||
bomb_state=BombState.from_bytes(data[2:3])
|
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 threading import Thread
|
||||||
|
from typing import Any, Callable
|
||||||
|
|
||||||
|
import pyglet
|
||||||
|
|
||||||
|
|
||||||
class StoppableThread(Thread):
|
class StoppableThread(Thread):
|
||||||
|
@ -9,7 +13,13 @@ class StoppableThread(Thread):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self._stop = False
|
self.stopped = False
|
||||||
|
|
||||||
def stop(self) -> None:
|
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