added more documentation in some files
This commit is contained in:
parent
dc4cb3b1a7
commit
a2d37c37f7
28 changed files with 168 additions and 31 deletions
6
NOTE.md
6
NOTE.md
|
@ -6,6 +6,7 @@ A faire :
|
||||||
- Police d'écriture
|
- Police d'écriture
|
||||||
|
|
||||||
- Voir si les event listener intégré à pyglet sont plus pratique que l'event propagation
|
- Voir si les event listener intégré à pyglet sont plus pratique que l'event propagation
|
||||||
|
- Documenter
|
||||||
|
|
||||||
Bug :
|
Bug :
|
||||||
- /
|
- /
|
||||||
|
@ -14,8 +15,9 @@ Autre :
|
||||||
- Tester sur Linux
|
- Tester sur Linux
|
||||||
|
|
||||||
|
|
||||||
|
A expliquer :
|
||||||
|
- in_pyglet_context
|
||||||
|
- Packet
|
||||||
|
|
||||||
|
|
||||||
Bonus ultime :
|
Bonus ultime :
|
||||||
|
|
|
@ -8,6 +8,11 @@ from source.utils import copy_array_offset
|
||||||
|
|
||||||
|
|
||||||
class Board:
|
class Board:
|
||||||
|
"""
|
||||||
|
Represent a board for the game.
|
||||||
|
Boat can be added and bomb can be placed.
|
||||||
|
"""
|
||||||
|
|
||||||
__slots__ = ("_columns", "_rows", "_boats", "_bombs")
|
__slots__ = ("_columns", "_rows", "_boats", "_bombs")
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
|
|
@ -4,6 +4,11 @@ from source.core.enums import Orientation
|
||||||
|
|
||||||
|
|
||||||
class Boat:
|
class Boat:
|
||||||
|
"""
|
||||||
|
Represent a boat.
|
||||||
|
It can be added to a board.
|
||||||
|
"""
|
||||||
|
|
||||||
__slots__ = ("orientation", "length")
|
__slots__ = ("orientation", "length")
|
||||||
|
|
||||||
def __init__(self, length: int, orientation: Orientation):
|
def __init__(self, length: int, orientation: Orientation):
|
||||||
|
|
|
@ -2,16 +2,13 @@ from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
class BombState(Enum):
|
class BombState(Enum):
|
||||||
NOTHING = 0
|
"""
|
||||||
TOUCHED = 1
|
This class represent the state of a bomb after being place on the board.
|
||||||
SUNKEN = 2
|
"""
|
||||||
WON = 3
|
|
||||||
|
|
||||||
ERROR = 10
|
NOTHING = 0 # the bomb missed
|
||||||
|
TOUCHED = 1 # the bomb touched a boat
|
||||||
|
SUNKEN = 2 # the bomb touched the last part of a boat
|
||||||
|
WON = 3 # the bomb sunk the last boat
|
||||||
|
|
||||||
def to_bytes(self) -> bytes:
|
ERROR = 10 # the bomb could not be placed
|
||||||
return self.value.to_bytes(1, "big")
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_bytes(cls, data: bytes):
|
|
||||||
return cls(int.from_bytes(data, "big"))
|
|
||||||
|
|
|
@ -2,6 +2,10 @@ from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
class Orientation(Enum):
|
class Orientation(Enum):
|
||||||
|
"""
|
||||||
|
Represent the orientation of a boat.
|
||||||
|
"""
|
||||||
|
|
||||||
HORIZONTAL = "H"
|
HORIZONTAL = "H"
|
||||||
VERTICAL = "V"
|
VERTICAL = "V"
|
||||||
|
|
||||||
|
|
|
@ -2,16 +2,36 @@ from typing import Callable
|
||||||
|
|
||||||
|
|
||||||
class Listener:
|
class Listener:
|
||||||
|
"""
|
||||||
|
The Listener can be subclassed to allow the subclass to add, remove and call event easily.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._events_listener: dict[str, set[Callable]] = {}
|
self._events_listener: dict[str, set[Callable]] = {}
|
||||||
|
|
||||||
def add_listener(self, name: str, callback: Callable):
|
def add_listener(self, name: str, callback: Callable):
|
||||||
|
"""
|
||||||
|
Add a function to an event name
|
||||||
|
:param name: the name of the event to react
|
||||||
|
:param callback: the function to call
|
||||||
|
"""
|
||||||
if name not in self._events_listener: self._events_listener[name] = set()
|
if name not in self._events_listener: self._events_listener[name] = set()
|
||||||
self._events_listener[name].add(callback)
|
self._events_listener[name].add(callback)
|
||||||
|
|
||||||
def remove_listener(self, name: str, callback: Callable):
|
def remove_listener(self, name: str, callback: Callable):
|
||||||
|
"""
|
||||||
|
Remove a function from an event name
|
||||||
|
:param name: the event name where to remove the callback
|
||||||
|
:param callback: the callback function to remove
|
||||||
|
"""
|
||||||
self._events_listener[name].remove(callback)
|
self._events_listener[name].remove(callback)
|
||||||
|
|
||||||
def trigger_event(self, name: str, *args, **kwargs):
|
def trigger_event(self, name: str, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Call all the callback attached to an event
|
||||||
|
:param name: the name of the event to call
|
||||||
|
:param args: the args of the callbacks
|
||||||
|
:param kwargs: the kwargs of the callbacks
|
||||||
|
"""
|
||||||
for listener in self._events_listener.get(name, set()):
|
for listener in self._events_listener.get(name, set()):
|
||||||
listener(*args, **kwargs)
|
listener(*args, **kwargs)
|
||||||
|
|
|
@ -6,6 +6,11 @@ from source.gui.event import StopEvent
|
||||||
|
|
||||||
|
|
||||||
class EventPropagationMixin:
|
class EventPropagationMixin:
|
||||||
|
"""
|
||||||
|
This class can be subclassed to allow the subclass to propagate all the call to the method that start by
|
||||||
|
"on_" to the object in the "childs" property.
|
||||||
|
"""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def childs(self):
|
def childs(self):
|
||||||
|
|
|
@ -1,2 +1,5 @@
|
||||||
class StopEvent(Exception):
|
class StopEvent(Exception):
|
||||||
|
"""
|
||||||
|
This error can be raised to prevent an event to propagate to further element.
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -6,6 +6,14 @@ import pyglet
|
||||||
|
|
||||||
|
|
||||||
class Style(ABC):
|
class Style(ABC):
|
||||||
|
"""
|
||||||
|
This class represent a style that can be attached to a widget.
|
||||||
|
All property of the class will be loaded into a pyglet image.
|
||||||
|
|
||||||
|
If the property is associated to only a Path, a simple image will be loaded.
|
||||||
|
If the property is associated to a list of Path, an animation will be loaded.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init_subclass__(cls, **kwargs):
|
def __init_subclass__(cls, **kwargs):
|
||||||
atlas = pyglet.image.atlas.TextureAtlas()
|
atlas = pyglet.image.atlas.TextureAtlas()
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,10 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class Checkbox(BoxWidget):
|
class Checkbox(BoxWidget):
|
||||||
|
"""
|
||||||
|
A checkbox widget with a background texture that change depending on if it is checked or unchecked.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, scene: "Scene",
|
def __init__(self, scene: "Scene",
|
||||||
|
|
||||||
style: Type[Style],
|
style: Type[Style],
|
||||||
|
|
|
@ -11,6 +11,10 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class Image(BoxWidget):
|
class Image(BoxWidget):
|
||||||
|
"""
|
||||||
|
An image widget with a texture.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, scene: "Scene",
|
def __init__(self, scene: "Scene",
|
||||||
|
|
||||||
image: pyglet.image.AbstractImage,
|
image: pyglet.image.AbstractImage,
|
||||||
|
|
|
@ -14,6 +14,10 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class Input(BoxWidget):
|
class Input(BoxWidget):
|
||||||
|
"""
|
||||||
|
An input widget with a background texture and a label. A regex pattern can be added to validate the input.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, scene: "Scene",
|
def __init__(self, scene: "Scene",
|
||||||
|
|
||||||
style: Type[Style],
|
style: Type[Style],
|
||||||
|
|
|
@ -13,6 +13,11 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class Scroller(BoxWidget):
|
class Scroller(BoxWidget):
|
||||||
|
"""
|
||||||
|
A scroller widget with a background texture, a scroller and a label.
|
||||||
|
The cursor can be moved between the "from" and the "to" value
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, scene: "Scene",
|
def __init__(self, scene: "Scene",
|
||||||
|
|
||||||
style: Type[Style],
|
style: Type[Style],
|
||||||
|
|
|
@ -11,7 +11,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
class BoxWidget(Widget, ABC):
|
class BoxWidget(Widget, ABC):
|
||||||
"""
|
"""
|
||||||
Same as a basic widget, but represent a box
|
Same as a basic widget, but inside a box
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, scene: "Scene",
|
def __init__(self, scene: "Scene",
|
||||||
|
|
|
@ -18,6 +18,10 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class GameGridAlly(GameGrid):
|
class GameGridAlly(GameGrid):
|
||||||
|
"""
|
||||||
|
A game grid that represent the ally grid.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, scene: "Scene",
|
def __init__(self, scene: "Scene",
|
||||||
|
|
||||||
rows: int,
|
rows: int,
|
||||||
|
|
|
@ -13,6 +13,10 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class GameGridEnemy(GameGrid):
|
class GameGridEnemy(GameGrid):
|
||||||
|
"""
|
||||||
|
A game grid that represent the enemy grid.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, scene: "Scene",
|
def __init__(self, scene: "Scene",
|
||||||
|
|
||||||
rows: int,
|
rows: int,
|
||||||
|
|
|
@ -13,6 +13,10 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class GameGrid(BoxWidget):
|
class GameGrid(BoxWidget):
|
||||||
|
"""
|
||||||
|
A widget that represent a game grid.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, scene: "Scene",
|
def __init__(self, scene: "Scene",
|
||||||
|
|
||||||
rows: int,
|
rows: int,
|
||||||
|
|
|
@ -9,6 +9,10 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class Client(StoppableThread):
|
class Client(StoppableThread):
|
||||||
|
"""
|
||||||
|
The thread executed on the person who join a room.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, window: "Window", username: str, ip_address: str, port: int = 52321, **kw):
|
def __init__(self, window: "Window", username: str, ip_address: str, port: int = 52321, **kw):
|
||||||
super().__init__(**kw)
|
super().__init__(**kw)
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,10 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class Host(StoppableThread):
|
class Host(StoppableThread):
|
||||||
|
"""
|
||||||
|
The thread executed on the person who create a room.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, window: "Window", username: str, port: int = 52321, **kw):
|
def __init__(self, window: "Window", username: str, port: int = 52321, **kw):
|
||||||
super().__init__(**kw)
|
super().__init__(**kw)
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,13 @@ from source.utils.thread import in_pyglet_context
|
||||||
|
|
||||||
|
|
||||||
def game_network(thread: "StoppableThread", window: "Window", connection: socket.socket):
|
def game_network(thread: "StoppableThread", window: "Window", connection: socket.socket):
|
||||||
|
"""
|
||||||
|
Run the networking to make the game work and react with the other player
|
||||||
|
:param thread: the thread where this function is called.
|
||||||
|
:param window: the window of the game
|
||||||
|
:param connection: the connection with the other player
|
||||||
|
"""
|
||||||
|
|
||||||
game_scene = in_pyglet_context(window.set_scene, scene.Game, connection=connection)
|
game_scene = in_pyglet_context(window.set_scene, scene.Game, connection=connection)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import socket
|
|
||||||
|
|
||||||
from source.network.packet.abc import Packet
|
from source.network.packet.abc import Packet
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PacketBoatPlaced(Packet):
|
class PacketBoatPlaced(Packet):
|
||||||
|
"""
|
||||||
|
A packet that signal that all the boat of the player have been placed
|
||||||
|
"""
|
||||||
|
|
||||||
packet_size: int = 0
|
packet_size: int = 0
|
||||||
|
|
||||||
def to_bytes(self):
|
def to_bytes(self):
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import socket
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
from source.network.packet.abc import Packet
|
from source.network.packet.abc import Packet
|
||||||
|
@ -7,6 +6,10 @@ from source.type import Point2D
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PacketBombPlaced(Packet):
|
class PacketBombPlaced(Packet):
|
||||||
|
"""
|
||||||
|
A packet that signal that a bomb have been placed on the board
|
||||||
|
"""
|
||||||
|
|
||||||
position: Point2D = field()
|
position: Point2D = field()
|
||||||
|
|
||||||
packet_size: int = 2
|
packet_size: int = 2
|
||||||
|
|
|
@ -8,6 +8,10 @@ from source.type import Point2D
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PacketBombState(Packet):
|
class PacketBombState(Packet):
|
||||||
|
"""
|
||||||
|
A packet that signal how a bomb exploded on the board
|
||||||
|
"""
|
||||||
|
|
||||||
position: Point2D = field()
|
position: Point2D = field()
|
||||||
bomb_state: BombState = field()
|
bomb_state: BombState = field()
|
||||||
|
|
||||||
|
@ -19,7 +23,7 @@ class PacketBombState(Packet):
|
||||||
return (
|
return (
|
||||||
x.to_bytes(1, "big") +
|
x.to_bytes(1, "big") +
|
||||||
y.to_bytes(1, "big") +
|
y.to_bytes(1, "big") +
|
||||||
self.bomb_state.value.to_bytes()
|
self.bomb_state.value.to_bytes(1, "big")
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -29,5 +33,5 @@ class PacketBombState(Packet):
|
||||||
int.from_bytes(data[0:1], "big"),
|
int.from_bytes(data[0:1], "big"),
|
||||||
int.from_bytes(data[1:2], "big"),
|
int.from_bytes(data[1:2], "big"),
|
||||||
),
|
),
|
||||||
bomb_state=BombState.from_bytes(data[2:3])
|
bomb_state=BombState(int.from_bytes(data[2:3], "big"))
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import socket
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
from source.network.packet.abc import Packet
|
from source.network.packet.abc import Packet
|
||||||
|
@ -6,6 +5,10 @@ from source.network.packet.abc import Packet
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PacketChat(Packet):
|
class PacketChat(Packet):
|
||||||
|
"""
|
||||||
|
A packet that represent a message from the chat
|
||||||
|
"""
|
||||||
|
|
||||||
message: str = field()
|
message: str = field()
|
||||||
|
|
||||||
packet_size: int = 256
|
packet_size: int = 256
|
||||||
|
|
|
@ -9,31 +9,54 @@ class Packet(ABC):
|
||||||
packet_id: int = 0
|
packet_id: int = 0
|
||||||
|
|
||||||
def __init_subclass__(cls, **kwargs):
|
def __init_subclass__(cls, **kwargs):
|
||||||
cls.packet_header = Packet.packet_id.to_bytes(1, "big")
|
cls.packet_header = Packet.packet_id.to_bytes(1, "big") # give a header to the newly created subclass
|
||||||
Packet.packet_id = Packet.packet_id + 1
|
Packet.packet_id = Packet.packet_id + 1 # increment by one the packet header for the next subclass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def to_bytes(self) -> bytes:
|
def to_bytes(self) -> bytes:
|
||||||
|
"""
|
||||||
|
Convert the packet into a bytes object. The size should be "packet_size" long.
|
||||||
|
:return: the packet encoded into a bytes.
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def from_bytes(cls, data: bytes) -> "Packet":
|
def from_bytes(cls, data: bytes) -> "Packet":
|
||||||
|
"""
|
||||||
|
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.
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def cls_from_header(cls, packet_header: bytes) -> Type["Packet"]:
|
def cls_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
|
||||||
|
:return: the class associated with this header
|
||||||
|
"""
|
||||||
return next(filter(
|
return next(filter(
|
||||||
lambda subcls: subcls.packet_header == packet_header,
|
lambda subcls: subcls.packet_header == packet_header,
|
||||||
cls.__subclasses__()
|
cls.__subclasses__()
|
||||||
))
|
))
|
||||||
|
|
||||||
def send_connection(self, connection: socket.socket) -> None:
|
def 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.packet_header)
|
connection.send(self.packet_header)
|
||||||
connection.send(self.to_bytes())
|
connection.send(self.to_bytes())
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_connection(cls, connection: socket.socket) -> Optional["Packet"]:
|
def from_connection(cls, connection: socket.socket) -> Optional["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.
|
||||||
|
"""
|
||||||
packet_header: Optional[bytes] = None
|
packet_header: Optional[bytes] = None
|
||||||
try: packet_header = connection.recv(1)
|
try: packet_header = connection.recv(1)
|
||||||
except socket.timeout: pass
|
except socket.timeout: pass
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
from typing import Union, Callable, Any
|
from typing import Union, Callable, Any
|
||||||
|
|
||||||
Point2D = tuple[int, int]
|
Point2D = tuple[int, int] # a 2D point
|
||||||
BBox = tuple[int, int, int, int]
|
BBox = tuple[int, int, int, int] # a boundary box
|
||||||
Percentage = float # a percentage, represented as a number between 0 and 1
|
Percentage = float # a percentage, represented as a number between 0 and 1
|
||||||
ColorRGB = tuple[int, int, int]
|
ColorRGB = tuple[int, int, int] # a RGB Color
|
||||||
ColorRGBA = tuple[int, int, int, int]
|
ColorRGBA = tuple[int, int, int, int] # a RGBA Color
|
||||||
|
|
||||||
DistanceFunction = Callable[[Any], int] # a function that return a distance
|
DistanceFunction = Callable[[Any], int] # a function that return a distance
|
||||||
# a distance, represented either by a whole number, a percentage or a function
|
Distance = Union[int, Percentage, DistanceFunction] # a distance, represented by a number, a percentage or a function
|
||||||
Distance = Union[Percentage, int, DistanceFunction]
|
|
||||||
|
|
|
@ -6,9 +6,9 @@ from source.type import Point2D
|
||||||
def copy_array_offset(src: np.array, dst: np.array, offset: Point2D) -> None:
|
def copy_array_offset(src: np.array, dst: np.array, offset: Point2D) -> None:
|
||||||
"""
|
"""
|
||||||
Copy a numpy array into another one with an offset
|
Copy a numpy array into another one with an offset
|
||||||
:source: source array
|
:param src: source array
|
||||||
:dst: destination array
|
:param dst: destination array
|
||||||
:offset: the offset where to copy the array
|
:param offset: the offset where to copy the array
|
||||||
"""
|
"""
|
||||||
column, row = offset
|
column, row = offset
|
||||||
width, height = src.shape
|
width, height = src.shape
|
||||||
|
|
|
@ -20,6 +20,15 @@ class StoppableThread(Thread):
|
||||||
|
|
||||||
|
|
||||||
def in_pyglet_context(func: Callable, *args, **kwargs) -> Any:
|
def in_pyglet_context(func: Callable, *args, **kwargs) -> Any:
|
||||||
|
"""
|
||||||
|
This function can be call in a thread. It will call the "func" in the pyglet event loop, avoiding
|
||||||
|
some operation that are not allowed outside of the pyglet context, and return its result
|
||||||
|
:param func: the function to call in the pyglet context
|
||||||
|
:param args: the args of the function
|
||||||
|
:param kwargs: the kwargs of the function
|
||||||
|
:return: the result of the function
|
||||||
|
"""
|
||||||
|
|
||||||
queue = Queue()
|
queue = Queue()
|
||||||
pyglet.clock.schedule_once(lambda dt: queue.put(func(*args, **kwargs)), 0)
|
pyglet.clock.schedule_once(lambda dt: queue.put(func(*args, **kwargs)), 0)
|
||||||
return queue.get()
|
return queue.get()
|
||||||
|
|
Loading…
Reference in a new issue