added more documentation in some files

This commit is contained in:
Faraphel 2023-02-23 00:02:28 +01:00
parent dc4cb3b1a7
commit a2d37c37f7
28 changed files with 168 additions and 31 deletions

View file

@ -6,6 +6,7 @@ A faire :
- Police d'écriture
- Voir si les event listener intégré à pyglet sont plus pratique que l'event propagation
- Documenter
Bug :
- /
@ -14,8 +15,9 @@ Autre :
- Tester sur Linux
A expliquer :
- in_pyglet_context
- Packet
Bonus ultime :

View file

@ -8,6 +8,11 @@ from source.utils import copy_array_offset
class Board:
"""
Represent a board for the game.
Boat can be added and bomb can be placed.
"""
__slots__ = ("_columns", "_rows", "_boats", "_bombs")
def __init__(

View file

@ -4,6 +4,11 @@ from source.core.enums import Orientation
class Boat:
"""
Represent a boat.
It can be added to a board.
"""
__slots__ = ("orientation", "length")
def __init__(self, length: int, orientation: Orientation):

View file

@ -2,16 +2,13 @@ from enum import Enum
class BombState(Enum):
NOTHING = 0
TOUCHED = 1
SUNKEN = 2
WON = 3
"""
This class represent the state of a bomb after being place on the board.
"""
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:
return self.value.to_bytes(1, "big")
@classmethod
def from_bytes(cls, data: bytes):
return cls(int.from_bytes(data, "big"))
ERROR = 10 # the bomb could not be placed

View file

@ -2,6 +2,10 @@ from enum import Enum
class Orientation(Enum):
"""
Represent the orientation of a boat.
"""
HORIZONTAL = "H"
VERTICAL = "V"

View file

@ -2,16 +2,36 @@ from typing import Callable
class Listener:
"""
The Listener can be subclassed to allow the subclass to add, remove and call event easily.
"""
def __init__(self):
self._events_listener: dict[str, set[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()
self._events_listener[name].add(callback)
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)
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()):
listener(*args, **kwargs)

View file

@ -6,6 +6,11 @@ from source.gui.event import StopEvent
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
@abstractmethod
def childs(self):

View file

@ -1,2 +1,5 @@
class StopEvent(Exception):
"""
This error can be raised to prevent an event to propagate to further element.
"""
pass

View file

@ -6,6 +6,14 @@ import pyglet
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):
atlas = pyglet.image.atlas.TextureAtlas()

View file

@ -13,6 +13,10 @@ if TYPE_CHECKING:
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",
style: Type[Style],

View file

@ -11,6 +11,10 @@ if TYPE_CHECKING:
class Image(BoxWidget):
"""
An image widget with a texture.
"""
def __init__(self, scene: "Scene",
image: pyglet.image.AbstractImage,

View file

@ -14,6 +14,10 @@ if TYPE_CHECKING:
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",
style: Type[Style],

View file

@ -13,6 +13,11 @@ if TYPE_CHECKING:
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",
style: Type[Style],

View file

@ -11,7 +11,7 @@ if TYPE_CHECKING:
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",

View file

@ -18,6 +18,10 @@ if TYPE_CHECKING:
class GameGridAlly(GameGrid):
"""
A game grid that represent the ally grid.
"""
def __init__(self, scene: "Scene",
rows: int,

View file

@ -13,6 +13,10 @@ if TYPE_CHECKING:
class GameGridEnemy(GameGrid):
"""
A game grid that represent the enemy grid.
"""
def __init__(self, scene: "Scene",
rows: int,

View file

@ -13,6 +13,10 @@ if TYPE_CHECKING:
class GameGrid(BoxWidget):
"""
A widget that represent a game grid.
"""
def __init__(self, scene: "Scene",
rows: int,

View file

@ -9,6 +9,10 @@ if TYPE_CHECKING:
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):
super().__init__(**kw)

View file

@ -9,6 +9,10 @@ if TYPE_CHECKING:
class Host(StoppableThread):
"""
The thread executed on the person who create a room.
"""
def __init__(self, window: "Window", username: str, port: int = 52321, **kw):
super().__init__(**kw)

View file

@ -13,6 +13,13 @@ from source.utils.thread import in_pyglet_context
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)
while True:

View file

@ -1,11 +1,14 @@
from dataclasses import dataclass
import socket
from source.network.packet.abc import Packet
@dataclass
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):

View file

@ -1,4 +1,3 @@
import socket
from dataclasses import dataclass, field
from source.network.packet.abc import Packet
@ -7,6 +6,10 @@ from source.type import Point2D
@dataclass
class PacketBombPlaced(Packet):
"""
A packet that signal that a bomb have been placed on the board
"""
position: Point2D = field()
packet_size: int = 2

View file

@ -8,6 +8,10 @@ from source.type import Point2D
@dataclass
class PacketBombState(Packet):
"""
A packet that signal how a bomb exploded on the board
"""
position: Point2D = field()
bomb_state: BombState = field()
@ -19,7 +23,7 @@ class PacketBombState(Packet):
return (
x.to_bytes(1, "big") +
y.to_bytes(1, "big") +
self.bomb_state.value.to_bytes()
self.bomb_state.value.to_bytes(1, "big")
)
@classmethod
@ -29,5 +33,5 @@ class PacketBombState(Packet):
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(int.from_bytes(data[2:3], "big"))
)

View file

@ -1,4 +1,3 @@
import socket
from dataclasses import dataclass, field
from source.network.packet.abc import Packet
@ -6,6 +5,10 @@ from source.network.packet.abc import Packet
@dataclass
class PacketChat(Packet):
"""
A packet that represent a message from the chat
"""
message: str = field()
packet_size: int = 256

View file

@ -9,31 +9,54 @@ class Packet(ABC):
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
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 # increment by one the packet header for the next subclass
@abstractmethod
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
@classmethod
@abstractmethod
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
@classmethod
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(
lambda subcls: subcls.packet_header == packet_header,
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.to_bytes())
@classmethod
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
try: packet_header = connection.recv(1)
except socket.timeout: pass

View file

@ -1,11 +1,10 @@
from typing import Union, Callable, Any
Point2D = tuple[int, int]
BBox = tuple[int, int, int, int]
Point2D = tuple[int, int] # a 2D point
BBox = tuple[int, int, int, int] # a boundary box
Percentage = float # a percentage, represented as a number between 0 and 1
ColorRGB = tuple[int, int, int]
ColorRGBA = tuple[int, int, int, int]
ColorRGB = tuple[int, int, int] # a RGB Color
ColorRGBA = tuple[int, int, int, int] # a RGBA Color
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[Percentage, int, DistanceFunction]
Distance = Union[int, Percentage, DistanceFunction] # a distance, represented by a number, a percentage or a function

View file

@ -6,9 +6,9 @@ from source.type import Point2D
def copy_array_offset(src: np.array, dst: np.array, offset: Point2D) -> None:
"""
Copy a numpy array into another one with an offset
:source: source array
:dst: destination array
:offset: the offset where to copy the array
:param src: source array
:param dst: destination array
:param offset: the offset where to copy the array
"""
column, row = offset
width, height = src.shape

View file

@ -20,6 +20,15 @@ class StoppableThread(Thread):
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()
pyglet.clock.schedule_once(lambda dt: queue.put(func(*args, **kwargs)), 0)
return queue.get()