diff --git a/.gitignore b/.gitignore index 05b9bf6..410ed88 100644 --- a/.gitignore +++ b/.gitignore @@ -137,3 +137,5 @@ dmypy.json /.idea/inspectionProfiles/Project_Default.xml /.idea/Projet_S6.iml /.idea/vcs.xml + +/.save/ diff --git a/.save/127.0.0.1.bn-save b/.save/127.0.0.1.bn-save deleted file mode 100644 index ecf1905..0000000 --- a/.save/127.0.0.1.bn-save +++ /dev/null @@ -1,227 +0,0 @@ -{ - "grid_ally": { - "columns": 8, - "rows": 8, - "boats": [ - [ - { - "length": 5, - "orientation": "H" - }, - [ - 0, - 0 - ] - ], - [ - { - "length": 4, - "orientation": "V" - }, - [ - 1, - 2 - ] - ], - [ - { - "length": 4, - "orientation": "H" - }, - [ - 3, - 4 - ] - ], - [ - { - "length": 3, - "orientation": "V" - }, - [ - 0, - 5 - ] - ], - [ - { - "length": 2, - "orientation": "H" - }, - [ - 2, - 6 - ] - ] - ], - "bombs": [ - [ - false, - false, - true, - true, - true, - true, - true, - true - ], - [ - true, - true, - false, - true, - true, - true, - true, - true - ], - [ - true, - false, - true, - false, - true, - true, - true, - true - ], - [ - true, - true, - true, - true, - true, - true, - true, - true - ], - [ - true, - true, - true, - true, - true, - true, - true, - true - ], - [ - true, - true, - false, - true, - true, - false, - true, - true - ], - [ - true, - false, - true, - true, - true, - true, - true, - true - ], - [ - true, - true, - true, - true, - true, - true, - true, - true - ] - ] - }, - "grid_enemy": { - "columns": 8, - "rows": 8, - "boats": [], - "bombs": [ - [ - true, - false, - true, - true, - true, - false, - true, - true - ], - [ - true, - true, - true, - true, - true, - true, - true, - true - ], - [ - false, - false, - false, - false, - true, - true, - false, - true - ], - [ - true, - true, - true, - true, - true, - true, - true, - true - ], - [ - true, - true, - false, - true, - true, - true, - true, - true - ], - [ - true, - true, - true, - true, - true, - true, - true, - true - ], - [ - true, - true, - true, - true, - true, - true, - true, - true - ], - [ - true, - true, - true, - true, - true, - true, - true, - true - ] - ] - } -} \ No newline at end of file diff --git a/NOTE.md b/NOTE.md index 05e7ced..f2f268a 100644 --- a/NOTE.md +++ b/NOTE.md @@ -12,13 +12,13 @@ A faire : - Changer les images, rajouter les fonds, ... 3. Hypothétique : -- Vrai musique +- Vraie 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" (?) Bug : -- / +- Dans de rare cas (souvent en fermant brutalement la fenêtre) le processus ne s'arrête pas Autre : diff --git a/assets/image/grid/bomb/animation/1.png b/assets/image/grid/boat/animation/1.png similarity index 100% rename from assets/image/grid/bomb/animation/1.png rename to assets/image/grid/boat/animation/1.png diff --git a/assets/image/grid/bomb/animation/10.png b/assets/image/grid/boat/animation/10.png similarity index 100% rename from assets/image/grid/bomb/animation/10.png rename to assets/image/grid/boat/animation/10.png diff --git a/assets/image/grid/bomb/animation/11.png b/assets/image/grid/boat/animation/11.png similarity index 100% rename from assets/image/grid/bomb/animation/11.png rename to assets/image/grid/boat/animation/11.png diff --git a/assets/image/grid/bomb/animation/12.png b/assets/image/grid/boat/animation/12.png similarity index 100% rename from assets/image/grid/bomb/animation/12.png rename to assets/image/grid/boat/animation/12.png diff --git a/assets/image/grid/bomb/animation/13.png b/assets/image/grid/boat/animation/13.png similarity index 100% rename from assets/image/grid/bomb/animation/13.png rename to assets/image/grid/boat/animation/13.png diff --git a/assets/image/grid/bomb/animation/14.png b/assets/image/grid/boat/animation/14.png similarity index 100% rename from assets/image/grid/bomb/animation/14.png rename to assets/image/grid/boat/animation/14.png diff --git a/assets/image/grid/bomb/animation/15.png b/assets/image/grid/boat/animation/15.png similarity index 100% rename from assets/image/grid/bomb/animation/15.png rename to assets/image/grid/boat/animation/15.png diff --git a/assets/image/grid/bomb/animation/16.png b/assets/image/grid/boat/animation/16.png similarity index 100% rename from assets/image/grid/bomb/animation/16.png rename to assets/image/grid/boat/animation/16.png diff --git a/assets/image/grid/bomb/animation/17.png b/assets/image/grid/boat/animation/17.png similarity index 100% rename from assets/image/grid/bomb/animation/17.png rename to assets/image/grid/boat/animation/17.png diff --git a/assets/image/grid/bomb/animation/18.png b/assets/image/grid/boat/animation/18.png similarity index 100% rename from assets/image/grid/bomb/animation/18.png rename to assets/image/grid/boat/animation/18.png diff --git a/assets/image/grid/bomb/animation/19.png b/assets/image/grid/boat/animation/19.png similarity index 100% rename from assets/image/grid/bomb/animation/19.png rename to assets/image/grid/boat/animation/19.png diff --git a/assets/image/grid/bomb/animation/2.png b/assets/image/grid/boat/animation/2.png similarity index 100% rename from assets/image/grid/bomb/animation/2.png rename to assets/image/grid/boat/animation/2.png diff --git a/assets/image/grid/bomb/animation/20.png b/assets/image/grid/boat/animation/20.png similarity index 100% rename from assets/image/grid/bomb/animation/20.png rename to assets/image/grid/boat/animation/20.png diff --git a/assets/image/grid/bomb/animation/21.png b/assets/image/grid/boat/animation/21.png similarity index 100% rename from assets/image/grid/bomb/animation/21.png rename to assets/image/grid/boat/animation/21.png diff --git a/assets/image/grid/bomb/animation/22.png b/assets/image/grid/boat/animation/22.png similarity index 100% rename from assets/image/grid/bomb/animation/22.png rename to assets/image/grid/boat/animation/22.png diff --git a/assets/image/grid/bomb/animation/23.png b/assets/image/grid/boat/animation/23.png similarity index 100% rename from assets/image/grid/bomb/animation/23.png rename to assets/image/grid/boat/animation/23.png diff --git a/assets/image/grid/bomb/animation/3.png b/assets/image/grid/boat/animation/3.png similarity index 100% rename from assets/image/grid/bomb/animation/3.png rename to assets/image/grid/boat/animation/3.png diff --git a/assets/image/grid/bomb/animation/4.png b/assets/image/grid/boat/animation/4.png similarity index 100% rename from assets/image/grid/bomb/animation/4.png rename to assets/image/grid/boat/animation/4.png diff --git a/assets/image/grid/bomb/animation/5.png b/assets/image/grid/boat/animation/5.png similarity index 100% rename from assets/image/grid/bomb/animation/5.png rename to assets/image/grid/boat/animation/5.png diff --git a/assets/image/grid/bomb/animation/6.png b/assets/image/grid/boat/animation/6.png similarity index 100% rename from assets/image/grid/bomb/animation/6.png rename to assets/image/grid/boat/animation/6.png diff --git a/assets/image/grid/bomb/animation/7.png b/assets/image/grid/boat/animation/7.png similarity index 100% rename from assets/image/grid/bomb/animation/7.png rename to assets/image/grid/boat/animation/7.png diff --git a/assets/image/grid/bomb/animation/8.png b/assets/image/grid/boat/animation/8.png similarity index 100% rename from assets/image/grid/bomb/animation/8.png rename to assets/image/grid/boat/animation/8.png diff --git a/assets/image/grid/bomb/animation/9.png b/assets/image/grid/boat/animation/9.png similarity index 100% rename from assets/image/grid/bomb/animation/9.png rename to assets/image/grid/boat/animation/9.png diff --git a/assets/image/grid/bomb/missed.png b/assets/image/grid/boat/missed.png similarity index 100% rename from assets/image/grid/bomb/missed.png rename to assets/image/grid/boat/missed.png diff --git a/assets/image/grid/bomb/touched.png b/assets/image/grid/boat/touched.png similarity index 100% rename from assets/image/grid/bomb/touched.png rename to assets/image/grid/boat/touched.png diff --git a/source/core/Board.py b/source/core/Board.py index bf13915..d4c621f 100644 --- a/source/core/Board.py +++ b/source/core/Board.py @@ -13,27 +13,24 @@ class Board: Boat can be added and bomb can be placed. """ - __slots__ = ("_columns", "_rows", "_boats", "_bombs") + __slots__ = ("width", "height", "boats", "bombs") def __init__( self, - rows: int, - columns: int = None, - boats: dict[Boat, Point2D] = None, - bombs: np.array = None - ) -> None: + width: int, height: int = None, - self._rows: int = rows - self._columns: int = rows if columns is None else columns + boats: np.array = None, + bombs: np.array = None) -> None: - # associate the boats to their position - self._boats: dict[Boat, Point2D] = {} if boats is None else boats + self.height: int = width + self.width: int = width if height is None else height - # position that have been shot by a bomb - self._bombs: np.array = np.ones((self._rows, self._columns), dtype=np.bool_) if bombs is None else bombs + # associate the boats and the bombs to array + self.boats: np.array = np.zeros((self.height, self.width), dtype=np.ushort) if boats is None else boats + self.bombs: np.array = np.ones((self.height, self.width), dtype=np.bool_) if bombs is None else bombs def __repr__(self) -> str: - return f"<{self.__class__.__name__} width={self._columns}, height={self._rows}>" + return f"<{self.__class__.__name__} width={self.width}, height={self.height}>" def __str__(self) -> str: return str(self.get_matrice()) @@ -46,33 +43,31 @@ class Board: :raise: InvalidBoatPosition if the boat position is not valid """ - # get the sum of the boat - boat_mat: np.array = boat.get_matrice() - boat_mat_sum: int = boat_mat.sum() - # get the old board matrice sum - board_mat: np.array = self.get_matrice() - board_mat_sum_old: int = board_mat.sum() + board_matrice = self.boats.copy() + board_matrice_sum_old: int = board_matrice.sum() + board_matrice_max = np.max(board_matrice) + + # get the sum of the boat + boat_matrice: np.array = boat.get_matrice(board_matrice_max+1) + boat_matrice_sum: int = boat_matrice.sum() # add the boat to the board matrice - try: copy_array_offset(boat_mat, board_mat, offset=position) - except ValueError: raise InvalidBoatPosition(boat, position) + try: + copy_array_offset(boat_matrice, board_matrice, offset=position) + except ValueError: + raise InvalidBoatPosition(boat, position) # get the new board matrice sum - board_mat_sum_new: int = board_mat.sum() + board_matrice_sum_new: int = board_matrice.sum() # if the sum of the old board plus the boat sum is different from the new board sum, # then the boat have been incorrectly placed (overlapping, outside of bounds, ...) - if board_mat_sum_old + boat_mat_sum != board_mat_sum_new: raise InvalidBoatPosition(boat, position) + if board_matrice_sum_old + boat_matrice_sum != board_matrice_sum_new: + raise InvalidBoatPosition(boat, position) # otherwise accept the boat in the boats dict - self._boats[boat] = position - - def remove_boat(self, boat: Boat) -> None: - """ - Remove a boat from the boat dict - """ - self._boats.pop(boat) + self.boats = board_matrice def bomb(self, position: Point2D) -> BombState: """ @@ -83,16 +78,16 @@ class Board: # if the bomb is inside the board x, y = position - if x >= self._columns or y >= self._rows: raise InvalidBombPosition(position) + if x >= self.width or y >= self.height: raise InvalidBombPosition(position) # if this position have already been shot - if not self._bombs[y, x]: raise PositionAlreadyShot(position) + if not self.bombs[y, x]: raise PositionAlreadyShot(position) # get the old board matrice board_mat_old_sum = self.get_matrice().sum() # place the bomb (setting the position to False cause the matrice multiplication to remove the boat if any) - self._bombs[y, x] = False + self.bombs[y, x] = False # get the new board matrice board_mat_new = self.get_matrice() @@ -116,42 +111,34 @@ class Board: def get_matrice(self) -> np.array: """ - :return: the boat represented as a matrice + :return: the boats and bombs represented as a matrice """ - board = np.zeros((self._rows, self._columns), dtype=np.ushort) - for index, (boat, position) in enumerate(self._boats.items(), start=1): - # Paste the boat into the board at the correct position. - # The boat is represented by a number representing its order in the boats list - copy_array_offset(boat.get_matrice(value=index), board, offset=position) - - board *= self._bombs # Remove the position that have been bombed - - return board + return self.boats * self.bombs # Remove the position that have been bombed def to_json(self) -> dict: return { - "columns": self._columns, - "rows": self._rows, - "boats": [[boat.to_json(), position] for boat, position in self._boats.items()], - "bombs": self._bombs.tolist() + "columns": self.width, + "rows": self.height, + "boats": [[boat.to_json(), position] for boat, position in self.boats.items()], + "bombs": self.bombs.tolist() } @classmethod def from_json(cls, json_: dict) -> "Board": return Board( - rows=json_["columns"], - columns=json_["rows"], + width=json_["columns"], + height=json_["rows"], boats={Boat.from_json(boat_json): tuple(position) for boat_json, position in json_["boats"]}, bombs=np.array(json_["bombs"], dtype=np.bool_) ) def __copy__(self): return self.__class__( - rows=self._rows, - columns=self._columns, - boats=self._boats.copy(), - bombs=self._bombs.copy(), + height=self.height, + width=self.width, + boats=self.boats.copy(), + bombs=self.bombs.copy(), ) diff --git a/source/gui/scene/Game.py b/source/gui/scene/Game.py index d3b2613..d94c54b 100644 --- a/source/gui/scene/Game.py +++ b/source/gui/scene/Game.py @@ -25,10 +25,13 @@ class Game(Scene): boats_length: list, name_ally: str, name_enemy: str, - grid_width: int, - grid_height: int, my_turn: bool, + grid_width: int = None, + grid_height: int = None, + board_ally_data: dict = None, + board_enemy_data: dict = None, + **kwargs): super().__init__(window, **kwargs) @@ -57,8 +60,9 @@ class Game(Scene): grid_style=texture.Grid.Style1, boat_style=texture.Grid.Boat.Style1, - bomb_style=texture.Grid.Bomb.Style1, - rows=self.grid_height, columns=self.grid_width + rows=self.grid_height, columns=self.grid_width, + + board_data=board_ally_data ) def board_ally_ready(widget): @@ -74,8 +78,9 @@ class Game(Scene): grid_style=texture.Grid.Style1, boat_style=texture.Grid.Boat.Style1, - bomb_style=texture.Grid.Bomb.Style1, - rows=self.grid_height, columns=self.grid_width + rows=self.grid_height, columns=self.grid_width, + + board_data=board_enemy_data ) def board_enemy_bomb(widget, cell: Point2D): @@ -167,6 +172,11 @@ class Game(Scene): ) def ask_save(widget, x, y, button, modifiers): + if not (self._boat_ready_ally and self._boat_ready_enemy): + self.chat_new_message("System", "Veuillez poser vos bateaux avant de sauvegarder.") + return + + # TODO: Pas spam le bouton PacketAskSave().send_connection(self.connection) self.chat_new_message("System", "demande de sauvegarde envoyé.") @@ -196,11 +206,15 @@ class Game(Scene): ) self._my_turn = my_turn # is it the player turn ? - self._boat_ready_ally: bool = False # does the player finished placing his boat ? + self._boat_ready_ally: bool = False # does the player finished placing his boat ? self._boat_ready_enemy: bool = False # does the opponent finished placing his boat ? self._boat_broken_ally: int = 0 self._boat_broken_enemy: int = 0 + if len(boats_length) == 0: # s'il n'y a pas de bateau à placé + self._boat_ready_ally = True # défini l'état de notre planche comme prête + PacketBoatPlaced().send_connection(connection) # indique à l'adversaire que notre planche est prête + self._refresh_turn_text() # refresh @@ -273,10 +287,35 @@ class Game(Scene): def to_json(self) -> dict: return { + "my_turn": self.my_turn, "grid_ally": self.grid_ally.board.to_json(), "grid_enemy": self.grid_enemy.board.to_json(), } + @classmethod + def from_json(cls, + data: dict, + + window: "Window", + thread: StoppableThread, + connection: socket.socket, + name_ally: str, + name_enemy: str) -> "Game": + + return cls( + window=window, + thread=thread, + connection=connection, + boats_length=[], + name_ally=name_ally, + name_enemy=name_enemy, + + my_turn=data["my_turn"], + + board_ally_data=data["grid_ally"], + board_enemy_data=data["grid_enemy"] + ) + def save(self, value: bool): self.chat_new_message( "System", @@ -284,9 +323,12 @@ class Game(Scene): ) if not value: return - ip_address, _ = self.connection.getpeername() + ip_address, port = self.connection.getpeername() + # Le nom du fichier est l'IP de l'opposent, suivi d'un entier indiquant si c'est à notre tour ou non. + # Cet entier permet aux localhost de toujours pouvoir sauvegarder et charger sans problème. + filename: str = f"{ip_address}-{int(self.my_turn)}.bn-save" - with open(path_save / (ip_address + ".bn-save"), "w", encoding="utf-8") as file: + with open(path_save / filename, "w", encoding="utf-8") as file: json.dump(self.to_json(), file, ensure_ascii=False, indent=4) def game_end(self, won: bool): diff --git a/source/gui/texture/Grid.py b/source/gui/texture/Grid.py index e3222e9..02035ca 100644 --- a/source/gui/texture/Grid.py +++ b/source/gui/texture/Grid.py @@ -4,7 +4,6 @@ from .type import Texture, Animation path = path / "grid" path_boat = path / "boat" -path_bomb = path / "bomb" class Grid: @@ -13,16 +12,14 @@ class Grid: class Boat: class Style1(Style): + _animation = sorted( + (path_boat / "animation").iterdir(), + key=lambda path: int(path.stem) + ) + body = Texture(path_boat / "body.png") edge = Texture(path_boat / "edge.png") solo = Texture(path_boat / "solo.png") - class Bomb: - class Style1(Style): - _animation = sorted( - (path_bomb / "animation").iterdir(), - key=lambda path: int(path.stem) - ) - - missed = Animation([*_animation, path_bomb / "missed.png"], 0.03, False) - touched = Animation([*_animation, path_bomb / "touched.png"], 0.03, False) + missed = Animation([*_animation, path_boat / "missed.png"], 0.03, False) + touched = Animation([*_animation, path_boat / "touched.png"], 0.03, False) diff --git a/source/gui/widget/GameGrid.py b/source/gui/widget/GameGrid.py index f3a4bd7..19fc5b5 100644 --- a/source/gui/widget/GameGrid.py +++ b/source/gui/widget/GameGrid.py @@ -24,12 +24,8 @@ class GameGrid(BoxWidget): def __init__(self, scene: "Scene", - rows: int, - columns: int, - grid_style: Type[Style], boat_style: Type[Style], - bomb_style: Type[Style], x: Distance = 0, y: Distance = 0, @@ -39,29 +35,36 @@ class GameGrid(BoxWidget): preview_color: ColorRGB = (150, 255, 150), boats_length: list[int] = None, + rows: int = None, + columns: int = None, + board_data: dict = None, + **kwargs): - self.cell_sprites: dict[Point2D, "Sprite"] = {} + if (rows is None or columns is None) and board_data is None: + raise ValueError(f"{self.__class__} object need to set rows and columns, or the board_data") + + self.cell_sprites: dict[Point2D, tuple["Sprite", hash]] = {} super().__init__(scene, x, y, width, height) self.group_cursor = pyglet.graphics.Group(order=1) self.group_line = pyglet.graphics.Group(order=2) - self.rows = rows - self.columns = columns - # the list of the size of the boats to place self.boats_length = [] if boats_length is None else sorted(boats_length, reverse=True) self.preview_color = preview_color - self.board = Board(rows=self.rows, columns=self.columns) + # créer la planche du jeu + self.board = Board(width=rows, height=columns) if board_data is None else Board.from_json(board_data) + self.rows = self.board.height + self.columns = self.board.width + self.orientation: Orientation = Orientation.HORIZONTAL self._boat_kwargs = dict_filter_prefix("boat_", kwargs) self._bomb_kwargs = dict_filter_prefix("bomb_", kwargs) self.grid_style = grid_style self.boat_style = boat_style - self.bomb_style = bomb_style self.background = Sprite( img=grid_style.get("background"), @@ -91,6 +94,7 @@ class GameGrid(BoxWidget): self.add_listener("on_hover", lambda _, *args: self._refresh_cursor(*args)) self._refresh_size() + self.display_board(self.board) def get_cell_from_rel(self, rel_x: int, rel_y: int) -> tuple[int, int]: """ @@ -124,7 +128,7 @@ class GameGrid(BoxWidget): # sprites - for (x, y), sprite in self.cell_sprites.items(): + for (x, y), (sprite, hash_) in self.cell_sprites.items(): # calcul des décalages à cause de la rotation qui est faite par rapport à l'origine de l'image offset_x = 0 if sprite.rotation <= 90 else self.cell_width @@ -147,7 +151,7 @@ class GameGrid(BoxWidget): self.cursor.y = self.y + cell_y * self.height / self.rows self.cursor.width, self.cursor.height = self.cell_size - self.preview_boat((cell_x, cell_y)) # display the previsualisation of the boat on this cell + self.preview_boat((cell_x, cell_y)) # display the preview of the boat on this cell # function @@ -155,17 +159,28 @@ class GameGrid(BoxWidget): self.cursor.width, self.cursor.height = 0, 0 def display_board(self, board: Board, preview: bool = False): - self.cell_sprites: dict[Point2D, "Sprite"] = {} + # remplacer par l'utilisation de board.boats ? - matrice = board.get_matrice() - max_boat: int = matrice.max() + matrice = board.boats + max_boat: int = np.max(matrice) for (y, x), value in np.ndenumerate(matrice): - if value == 0: continue + bombed: bool = not board.bombs[y, x] # cette case a déjà été attaqué si la valeur est "False". + + if value == 0 and not bombed: + + if (x, y) in self.cell_sprites: + self.cell_sprites.pop((x, y)) + + continue # ignore s'il n'y a ni bombe, ni bateau. # calcul de la forme et de la rotation de cette cellule du bateau form, rotation = ( + # bombe + ("touched", 0) if bombed and value != 0 else + ("missed", 0) if bombed else + # corps ("body", 0) if 0 < y < (self.rows-1) and matrice[y-1, x] == matrice[y+1, x] == value else # colonne ("body", 1) if 0 < x < (self.columns-1) and matrice[y, x-1] == matrice[y, x+1] == value else # ligne @@ -180,17 +195,25 @@ class GameGrid(BoxWidget): ("solo", 0) ) + # si le bateau est le dernier placé et qu'on est en prévisualisation, change sa teinte. + color: ColorRGB = self.preview_color if preview and value == max_boat else (255, 255, 255) + + hash_new = hash((form, rotation, color)) + sprite_old, hash_old = self.cell_sprites.get((x, y), (None, None)) + + if hash_old == hash_new: + # si la texture n'a pas changé, ne rafraichi pas le sprite + continue + sprite = Sprite( img=self.boat_style.get(form), batch=self.scene.batch, **self._boat_kwargs ) sprite.rotation = rotation * 90 + sprite.color = color - if preview and value == max_boat: # if in preview and it is the latest boat - sprite.color = self.preview_color # make the image more greenish - - self.cell_sprites[(x, y)] = sprite + self.cell_sprites[x, y] = (sprite, hash_new) self._refresh_size() @@ -208,14 +231,15 @@ class GameGrid(BoxWidget): Boat(self.boats_length[0], orientation=self.orientation), cell ) - except InvalidBoatPosition: pass # if the boat can't be placed, ignore + except InvalidBoatPosition: + pass # if the boat can't be placed, ignore else: # if the boat have been placed self.boats_length.pop(0) # remove the boat from the list of boat to place if len(self.boats_length) == 0: self.trigger_event("on_all_boats_placed") - self.display_board(self.board) + self.display_board(self.board) # rafraichi l'affichage def preview_boat(self, cell: Point2D): if len(self.boats_length) == 0: return @@ -226,23 +250,19 @@ class GameGrid(BoxWidget): Boat(self.boats_length[0], orientation=self.orientation), cell ) + except InvalidBoatPosition: + self.display_board(self.board) # if the boat can't be placed, ignore - except InvalidBoatPosition: self.display_board(self.board) # if the boat can't be placed, ignore else: self.display_board(preview_board, preview=True) def place_bomb(self, cell: Point2D, force_touched: bool = None) -> BombState: bomb_state = self.board.bomb(cell) - self.cell_sprites[cell] = Sprite( - img=self.bomb_style.get( - "touched" if (bomb_state.success if force_touched is None else force_touched) else "missed" - ), - batch=self.scene.batch, - **self._bomb_kwargs - ) - - self._refresh_size() + if force_touched is not None: + x, y = cell + self.board.boats[y, x] = int(force_touched) + self.display_board(self.board) return bomb_state def on_click_release(self, rel_x: int, rel_y: int, button: int, modifiers: int): diff --git a/source/gui/widget/Image.py b/source/gui/widget/Image.py index 9b40e91..fe210ca 100644 --- a/source/gui/widget/Image.py +++ b/source/gui/widget/Image.py @@ -38,7 +38,8 @@ class Image(BoxWidget): # refresh def _refresh_size(self): - self.image.x, self.image.y, self.image.width, self.image.height = self.bbox + self.image.x, self.image.y = self.xy + self.image.width, self.image.height = self.size # event diff --git a/source/network/Client.py b/source/network/Client.py index 582cdd5..2884cbf 100644 --- a/source/network/Client.py +++ b/source/network/Client.py @@ -1,3 +1,4 @@ +import json import socket from pathlib import Path from typing import TYPE_CHECKING, Any, Optional @@ -37,17 +38,21 @@ class Client(StoppableThread): # sauvegarde - # attend que l'hôte indique s'il a trouvé une ancienne sauvegarde - packet_save_found = PacketHaveSaveBeenFound.from_connection(connection) + load_old_save: bool = False - if packet_save_found.value: + # attend que l'hôte indique s'il a trouvé une ancienne sauvegarde + packet_save_found = PacketHaveSaveBeenFound.from_connection(connection).value + + if packet_save_found: # 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: + for file in reversed(list(path_save.iterdir())): + # la liste est inversée dans le cas où le fichier est en localhost, afin que l'hôte + # prenne le fichier en -0.bn-save et le client en -1.bn-save + if file.stem.startswith(ip_address): path_old_save = file break @@ -63,37 +68,52 @@ class Client(StoppableThread): while True: # attend la décision de l'hôte try: - load_old_save = PacketLoadOldSave.from_connection(connection) + load_old_save = PacketLoadOldSave.from_connection(connection).value break except socket.timeout: if self.stopped: return - print("accept load", load_old_save) + if load_old_save: - if load_old_save.value: - ... - # TODO: Charger nos données + # charge la sauvegarde + with open(path_old_save, "r", encoding="utf-8") as file: + save_data = json.load(file) # paramètres & jeu settings: Any = PacketSettings.from_connection(connection) PacketUsername(username=self.username).send_data_connection(connection) - packet_username = PacketUsername.from_connection(connection) + enemy_username = PacketUsername.from_connection(connection).username - game_scene = in_pyglet_context( - self.window.set_scene, - scene.Game, + if load_old_save: + game_scene = in_pyglet_context( + self.window.set_scene, + scene.Game.from_json, # depuis le fichier json - thread=self, - connection=connection, + data=save_data, - boats_length=settings.boats_length, - name_ally=self.username, - name_enemy=packet_username.username, - grid_width=settings.grid_width, - grid_height=settings.grid_height, - my_turn=not settings.host_start - ) + thread=self, + connection=connection, + + name_ally=self.username, + name_enemy=enemy_username, + ) + + else: + game_scene = in_pyglet_context( + self.window.set_scene, + scene.Game, + + thread=self, + connection=connection, + + boats_length=settings.boats_length, + name_ally=self.username, + name_enemy=enemy_username, + grid_width=settings.grid_width, + grid_height=settings.grid_height, + my_turn=not settings.host_start + ) game_network( thread=self, diff --git a/source/network/Host.py b/source/network/Host.py index 05f29c0..21fb3e5 100644 --- a/source/network/Host.py +++ b/source/network/Host.py @@ -1,3 +1,4 @@ +import json import socket from pathlib import Path from typing import TYPE_CHECKING, Optional @@ -29,7 +30,7 @@ class Host(StoppableThread): self.port = port self.condition_load = Condition() - self.accept_load: Optional[bool] = None + self.accept_load: bool = False def run(self) -> None: print("[Serveur] Thread démarré") @@ -55,7 +56,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 - if file.stem == ip_address: + if file.stem.startswith(ip_address): path_old_save = file break @@ -64,10 +65,10 @@ class Host(StoppableThread): 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) + packet_save_found = PacketHaveSaveBeenFound.from_connection(connection).value # si l'adversaire à également la sauvegarde, demande à l'hôte de confirmer l'utilisation de la save - if packet_save_found.value: + if packet_save_found: from source.gui.scene import GameLoad in_pyglet_context(self.window.set_scene, GameLoad, thread_host=self) @@ -77,29 +78,46 @@ class Host(StoppableThread): PacketLoadOldSave(value=self.accept_load).send_data_connection(connection) if self.accept_load: - ... - # TODO: Charger nos données - # paramètres & jeu + # charge la sauvegarde + with open(path_old_save, "r", encoding="utf-8") as file: + save_data = json.load(file) + + # paramètres et jeu self.settings.send_data_connection(connection) - packet_username = PacketUsername.from_connection(connection) + enemy_username = PacketUsername.from_connection(connection).username PacketUsername(username=self.username).send_data_connection(connection) - game_scene = in_pyglet_context( - self.window.set_scene, - scene.Game, + if self.accept_load: + game_scene = in_pyglet_context( + self.window.set_scene, + scene.Game.from_json, # depuis le fichier json - thread=self, - connection=connection, + data=save_data, - boats_length=self.settings.boats_length, - name_ally=self.username, - name_enemy=packet_username.username, - grid_width=self.settings.grid_width, - grid_height=self.settings.grid_height, - my_turn=self.settings.host_start - ) + thread=self, + connection=connection, + + name_ally=self.username, + name_enemy=enemy_username, + ) + + else: + game_scene = in_pyglet_context( + self.window.set_scene, + scene.Game, + + thread=self, + connection=connection, + + boats_length=self.settings.boats_length, + name_ally=self.username, + name_enemy=enemy_username, + grid_width=self.settings.grid_width, + grid_height=self.settings.grid_height, + my_turn=self.settings.host_start + ) game_network( thread=self, diff --git a/source/network/game_network.py b/source/network/game_network.py index 18ed6ec..3648e8f 100644 --- a/source/network/game_network.py +++ b/source/network/game_network.py @@ -1,3 +1,4 @@ +import builtins import socket from typing import Type, Callable @@ -47,9 +48,14 @@ def game_network( if thread.stopped: return # vérifie si le thread n'est pas censé s'arrêter - except Exception as e: - # TODO: meilleur messages + except Exception as exception: + message: str = "Erreur :\n" + + match type(exception): + case builtins.ConnectionResetError: + message += "Perte de connexion avec l'adversaire." + case _: + message += str(exception) from source.gui.scene import GameError - in_pyglet_context(game_scene.window.set_scene, GameError, text=f"Une erreur est survenu :\n{str(e)}") - print(type(e), e) + in_pyglet_context(game_scene.window.set_scene, GameError, text=message)