diff --git a/source/core/enums/bomb.py b/source/core/enums/bomb.py index 9b2c196..bd56ee6 100644 --- a/source/core/enums/bomb.py +++ b/source/core/enums/bomb.py @@ -12,3 +12,10 @@ class BombState(Enum): WON = 3 # the bomb sunk the last boat ERROR = -1 # the bomb could not be placed + + @property + def success(self): + """ + :return: Vrai si la valeur correspond à une case qui a été touché + """ + return self in [self.TOUCHED, self.SUNKEN, self.WON] diff --git a/source/gui/scene/Game.py b/source/gui/scene/Game.py index 52bcecf..4ff3b9b 100644 --- a/source/gui/scene/Game.py +++ b/source/gui/scene/Game.py @@ -3,11 +3,13 @@ from typing import TYPE_CHECKING import pyglet +from source.core.enums import BombState +from source.core.error import InvalidBombPosition, PositionAlreadyShot from source.gui.scene import Result from source.gui.scene.abc import Scene from source.gui import widget, texture from source import core -from source.network.packet import PacketChat, PacketBombPlaced, PacketBoatPlaced +from source.network.packet import PacketChat, PacketBombPlaced, PacketBoatPlaced, PacketBombState from source.type import Point2D if TYPE_CHECKING: @@ -18,7 +20,7 @@ class Game(Scene): def __init__(self, window: "Window", connection: socket.socket, - boat_sizes: list, + boats_length: list, name_ally: str, name_enemy: str, grid_width: int, @@ -29,12 +31,11 @@ class Game(Scene): super().__init__(window, **kwargs) self.connection = connection - self.boat_sizes = boat_sizes + self.boats_length = boats_length self.name_ally = name_ally self.name_enemy = name_enemy self.grid_width = grid_width self.grid_height = grid_height - self.my_turn = my_turn # is it the player turn ? self.batch_label = pyglet.graphics.Batch() self.batch_button_background = pyglet.graphics.Batch() @@ -58,7 +59,7 @@ class Game(Scene): x=75, y=0.25, width=0.35, height=0.5, - boats_length=self.boat_sizes, + boats_length=self.boats_length, grid_style=texture.Grid.Style1, boat_style=texture.Grid.Boat.Style1, @@ -74,10 +75,6 @@ class Game(Scene): def board_ally_ready(widget): self.boat_ready_ally = True - - self.me_ready() - if self.boat_ready_enemy: self.refresh_turn_text() - PacketBoatPlaced().send_connection(connection) self.grid_ally.add_listener("on_all_boats_placed", board_ally_ready) @@ -102,6 +99,7 @@ class Game(Scene): def board_enemy_bomb(widget, cell: Point2D): if not (self.boat_ready_ally and self.boat_ready_enemy): return if not self.my_turn: return + PacketBombPlaced(position=cell).send_connection(connection) self.my_turn = False @@ -222,7 +220,6 @@ class Game(Scene): anchor_x="center", - text="Placer vos bateaux", font_size=20, batch=self.batch_label @@ -231,28 +228,132 @@ class Game(Scene): self.board_ally = core.Board(rows=self.grid_height, columns=self.grid_width) self.board_enemy = core.Board(rows=self.grid_height, columns=self.grid_width) - 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._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_enemy: bool = False # does the opponent finished placing his boat ? self._boat_broken_ally: int = 0 self._boat_broken_enemy: int = 0 - def boat_broken_ally(self): - self._boat_broken_ally += 1 - self.score_ally.text = str(self._boat_broken_ally) + self._refresh_turn_text() - def boat_broken_enemy(self): - self._boat_broken_enemy += 1 - self.score_enemy.text = str(self._boat_broken_enemy) + # function - def me_ready(self): - self.label_state.text = "L'adversaire place ses bateaux" - - def refresh_turn_text(self): - self.label_state.text = "Placer vos bombes" if self.my_turn else "L'adversaire place ses bombes" + def _refresh_turn_text(self): + self.label_state.text = ( + "Placer vos bateaux" if not self.boat_ready_ally else + "L'adversaire place ses bateaux..." if not self.boat_ready_enemy else + "Placer vos bombes" if self.my_turn else + "L'adversaire place ses bombes..." + ) def game_end(self, won: bool): self.window.add_scene(Result, won=won) + # property + + @property + def boat_broken_ally(self): + return self._boat_broken_ally + + @boat_broken_ally.setter + def boat_broken_ally(self, boat_broken_ally: int): + self._boat_broken_ally = boat_broken_ally + self.score_ally.text = str(self._boat_broken_ally) + + @property + def boat_broken_enemy(self): + return self._boat_broken_enemy + + @boat_broken_enemy.setter + def boat_broken_enemy(self, boat_broken_enemy: int): + self._boat_broken_enemy = boat_broken_enemy + self.score_enemy.text = str(self._boat_broken_enemy) + + @property + def my_turn(self): + return self._my_turn + + @my_turn.setter + def my_turn(self, my_turn: bool): + self._my_turn = my_turn + self._refresh_turn_text() + + @property + def boat_ready_ally(self): + return self._boat_ready_ally + + @boat_ready_ally.setter + def boat_ready_ally(self, boat_ready_ally: bool): + self._boat_ready_ally = boat_ready_ally + self._refresh_turn_text() + + @property + def boat_ready_enemy(self): + return self._boat_ready_enemy + + @boat_ready_enemy.setter + def boat_ready_enemy(self, boat_ready_enemy: bool): + self._boat_ready_enemy = boat_ready_enemy + self._refresh_turn_text() + + # network + + def network_on_chat(self, connection: socket.socket, packet: PacketChat): + print(packet.message) + + def network_on_boat_placed(self, connection: socket.socket, packet: PacketBoatPlaced): + self.boat_ready_enemy = True + + def network_on_bomb_placed(self, connection: socket.socket, packet: PacketBombPlaced): + try: + # essaye de poser la bombe sur la grille alliée + bomb_state = self.grid_ally.board.bomb(packet.position) + except (InvalidBombPosition, PositionAlreadyShot): + # si une erreur se produit, signale l'erreur + bomb_state = BombState.ERROR + # l'opposant va rejouer, ce n'est donc pas notre tour + self.my_turn = False + else: + # si la bombe a bien été placé, affiche la sur la grille visuel allié + self.grid_ally.place_bomb(packet.position, bomb_state.success) + # c'est à notre tour si l'opposant à loupé sa bombe + self.my_turn = not bomb_state.success + + # envoie le résultat à l'autre joueur + PacketBombState(position=packet.position, bomb_state=bomb_state).send_connection(connection) + + if bomb_state.success: + # si la bombe a touché un bateau, incrémente le score + self.boat_broken_enemy += 1 + + if bomb_state is BombState.WON: + # si l'ennemie à gagner, alors l'on a perdu + self.game_end(won=False) + return True # coupe la connexion + + def network_on_bomb_state(self, connection: socket.socket, packet: PacketBombState): + if packet.bomb_state is BombState.ERROR: + # si une erreur est survenue, on rejoue + self.my_turn = True + return + + # on rejoue uniquement si la bombe à toucher + self.my_turn = packet.bomb_state.success + + if packet.bomb_state.success: + # si la bombe à toucher, incrémente le score + self.boat_broken_ally += 1 + + # place la bombe sur la grille ennemie visuelle + self.grid_enemy.place_bomb(packet.position, packet.bomb_state.success) + + if packet.bomb_state is BombState.WON: + # si cette bombe a touché le dernier bateau, alors l'on a gagné + self.game_end(won=True) + return True # coupe la connexion + + # event + def on_draw(self): self.background.draw() diff --git a/source/gui/scene/RoomCreate.py b/source/gui/scene/RoomCreate.py index 570a587..56892d9 100644 --- a/source/gui/scene/RoomCreate.py +++ b/source/gui/scene/RoomCreate.py @@ -150,7 +150,7 @@ class RoomCreate(Scene): self.input_boat_amount.text = str(self.boat_size_amount.get(self.boat_size, 0)) self.label_boat_recap.text = "" - for size, amount in self.boat_size_amount.items(): + for size, amount in sorted(self.boat_size_amount.items(), key=lambda v: v[0]): self.label_boat_recap.text += f"Taille: {size}, Quantité: {amount}\n" self.button_boat_size_previous = self.add_widget( @@ -199,7 +199,6 @@ class RoomCreate(Scene): ) def next_boat_size(): - if self.boat_size >= min(int(self.input_width.text), int(self.input_height.text)): return self.boat_size += 1 update_boat_size_text() @@ -269,7 +268,7 @@ class RoomCreate(Scene): grid_width=int(self.input_width.text), grid_height=int(self.input_height.text), host_start=self.checkbox_host_start.state, - boat_size=[size for size, quantity in self.boat_size_amount.items() for _ in range(quantity)] + boats_length=[size for size, quantity in self.boat_size_amount.items() for _ in range(quantity)] ) self.window.set_scene(RoomHost, settings=settings) diff --git a/source/gui/widget/GameGrid.py b/source/gui/widget/GameGrid.py index 91870e6..befa02c 100644 --- a/source/gui/widget/GameGrid.py +++ b/source/gui/widget/GameGrid.py @@ -47,7 +47,8 @@ class GameGrid(BoxWidget): self.rows = rows self.columns = columns - self.boats_length = [] if boats_length is None else boats_length # the list of the size of the boats to place + # 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) diff --git a/source/network/Client.py b/source/network/Client.py index feb0e94..a1980e4 100644 --- a/source/network/Client.py +++ b/source/network/Client.py @@ -43,7 +43,7 @@ class Client(StoppableThread): connection=connection, - boat_sizes=settings.boat_size, + boats_length=settings.boats_length, name_ally=self.username, name_enemy=settings.username, grid_width=settings.grid_width, diff --git a/source/network/Host.py b/source/network/Host.py index 6c7c2fa..8f5a90c 100644 --- a/source/network/Host.py +++ b/source/network/Host.py @@ -52,7 +52,7 @@ class Host(StoppableThread): connection=connection, - boat_sizes=self.settings.boat_size, + boats_length=self.settings.boats_length, name_ally=self.settings.username, name_enemy=packet_username.username, grid_width=self.settings.grid_width, diff --git a/source/network/game_network.py b/source/network/game_network.py index 4dd2134..c2a2998 100644 --- a/source/network/game_network.py +++ b/source/network/game_network.py @@ -1,8 +1,6 @@ import socket from typing import Any -from source.core.enums import BombState -from source.core.error import InvalidBombPosition, PositionAlreadyShot from source.gui.scene import Game from source.network.packet.abc import Packet from source.network import packet @@ -23,6 +21,13 @@ def game_network( :param connection: the connection with the other player """ + game_methods = { + packet.PacketChat: game_scene.network_on_chat, + packet.PacketBoatPlaced: game_scene.network_on_boat_placed, + packet.PacketBombPlaced: game_scene.network_on_bomb_placed, + packet.PacketBombState: game_scene.network_on_bomb_state, + } + while True: data: Any = Packet.from_connection(connection) @@ -30,53 +35,7 @@ def game_network( if thread.stopped: return # vérifie si le thread n'est pas censé s'arrêter continue - match type(data): - case packet.PacketChat: - print(data.message) - - case packet.PacketBoatPlaced: - game_scene.boat_ready_enemy = True - if game_scene.boat_ready_ally: - in_pyglet_context(game_scene.refresh_turn_text) - - case packet.PacketBombPlaced: - try: - bomb_state = game_scene.grid_ally.board.bomb(data.position) - except (InvalidBombPosition, PositionAlreadyShot): - bomb_state = BombState.ERROR - - packet.PacketBombState(position=data.position, bomb_state=bomb_state).send_connection(connection) - - touched = bomb_state in [BombState.TOUCHED, BombState.SUNKEN, BombState.WON] - - if bomb_state is not BombState.ERROR: - in_pyglet_context(game_scene.grid_ally.place_bomb, data.position, touched) - - if touched: - in_pyglet_context(game_scene.boat_broken_enemy) - - game_scene.my_turn = not (touched or (bomb_state is BombState.ERROR)) - in_pyglet_context(game_scene.refresh_turn_text) - - if bomb_state is BombState.WON: - in_pyglet_context(game_scene.game_end, won=False) - return # coupe la connexion - - case packet.PacketBombState: - print(data.bomb_state) - if data.bomb_state is BombState.ERROR: - game_scene.my_turn = True - continue - - touched = data.bomb_state in [BombState.TOUCHED, BombState.SUNKEN, BombState.WON] - game_scene.my_turn = touched - in_pyglet_context(game_scene.refresh_turn_text) - - if touched: - in_pyglet_context(game_scene.boat_broken_ally) - - in_pyglet_context(game_scene.grid_enemy.place_bomb, data.position, touched) - - if data.bomb_state is BombState.WON: - in_pyglet_context(game_scene.game_end, won=True) - return # coupe la connexion + if in_pyglet_context( + game_methods[type(data)], # récupère la methode relié ce type de donnée + connection, data + ): return # Appelle la méthode. Si elle renvoie True, arrête le thread diff --git a/source/network/packet/PacketSettings.py b/source/network/packet/PacketSettings.py index 478289f..4572f13 100644 --- a/source/network/packet/PacketSettings.py +++ b/source/network/packet/PacketSettings.py @@ -10,13 +10,13 @@ class PacketSettings(Packet): grid_width: int = field() grid_height: int = field() host_start: bool = field() - boat_size: list = field() + boats_length: list = field() packet_size: int = 51 packet_format: str = ">16sBB?32B" def to_bytes(self): - boat_size = self.boat_size + ([0] * (32 - len(self.boat_size))) + boat_size = self.boats_length + ([0] * (32 - len(self.boats_length))) return struct.pack( self.packet_format, @@ -31,12 +31,12 @@ class PacketSettings(Packet): @classmethod def from_bytes(cls, data: bytes): - username, grid_width, grid_height, host_start, *boat_size = struct.unpack(cls.packet_format, data) + username, grid_width, grid_height, host_start, *boats_length = struct.unpack(cls.packet_format, data) return cls( username=username.replace(b"\x00", b"").decode("utf-8"), grid_width=grid_width, grid_height=grid_height, host_start=host_start, - boat_size=list(filter(lambda value: value != 0, boat_size)) + boats_length=list(filter(lambda value: value != 0, boats_length)) )