more comment in all modules (except GUI)

This commit is contained in:
Faraphel 2023-03-14 08:44:39 +01:00
parent 88e89209ac
commit f6f2e1ed52
32 changed files with 142 additions and 98 deletions

View file

@ -1,8 +1,5 @@
A faire :
1. Principal :
- Documenter (Docstring, README, ...)
- Documenter (Docstring, ...)
- mode d'emploi (video + pdf) expliquant le fonctionnement
2. Bonus :

View file

@ -6,13 +6,13 @@ from source.gui.scene import MainMenu
from source.gui.window import GameWindow
from source.path import path_font
from source.path import path_font, path_image
from source.gui.better_pyglet import Label
# Change la police par défaut utilisé pour le Century Gothic
pyglet.font.add_directory(path_font)
Label.default_kwargs["font_name"] = "Century Gothic" # NOQA: Label à un "default_kwargs" avec la metaclass
Label.default_kwargs["font_name"] = "Century Gothic"
# Créer une nouvelle fenêtre
window = GameWindow(
@ -21,7 +21,8 @@ window = GameWindow(
option_path=Path("./option.json")
)
try: window.set_icon(pyglet.image.load("./assets/image/icon/icon.png"))
# Change l'icône de cette fenêtre
try: window.set_icon(pyglet.image.load(path_image / "/icon/icon.png"))
except: pass # NOQA E722
window.set_minimum_size(720, 480)

View file

@ -1,5 +1,7 @@
from cx_Freeze import setup, Executable
from source.path import path_image, path_assets
setup(
name='Bataille Navale',
description='Bataille Navale',
@ -8,7 +10,7 @@ setup(
options={
"build_exe": {
"include_files": ["./assets"],
"include_files": [path_assets],
}
},
@ -17,7 +19,7 @@ setup(
executables=[
Executable(
"main.pyw",
icon="./assets/image/icon/icon.ico",
icon=path_image / "/icon/icon.ico",
base="win32gui",
target_name="Bataille Navale.exe",
shortcut_name="Bataille Navale",

View file

@ -3,35 +3,36 @@ from typing import Callable
class Listener:
"""
The Listener can be subclassed to allow the subclass to add, remove and call event easily.
Les classes héritant de Listener permettent d'ajouter, retirer et appeler facilement des événements.
"""
def __init__(self):
# dictionnaire des événements et de leurs fonctions associées
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
Ajoute une fonction à un événement
:param name: le nom de l'événement
:param callback: la fonction à appeler
"""
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
Retire une fonction d'un événement
:param name: le nom de l'événement
:param callback: la fonction à retirer
"""
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
Appelle les fonctions associées à un événement
:param name: le nom de l'événement
:param args: les arguments des fonctions
:param kwargs: les arguments à clé des fonctions
"""
# .copy() pour que si le listener supprime un de ses événements, la liste de la boucle de change pas de taille

View file

@ -3,5 +3,7 @@ from typing import Any
class Element(ABC):
default_kwargs: dict[str, Any]
def __init_subclass__(cls, **kwargs):
cls.default_kwargs: dict[str, Any] = {} # all subclasses will have their own "default_kwargs" dict
cls.default_kwargs = {} # all subclasses will have their own "default_kwargs" dict

View file

@ -16,7 +16,7 @@ if TYPE_CHECKING:
class Client(StoppableThread):
"""
The thread executed on the person who join a room.
Ce thread est utilisé pour la personne qui rejoint la salle.
"""
def __init__(self, window: "Window",
@ -39,9 +39,10 @@ class Client(StoppableThread):
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as connection:
try:
# Se connecte à l'opposant
connection.connect((self.ip_address, self.port))
except ConnectionRefusedError:
# Appelle l'événement lorsque la connexion échoue
# Appelle la fonction prévue lorsque la connexion échoue
if self.on_connexion_refused is not None:
in_pyglet_context(self.on_connexion_refused)
return
@ -91,13 +92,14 @@ class Client(StoppableThread):
with open(path_old_save, "r", encoding="utf-8") as file:
save_data = json.load(file)
# paramètres & jeu
# paramètres et jeu
settings = PacketSettings.from_connection(connection)
PacketUsername(username=self.username).send_data_connection(connection)
enemy_username = PacketUsername.from_connection(connection).username
if load_old_save:
# si les joueurs utilise une ancienne sauvegarde, créer la scène du jeu à partir des données
game_scene = in_pyglet_context(
self.window.set_scene,
scene.Game.from_json, # depuis le fichier json
@ -109,6 +111,7 @@ class Client(StoppableThread):
)
else:
# s'il n'y a pas de sauvegarde à utiliser, initialise une nouvelle scène
game_scene = in_pyglet_context(
self.window.set_scene,
scene.Game,
@ -124,6 +127,7 @@ class Client(StoppableThread):
my_turn=not settings.host_start
)
# commence la partie réseau du jeu
game_network(
thread=self,
connection=connection,

View file

@ -18,7 +18,7 @@ if TYPE_CHECKING:
class Host(StoppableThread):
"""
The thread executed on the person who create a room.
Le thread utilisé lorsqu'un joueur créer une salle
"""
def __init__(self, window: "Window", port: int, username: str, settings: "PacketSettings", **kw):
@ -29,6 +29,7 @@ class Host(StoppableThread):
self.settings = settings
self.port = port
# condition utilisée lorsque l'on attend la décision de charger une partie ou non
self.condition_load = Condition()
self.accept_load: bool = False
@ -54,7 +55,7 @@ class Host(StoppableThread):
path_old_save: Optional[Path] = None
for file in path_save.iterdir(): # cherche une ancienne sauvegarde correspondant à l'ip de l'adversaire
for file in path_save.iterdir(): # cherche une ancienne sauvegarde correspondant à l'IP de l'adversaire
if file.stem.startswith(ip_address):
path_old_save = file
break
@ -72,7 +73,8 @@ class Host(StoppableThread):
from source.gui.scene import GameLoad
in_pyglet_context(self.window.set_scene, GameLoad, path=path_old_save, thread_host=self)
with self.condition_load: self.condition_load.wait() # attend que l'utilisateur choisisse l'option
# attend que l'utilisateur choisisse l'option
with self.condition_load: self.condition_load.wait()
PacketLoadOldSave(value=self.accept_load).send_data_connection(connection)
@ -89,6 +91,7 @@ class Host(StoppableThread):
PacketUsername(username=self.username).send_data_connection(connection)
if self.accept_load:
# si une sauvegarde doit être chargée
game_scene = in_pyglet_context(
self.window.set_scene,
scene.Game.from_json, # depuis le fichier json
@ -100,6 +103,7 @@ class Host(StoppableThread):
)
else:
# s'il n'y a pas de sauvegarde à charger
game_scene = in_pyglet_context(
self.window.set_scene,
scene.Game,
@ -115,6 +119,7 @@ class Host(StoppableThread):
my_turn=self.settings.host_start
)
# commence la partie réseau du jeu
game_network(
thread=self,
connection=connection,

View file

@ -9,8 +9,15 @@ if TYPE_CHECKING:
def handle_error(window: "Window", exception: Exception):
"""
Fonction permettant d'afficher le bon message d'erreur du au réseau.
:param window: la fenêtre du jeu
:param exception: l'erreur qui s'est produite
"""
message: str = "Erreur :\n"
# récupère le message d'erreur selon le type de l'erreur
match type(exception):
case builtins.ConnectionResetError:
message += "Perte de connexion avec l'adversaire."

View file

@ -18,12 +18,13 @@ def game_network(
game_scene: "Game",
):
"""
Run the networking to make the game work and react with the other player
:param game_scene: the scene of the game
:param thread: the thread where this function is called.
:param connection: the connection with the other player
Partie réseau permettant au jeu de fonctionner et de réagir avec l'autre joueur
:param game_scene: la scène du jeu
:param thread: le thread dans lequel la fonction est appelé
:param connection: la connexion avec l'autre joueur
"""
# associe le type de packet avec la fonction correspondante
game_methods: dict[Type["Packet"], Callable] = {
packet.PacketChat: game_scene.network_on_chat,
packet.PacketBoatPlaced: game_scene.network_on_boat_placed,
@ -35,16 +36,19 @@ def game_network(
}
while True:
# récupère le type de packet reçu
data_type = Packet.type_from_connection(connection)
if data_type is None:
if thread.stopped: return # vérifie si le thread n'est pas censé s'arrêter
# s'il n'y a pas de donnée reçue, vérifie si le thread devrait s'arrêter, sinon ignore
if thread.stopped: return
continue
# récupère les données du packet
data = data_type.from_connection(connection)
in_pyglet_context(
game_methods[data_type], data # récupère la methode relié ce type de donnée
game_methods[data_type], data # récupère la methode relié à ce type de donnée
) # Appelle la méthode.
if thread.stopped: return # vérifie si le thread n'est pas censé s'arrêter

View file

@ -6,5 +6,5 @@ from source.network.packet.abc import SignalPacket
@dataclass
class PacketAskSave(SignalPacket):
"""
A packet that is sent when the player wish to save the game.
Un packet envoyé quand le joueur souhaite sauvegarder
"""

View file

@ -6,5 +6,5 @@ from source.network.packet.abc import SignalPacket
@dataclass
class PacketBoatPlaced(SignalPacket):
"""
A packet that signal that all the boat of the player have been placed
Un packet signalant que tous les bateaux du joueur ont été placé
"""

View file

@ -9,6 +9,9 @@ from source.network.packet.abc import Packet
@dataclass
class PacketBoatsData(Packet):
"""
Un packet contenant les données de la position de tous les bateaux
"""
boats: np.array = field()

View file

@ -9,7 +9,7 @@ from source.type import Point2D
@dataclass
class PacketBombPlaced(SimplePacket):
"""
A packet that signal that a bomb have been placed on the board
Un packet qui signale qu'une bombe à été placé sur la grille
"""
position: Point2D = field()

View file

@ -9,7 +9,7 @@ from source.type import Point2D
@dataclass
class PacketBombState(SimplePacket):
"""
A packet that signal how a bomb exploded on the board
Un packet qui signale qu'une bombe à explosé sur la grille
"""
position: Point2D = field()

View file

@ -6,7 +6,7 @@ from source.network.packet.abc import VariableLengthPacket
@dataclass
class PacketChat(VariableLengthPacket):
"""
A packet that represent a message from the chat
Un packet qui représente un message du chat
"""
message: str = field()

View file

@ -7,7 +7,7 @@ from source.network.packet.abc import SimplePacket
@dataclass
class PacketHaveSaveBeenFound(SimplePacket):
"""
A packet that is sent when the player accept or refuse a requested save.
Un packet indiquant si le joueur à trouvé un ancien fichier de sauvegarde avec l'opposant
"""
value: bool = field() # True si requête accepter, sinon False

View file

@ -7,7 +7,7 @@ from source.network.packet.abc import SimplePacket
@dataclass
class PacketLoadOldSave(SimplePacket):
"""
A packet that is sent when the player accept or refuse a requested save.
Un packet qui est envoyé lorsque l'hôte accepte ou refuse de charger une ancienne sauvegarde
"""
value: bool = field() # True si requête accepter, sinon False

View file

@ -6,5 +6,5 @@ from source.network.packet.abc import SignalPacket
@dataclass
class PacketQuit(SignalPacket):
"""
A packet that is sent when the player wish to quit a game.
Un packet envoyé lorsque le joueur souhaite quitter la partie
"""

View file

@ -7,7 +7,7 @@ from source.network.packet.abc import SimplePacket
@dataclass
class PacketResponseSave(SimplePacket):
"""
A packet that is sent when the player accept or refuse a requested save.
Un packet qui est envoyé lorsque le joueur accepte ou refuse une demande de sauvegarde
"""
value: bool = field() # True si requête accepter, sinon False

View file

@ -7,6 +7,10 @@ from source.network.packet.abc import Packet
@dataclass
class PacketSettings(Packet):
"""
Un packet contenant tous les paramètres de la partie
"""
grid_width: int = field()
grid_height: int = field()
host_start: bool = field()

View file

@ -5,6 +5,10 @@ from source.network.packet.abc import VariableLengthPacket
@dataclass
class PacketUsername(VariableLengthPacket):
"""
Un packet contenant le nom d'utilisateur de l'opposant
"""
username: str = field()
@property

View file

@ -9,10 +9,8 @@ T = TypeVar("T", bound="Packet")
class Packet(ABC):
"""
A packet that can be sent on a socket.
Multiple subtype of packet can be sent and received in an easier way.
The to_bytes and from_connection method need to be defined.
Un packet pouvant être envoyé dans un socket.
Permet aux sous-classes de ce packet d'être envoyé et reçu d'une manière beaucoup plus simple.
"""
packet_types: set[T] = set()

View file

@ -10,8 +10,8 @@ T = TypeVar("T", bound="SignalPacket")
class SignalPacket(Packet, ABC):
"""
A packet that has for only usage to send a signal thanks to the type of the class.
It does not hold any other data.
Un packet ne contenant aucune donnée.
Permet de réagir à des événements seulement avec le type de la classe.
"""
def to_bytes(self) -> bytes:

View file

@ -11,8 +11,8 @@ T = TypeVar("T", bound="SimplePacket")
class SimplePacket(Packet, ABC):
"""
A packet with a simple packet format.
Only the from_bytes and to_bytes method need to be implemented.
Un packet avec un format plus simple.
Se base sur le "packet_format" pour envoyé et reservoir les données.
"""
packet_format: str = ">"
@ -21,9 +21,9 @@ class SimplePacket(Packet, ABC):
@abstractmethod
def from_bytes(cls, data: bytes) -> T:
"""
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.
Convertie un objet bytes en un SimplePacket
:param data: les données à charger, doit être "packet_size" de long
:return: un SimplePacket correspondant à ces données
"""
pass

View file

@ -11,8 +11,7 @@ T = TypeVar("T", bound="VariableLengthPacket")
class VariableLengthPacket(Packet, ABC):
"""
A Packet that represent a single value that can be encoded with a variable length.
The property "data" and the method "from_bytes" need to be defined.
Un packet représentant une seule valeur avec une longueur variable qui peut être encodé, comme une chaîne.
"""
packet_format: str = ">I"
@ -20,6 +19,11 @@ class VariableLengthPacket(Packet, ABC):
@property
@abstractmethod
def data(self) -> bytes:
"""
Donnée à envoyer sur le réseau
:return: la donnée sous la forme d'un objet bytes
"""
pass
def to_bytes(self) -> bytes:

View file

@ -9,6 +9,10 @@ if TYPE_CHECKING:
class Option:
"""
Cette classe permet de modifier, sauvegarder et charger les options.
"""
def __init__(self, window: "GameWindow",
volume_ambient: float = 0.1,
volume_fx: float = 0.1,
@ -24,7 +28,7 @@ class Option:
self.set_fps_limit(fps_limit)
self.set_vsync(vsync)
# propriété
# propriétés
@staticmethod
def get_volume_ambient() -> float: return media.SoundAmbient.get_volume()

View file

@ -1,10 +1,10 @@
from typing import Union, Callable
Point2D = tuple[int, int] # a 2D point
BBox = tuple[int, int, int, int] # a boundary box
ColorRGB = tuple[int, int, int] # a RGB Color
ColorRGBA = tuple[int, int, int, int] # a RGBA Color
Point2D = tuple[int, int] # un point 2D
BBox = tuple[int, int, int, int] # une boîte de collision
ColorRGB = tuple[int, int, int] # une couleur RGB
ColorRGBA = tuple[int, int, int, int] # une couleur RGBA
DistanceFunc = Callable[["BoxWidget"], int] # a function that return a position / distance
Distance = Union[int, DistanceFunc] # a position / distance, represented by a number or a function
DistanceFunc = Callable[["BoxWidget"], int] # une fonction renvoyant une position / distance
Distance = Union[int, DistanceFunc] # une position / distance sous la forme d'un nombre ou d'une fonction

View file

@ -3,10 +3,10 @@ from source.type import Point2D, BBox
def in_bbox(point: Point2D, bbox: BBox) -> bool:
"""
Return true if a point is inside a bounding box
:param point: the point to check
:param bbox: the bbox where to check the point
:return: True if the point is inside the bbox, False otherwise
Indique si un point est dans un rectangle
:param point: le point à vérifier
:param bbox: la bbox à vérifier
:return: True si le point est dans la bbox, sinon Faux
"""
point_x, point_y = point

View file

@ -3,17 +3,15 @@ from typing import Callable, Any
def dict_filter(filter_func: Callable[[Any, Any], bool], dictionary: dict[Any, Any]) -> dict[Any, Any]:
"""
Filter a dict object with the filter function given.
:param filter_func: the function to filter with
:param dictionary: the dictionary to filter
:return: the filtered dictionary
Filtre les objets d'un dictionnaire avec la fonction de filtre donnée.
:param filter_func: La fonction utilisée pour le filtre. Reçois l'argument clé et valeur
:param dictionary: Le dictionnaire à filtrer
:return: Le dictionnaire filtrer
Example :
Exemple :
filter_func = lambda key, value: key.startswith("valeur")
dictionary = {"valeur1": 1, "valeur2": 2, "clé1": None}
result = {"valeur1": 1, "valeur2": 2}
-> {"valeur1": 1, "valeur2": 2}
"""
return {
@ -26,16 +24,15 @@ def dict_filter(filter_func: Callable[[Any, Any], bool], dictionary: dict[Any, A
def dict_filter_prefix(prefix: str, dictionary: dict[str, Any]) -> dict[str, Any]:
"""
Take only the keys that start with the prefix, and remove this prefix from the keys.
:param prefix: the prefix to use
:param dictionary: the dictionary to filter
:return: the dictionary with the prefix
Ne garde que les clés qui commencent avec ce préfixe dans le dictionnaire et retire leur préfixe.
:param prefix: le préfixe à utiliser
:param dictionary: le dictionnaire à filtrer
:return: le dictionnaire avec le préfixe
Example:
Exemple :
prefix = "button"
dictionary = {"button1": 1, "button2": 2, "label1": None}
result = {"1": 1, "2": 2}
-> {"1": 1, "2": 2}
"""
return {
@ -48,10 +45,10 @@ def dict_filter_prefix(prefix: str, dictionary: dict[str, Any]) -> dict[str, Any
def dict_add_prefix(prefix: str, dictionary: dict[str, Any]) -> dict[str, Any]:
"""
Add a prefix to every key of the dictionary
:param prefix: the prefix to add
:param dictionary: the dictionary to modify
:return: the dictionary with the prefix at the start of the keys
Ajoute un préfixe à toute les clés d'un dictionnaire
:param prefix: le préfixe à ajouter
:param dictionary: le dictionnaire à modifier
:return: le dictionnaire avec le préfixe à chaque clé
"""
return {

View file

@ -5,10 +5,10 @@ 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
:param src: source array
:param dst: destination array
:param offset: the offset where to copy the array
Copie une matrice dans une autre matrice avec un décalage.
:param src: la matrice source
:param dst: la matrice de destination
:param offset: le décalage avec lequel copier la matrice
"""
column, row = offset
width, height = src.shape

View file

@ -2,5 +2,10 @@ from datetime import datetime
from pathlib import Path
def path_ctime_str(path: Path):
def path_ctime_str(path: Path) -> str:
"""
Un raccourci permettant d'obtenir la représentation de la date de création d'un fichier
:param path: le chemin du fichier
:return: la date correspondante sous la forme d'un texte
"""
return datetime.fromtimestamp(path.lstat().st_ctime).strftime('%d/%m/%Y %H:%M:%S')

View file

@ -7,8 +7,9 @@ import pyglet
class StoppableThread(Thread):
"""
A thread that can be stopped.
The run method need to check for the "self._stop" variable and return manually if it is true.
Un thread pouvant être arrêté.
La méthode "run" doit souvent vérifier la variable self.stopped et faire un return manuellement si cette variable
est vrai.
"""
def __init__(self, *args, **kwargs):
@ -16,17 +17,18 @@ class StoppableThread(Thread):
self.stopped = False
def stop(self) -> None:
# indique que le thread devrait s'arrêter dès que possible
self.stopped = True
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
Cette fonction doit être appelée dans un thread. Elle appellera une fonction dans la boucle d'événement de pyglet,
ce qui permet d'éviter certaines opérations illégales en dehors de ce contexte et renvoie le résultat.
:param func: la fonction à appeler
:param args: les arguments de la fonction
:param kwargs: les arguments à clé de la fonction
:return: le résultat de la fonction
"""
queue = Queue()