diff --git a/NOTE.md b/NOTE.md index 8a581fc..05e7ced 100644 --- a/NOTE.md +++ b/NOTE.md @@ -3,7 +3,7 @@ A faire : 1. Principal : - Sauvegarde -- Historique / Replay +- Historique - Documenter 2. Visuel : @@ -12,6 +12,7 @@ A faire : - Changer les images, rajouter les fonds, ... 3. Hypothétique : +- Vrai musique - Voir si les event listener intégré à pyglet sont plus pratiques que l'event propagation (?) - Faire une scène incluant par défaut les boutons "Retour" (?) diff --git a/source/gui/scene/GameLoad.py b/source/gui/scene/GameLoad.py index 3e5a786..328746d 100644 --- a/source/gui/scene/GameLoad.py +++ b/source/gui/scene/GameLoad.py @@ -2,16 +2,18 @@ from typing import TYPE_CHECKING from source.gui import widget, texture from source.gui.scene.abc import Scene - +from source.network import Host if TYPE_CHECKING: from source.gui.window import Window class GameLoad(Scene): - def __init__(self, window: "Window", **kwargs): + def __init__(self, window: "Window", thread_host: Host, **kwargs): super().__init__(window, **kwargs) + self.thread_host = thread_host # thread de l'hôte + self.label = self.add_widget( widget.Text, @@ -35,6 +37,8 @@ class GameLoad(Scene): style=texture.Button.Style1 ) + self.refuse.add_listener("on_click_release", lambda *_: self.response(value=False)) + self.accept = self.add_widget( widget.Button, @@ -45,3 +49,11 @@ class GameLoad(Scene): style=texture.Button.Style1 ) + self.accept.add_listener("on_click_release", lambda *_: self.response(value=True)) + + def response(self, value: bool): + self.thread_host.accept_load = value + self.window.remove_scene(self) + + with self.thread_host.condition_load: + self.thread_host.condition_load.notify_all() diff --git a/source/gui/scene/GameWaitLoad.py b/source/gui/scene/GameWaitLoad.py new file mode 100644 index 0000000..49f62e8 --- /dev/null +++ b/source/gui/scene/GameWaitLoad.py @@ -0,0 +1,26 @@ +from typing import TYPE_CHECKING + +from source.gui import widget, texture +from source.gui.scene.abc import Scene +from source.network import Host + +if TYPE_CHECKING: + from source.gui.window import Window + + +class GameWaitLoad(Scene): + def __init__(self, window: "Window", **kwargs): + super().__init__(window, **kwargs) + + self.label = self.add_widget( + widget.Text, + + x=0.5, y=0.5, width=1.0, + + anchor_x="center", + + text="Une ancienne sauvegarde à été trouvé.\nL'hôte décide de son utilisation...", + align="center", + multiline=True, + font_size=28, + ) diff --git a/source/gui/scene/__init__.py b/source/gui/scene/__init__.py index 3a350b0..b679a3b 100644 --- a/source/gui/scene/__init__.py +++ b/source/gui/scene/__init__.py @@ -4,6 +4,7 @@ from .GameQuit import GameQuit from .GameError import GameError from .GameSave import GameSave from .GameLoad import GameLoad +from .GameWaitLoad import GameWaitLoad from .Settings import Settings from .RoomHost import RoomHost from .RoomJoin import RoomJoin diff --git a/source/network/Client.py b/source/network/Client.py index 2f2cce8..582cdd5 100644 --- a/source/network/Client.py +++ b/source/network/Client.py @@ -1,9 +1,11 @@ import socket -from typing import TYPE_CHECKING, Any +from pathlib import Path +from typing import TYPE_CHECKING, Any, Optional +from source import path_save from source.gui import scene from source.network import game_network -from source.network.packet import PacketUsername, PacketSettings +from source.network.packet import PacketUsername, PacketSettings, PacketHaveSaveBeenFound, PacketLoadOldSave from source.utils import StoppableThread from source.utils.thread import in_pyglet_context @@ -33,7 +35,46 @@ class Client(StoppableThread): print(f"[Client] Connecté avec {connection}") - ... + # sauvegarde + + # attend que l'hôte indique s'il a trouvé une ancienne sauvegarde + packet_save_found = PacketHaveSaveBeenFound.from_connection(connection) + + if packet_save_found.value: + # si l'hôte a trouvé une ancienne sauvegarde, vérifier de notre côté également. + + path_old_save: Optional[Path] = None + ip_address, _ = connection.getpeername() + + for file in path_save.iterdir(): + if file.stem == ip_address: + path_old_save = file + break + + # envoie à l'hôte si l'on possède également la sauvegarde + PacketHaveSaveBeenFound(value=path_old_save is not None).send_data_connection(connection) + + if path_old_save: + # si l'on possède la sauvegarde, attend que l'hôte confirme son utilisation + + from source.gui.scene import GameWaitLoad + in_pyglet_context(self.window.set_scene, GameWaitLoad) + + while True: + # attend la décision de l'hôte + try: + load_old_save = PacketLoadOldSave.from_connection(connection) + break + except socket.timeout: + if self.stopped: return + + print("accept load", load_old_save) + + if load_old_save.value: + ... + # TODO: Charger nos données + + # paramètres & jeu settings: Any = PacketSettings.from_connection(connection) PacketUsername(username=self.username).send_data_connection(connection) diff --git a/source/network/Host.py b/source/network/Host.py index 1994b12..05f29c0 100644 --- a/source/network/Host.py +++ b/source/network/Host.py @@ -1,12 +1,14 @@ import socket -from typing import TYPE_CHECKING +from pathlib import Path +from typing import TYPE_CHECKING, Optional +from threading import Condition from source import path_save from source.gui import scene from source.network import game_network from source.utils import StoppableThread from source.utils.thread import in_pyglet_context -from source.network.packet import PacketUsername +from source.network.packet import PacketUsername, PacketLoadOldSave, PacketHaveSaveBeenFound if TYPE_CHECKING: from source.gui.window import Window @@ -26,6 +28,9 @@ class Host(StoppableThread): self.settings = settings self.port = port + self.condition_load = Condition() + self.accept_load: Optional[bool] = None + def run(self) -> None: print("[Serveur] Thread démarré") @@ -45,9 +50,35 @@ class Host(StoppableThread): print(f"[Serveur] Connecté avec {ip_address}") - # check pour ancienne sauvegarde contre ce joueur + # ancienne sauvegarde - ... + path_old_save: Optional[Path] = None + + for file in path_save.iterdir(): # cherche une ancienne sauvegarde correspondant à l'ip de l'adversaire + if file.stem == ip_address: + path_old_save = file + break + + # envoie à l'adversaire si une ancienne sauvegarde a été trouvée + PacketHaveSaveBeenFound(value=path_old_save is not None).send_data_connection(connection) + + if path_old_save is not None: + # si une ancienne sauvegarde a été trouvée, attend que l'adversaire confirme avoir également la save + packet_save_found = PacketHaveSaveBeenFound.from_connection(connection) + + # si l'adversaire à également la sauvegarde, demande à l'hôte de confirmer l'utilisation de la save + if packet_save_found.value: + + from source.gui.scene import GameLoad + in_pyglet_context(self.window.set_scene, GameLoad, thread_host=self) + + with self.condition_load: self.condition_load.wait() # attend que l'utilisateur choisisse l'option + + PacketLoadOldSave(value=self.accept_load).send_data_connection(connection) + + if self.accept_load: + ... + # TODO: Charger nos données # paramètres & jeu diff --git a/source/network/packet/PacketHaveSaveBeenFound.py b/source/network/packet/PacketHaveSaveBeenFound.py new file mode 100644 index 0000000..68c9d8f --- /dev/null +++ b/source/network/packet/PacketHaveSaveBeenFound.py @@ -0,0 +1,24 @@ +import struct +from dataclasses import field, dataclass + +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. + """ + + value: bool = field() # True si requête accepter, sinon False + + packet_format = ">?" + + def to_bytes(self) -> bytes: + return struct.pack(self.packet_format, self.value) + + @classmethod + def from_bytes(cls, data: bytes) -> "PacketHaveSaveBeenFound": + value, *_ = struct.unpack(cls.packet_format, data) + return cls(value=value) + diff --git a/source/network/packet/PacketLoadOldSave.py b/source/network/packet/PacketLoadOldSave.py new file mode 100644 index 0000000..c46601e --- /dev/null +++ b/source/network/packet/PacketLoadOldSave.py @@ -0,0 +1,24 @@ +import struct +from dataclasses import field, dataclass + +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. + """ + + value: bool = field() # True si requête accepter, sinon False + + packet_format = ">?" + + def to_bytes(self) -> bytes: + return struct.pack(self.packet_format, self.value) + + @classmethod + def from_bytes(cls, data: bytes) -> "PacketLoadOldSave": + value, *_ = struct.unpack(cls.packet_format, data) + return cls(value=value) + diff --git a/source/network/packet/__init__.py b/source/network/packet/__init__.py index 9a6251d..c9d7802 100644 --- a/source/network/packet/__init__.py +++ b/source/network/packet/__init__.py @@ -7,3 +7,5 @@ from .PacketResponseSave import PacketResponseSave from .PacketSettings import PacketSettings from .PacketUsername import PacketUsername from .PacketQuit import PacketQuit +from .PacketLoadOldSave import PacketLoadOldSave +from .PacketHaveSaveBeenFound import PacketHaveSaveBeenFound