comment in the _test et core modules

This commit is contained in:
Faraphel 2023-03-13 09:01:59 +01:00
parent f1edbeb8ac
commit 88e89209ac
11 changed files with 141 additions and 47 deletions

View file

@ -8,8 +8,15 @@ from source.core.error import InvalidBoatPosition, InvalidBombPosition, Position
class TestCore(unittest.TestCase): class TestCore(unittest.TestCase):
"""
Unité de test pour tester l'implémentation du jeu.
"""
def test_boats(self): def test_boats(self):
"""
Test pour le placement des bateaux
"""
board = Board(width=5, height=5) board = Board(width=5, height=5)
board.add_boat(Boat(5, Orientation.HORIZONTAL), (0, 0)) board.add_boat(Boat(5, Orientation.HORIZONTAL), (0, 0))
@ -46,6 +53,10 @@ class TestCore(unittest.TestCase):
) )
def test_bombs(self): def test_bombs(self):
"""
Test pour le placement des bombes
"""
board = Board(width=5, height=5) board = Board(width=5, height=5)
board.add_boat(Boat(5, Orientation.HORIZONTAL), (0, 0)) board.add_boat(Boat(5, Orientation.HORIZONTAL), (0, 0))
board.add_boat(Boat(4, Orientation.VERTICAL), (1, 1)) board.add_boat(Boat(4, Orientation.VERTICAL), (1, 1))

View file

@ -8,13 +8,15 @@ from typing import Optional
import numpy as np import numpy as np
from source.core.enums import BombState from source.core.enums import BombState
from source.network.packet import PacketChat, PacketUsername, PacketQuit, PacketAskSave, PacketBoatPlaced, \ from source.network.packet import *
PacketLoadOldSave, PacketResponseSave, PacketHaveSaveBeenFound, PacketBombPlaced, PacketBombState, PacketSettings, \
PacketBoatsData
from source.network.packet.abc import Packet from source.network.packet.abc import Packet
class TestNetwork(unittest.TestCase): class TestNetwork(unittest.TestCase):
"""
Unité de test pour le réseau
"""
PORT: int = 54200 PORT: int = 54200
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -39,11 +41,11 @@ class TestNetwork(unittest.TestCase):
thread_client.join() thread_client.join()
def __del__(self): def __del__(self):
# ferme les connexions lorsque l'objet est supprimé
self.co_client.close() self.co_client.close()
self.so_server.close() self.so_server.close()
# tous les tests de packet sont réunis dans la même fonction pour éviter de réouvrir des sockets sur le mêmes # tous les tests de packet sont réunis dans la même fonction pour éviter de rouvrir des sockets sur le même port
# ports encore et encore
def test_packet(self): def test_packet(self):
# PacketChat # PacketChat
for _ in range(100): for _ in range(100):
@ -161,7 +163,7 @@ class TestNetwork(unittest.TestCase):
# Packet Générique # Packet Générique
for _ in range(100): for _ in range(100):
# prend un packet signal aléatoire (sont plus simples a initialisé) # prend un packet "signal" ou "variable lengh" aléatoire (sont les plus simples a initialisé)
packet_sent_type = random.choice([PacketQuit, PacketAskSave, PacketBoatPlaced, PacketUsername, PacketChat]) packet_sent_type = random.choice([PacketQuit, PacketAskSave, PacketBoatPlaced, PacketUsername, PacketChat])
if packet_sent_type in [PacketUsername, PacketChat]: if packet_sent_type in [PacketUsername, PacketChat]:

View file

@ -21,33 +21,62 @@ class TestBoxWidget(BoxWidget):
super().__init__(x=100, y=200, width=150, height=175, scene=scene) super().__init__(x=100, y=200, width=150, height=175, scene=scene)
# Créer un objet widget qui pourra être utilisé dans les tests
window = TestWindow() window = TestWindow()
scene = TestScene(window) scene = TestScene(window)
widget = TestBoxWidget(scene) widget = TestBoxWidget(scene)
class TestPosition(unittest.TestCase): class TestPosition(unittest.TestCase):
"""
Unité de test pour les unités de positionnement
"""
def test_unit_px(self): def test_unit_px(self):
"""
Test des unités px (pixel)
"""
for value in range(1, 500): for value in range(1, 500):
self.assertEqual((value*px)(widget), value) self.assertEqual((value*px)(widget), value)
def test_unit_vw(self): def test_unit_vw(self):
"""
Test des unités vw (viewport width)
"""
for value in range(1, 200): for value in range(1, 200):
self.assertEqual((value*vw)(widget), int(window.width * (value / 100))) self.assertEqual((value*vw)(widget), int(window.width * (value / 100)))
def test_unit_vh(self): def test_unit_vh(self):
"""
Test des unités vh (viewport height)
"""
for value in range(1, 200): for value in range(1, 200):
self.assertEqual((value*vh)(widget), int(window.height * (value / 100))) self.assertEqual((value*vh)(widget), int(window.height * (value / 100)))
def test_unit_ww(self): def test_unit_ww(self):
"""
Test des unités ww (widget width)
"""
for value in range(1, 200): for value in range(1, 200):
self.assertEqual((value*ww)(widget), int(widget.width * (value / 100))) self.assertEqual((value*ww)(widget), int(widget.width * (value / 100)))
def test_unit_wh(self): def test_unit_wh(self):
"""
Test des unités wh (widget height)
"""
for value in range(1, 200): for value in range(1, 200):
self.assertEqual((value * wh)(widget), int(widget.height * (value / 100))) self.assertEqual((value * wh)(widget), int(widget.height * (value / 100)))
def test_unit_add(self): def test_unit_add(self):
"""
Test des additions d'unités
"""
for value_px in range(1, 100): for value_px in range(1, 100):
for value_vw in range(1, 100): for value_vw in range(1, 100):
self.assertEqual( self.assertEqual(
@ -56,6 +85,10 @@ class TestPosition(unittest.TestCase):
) )
def test_unit_sub(self): def test_unit_sub(self):
"""
Test des soustractions d'unités
"""
for value_px in range(1, 100): for value_px in range(1, 100):
for value_vw in range(1, 100): for value_vw in range(1, 100):
self.assertEqual( self.assertEqual(
@ -64,6 +97,10 @@ class TestPosition(unittest.TestCase):
) )
def test_unit_rsub(self): def test_unit_rsub(self):
"""
Test des soustractions d'unités (inversé)
"""
for value_px in range(1, 100): for value_px in range(1, 100):
for value_vw in range(1, 100): for value_vw in range(1, 100):
self.assertEqual( self.assertEqual(

View file

@ -5,7 +5,15 @@ from source.utils import dict_filter, dict_filter_prefix, dict_add_prefix, copy_
class TestDict(unittest.TestCase): class TestDict(unittest.TestCase):
"""
Unité de test des fonctionnalités utilitaire pour dictionnaire
"""
def test_dict_filter(self): def test_dict_filter(self):
"""
Test du filtre de dictionnaire
"""
self.assertEqual( self.assertEqual(
dict_filter( dict_filter(
lambda key, value: key.startswith("valeur"), lambda key, value: key.startswith("valeur"),
@ -31,6 +39,10 @@ class TestDict(unittest.TestCase):
) )
def test_dict_filter_prefix(self): def test_dict_filter_prefix(self):
"""
Test du filtre de dictionnaire par prefix
"""
self.assertEqual( self.assertEqual(
dict_filter_prefix( dict_filter_prefix(
"valeur", "valeur",
@ -56,6 +68,10 @@ class TestDict(unittest.TestCase):
) )
def test_dict_add_prefix(self): def test_dict_add_prefix(self):
"""
Test de l'ajout de prefix dans un dictionnaire
"""
self.assertEqual( self.assertEqual(
dict_add_prefix( dict_add_prefix(
"valeur", "valeur",
@ -82,7 +98,15 @@ class TestDict(unittest.TestCase):
class TestMatrice(unittest.TestCase): class TestMatrice(unittest.TestCase):
"""
Unité de test des fonctionnalités utilitaire pour matrice
"""
def test_copy_array_offset(self): def test_copy_array_offset(self):
"""
Test de la copie d'une matrice dans une autre avec décalage
"""
src = np.array([ src = np.array([
[1, 2, 3, 4], [1, 2, 3, 4],
[5, 6, 7, 8] [5, 6, 7, 8]

View file

@ -1,7 +1,7 @@
import numpy as np import numpy as np
from source.core import Boat from source.core import Boat
from source.core.enums import Orientation, BombState from source.core.enums import BombState
from source.core.error import InvalidBoatPosition, PositionAlreadyShot, InvalidBombPosition from source.core.error import InvalidBoatPosition, PositionAlreadyShot, InvalidBombPosition
from source.type import Point2D from source.type import Point2D
from source.utils import copy_array_offset from source.utils import copy_array_offset
@ -9,8 +9,8 @@ from source.utils import copy_array_offset
class Board: class Board:
""" """
Represent a board for the game. Représente la planche de jeu.
Boat can be added and bomb can be placed. Des bateaux et des bombes peuvent y être placé.
""" """
__slots__ = ("width", "height", "boats", "bombs") __slots__ = ("width", "height", "boats", "bombs")
@ -41,76 +41,76 @@ class Board:
def add_boat(self, boat: Boat, position: Point2D) -> None: def add_boat(self, boat: Boat, position: Point2D) -> None:
""" """
Add a boat to the board. Check before if the position is valid. Ajoute un bateau à la planche. Vérifie avant si la position est valide.
:boat: the boat to add :param boat: le bateau a placé
:position: the position where to add the boat :param position: la position du bateau sur la planche
:raise: InvalidBoatPosition if the boat position is not valid :raise: InvalidBoatPosition si la position du bateau est invalide
""" """
# get the old board matrice sum # récupère l'ancienne somme total de la grille matriciel
board_matrice = self.boats.copy() board_matrice = self.boats.copy()
board_matrice_sum_old: int = board_matrice.sum() board_matrice_sum_old: int = board_matrice.sum()
board_matrice_max = np.max(board_matrice) board_matrice_max = np.max(board_matrice)
# get the sum of the boat # récupère la somme du bateau matriciel
boat_matrice: np.array = boat.get_matrice(board_matrice_max+1) boat_matrice: np.array = boat.get_matrice(board_matrice_max+1)
boat_matrice_sum: int = boat_matrice.sum() boat_matrice_sum: int = boat_matrice.sum()
# add the boat to the board matrice # ajoute la matrice du bateau à la matrice de la grille
try: try:
copy_array_offset(boat_matrice, board_matrice, offset=position) copy_array_offset(boat_matrice, board_matrice, offset=position)
except ValueError: except ValueError:
raise InvalidBoatPosition(boat, position) raise InvalidBoatPosition(boat, position)
# get the new board matrice sum # récupère la nouvelle somme de la grille matricielle
board_matrice_sum_new: int = board_matrice.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, # si la somme de l'ancienne planche et de la matrice du bateau n'est pas égal à celle de la nouvelle grille,
# then the boat have been incorrectly placed (overlapping, outside of bounds, ...) # alors le bateau n'est pas correctement placé (hors de la grille, par dessus un autre bateau, ...)
if board_matrice_sum_old + boat_matrice_sum != board_matrice_sum_new: if board_matrice_sum_old + boat_matrice_sum != board_matrice_sum_new:
raise InvalidBoatPosition(boat, position) raise InvalidBoatPosition(boat, position)
# otherwise accept the boat in the boats dict # sinon remplace l'ancienne matrice par la nouvelle
self.boats = board_matrice self.boats = board_matrice
def bomb(self, position: Point2D) -> BombState: def bomb(self, position: Point2D) -> BombState:
""" """
Hit a position on the board Place une bombe sur la grille
:position: the position where to shoot :position: la position de la bombe
:raise: PositionAlreadyShot if the position have already been shot before :raise: PositionAlreadyShot si la bombe a déjà été placé ici, InvalidBombPosition si la position est invalide.
""" """
# if the bomb is inside the board # si la bombe est bien dans les limites de la grille
x, y = position x, y = position
if x >= self.width or y >= self.height: raise InvalidBombPosition(position) if x >= self.width or y >= self.height: raise InvalidBombPosition(position)
# if this position have already been shot # si une bombe a déjà été placé ici
if not self.bombs[y, x]: raise PositionAlreadyShot(position) if not self.bombs[y, x]: raise PositionAlreadyShot(position)
# get the old board matrice # récupère l'ancienne somme de la matrice de la grille
board_mat_old_sum = self.get_matrice().sum() 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) # place la bombe dessus (False équivaut à placer une bombe)
self.bombs[y, x] = False self.bombs[y, x] = False
# get the new board matrice # récupère la nouvelle somme de la matrice de la grille
board_mat_new = self.get_matrice() board_mat_new = self.get_matrice()
board_mat_new_sum = board_mat_new.sum() board_mat_new_sum = board_mat_new.sum()
# if the board sum is 0, then there is no boat left on the board # si la somme de la grille matricielle est 0, alors il n'y a plus de bateau sur la grille
if board_mat_new_sum == 0: return BombState.WON if board_mat_new_sum == 0: return BombState.WON
# get the difference between the old and new board sum. # récupère la différence entre l'ancienne et la nouvelle somme de la grille
# if the board sum changed, then the difference is the number of the boat that have been hit # si la somme a changé, alors un bateau a été touché.
boat_touched: int = board_mat_old_sum - board_mat_new_sum boat_touched: int = board_mat_old_sum - board_mat_new_sum
# if no boat have been touched, ignore # si aucun bateau n'a été touché, ignore
if boat_touched == 0: return BombState.NOTHING if boat_touched == 0: return BombState.NOTHING
# if the boat have sinked (no more tile with the boat on it) # si le bateau a coulé (il n'y a plus de case correspondant à ce bateau)
if not np.isin(boat_touched, board_mat_new): return BombState.SUNKEN if not np.isin(boat_touched, board_mat_new): return BombState.SUNKEN
# if the boat have been touched, but without sinking # si le bateau a été touché partiellement
return BombState.TOUCHED return BombState.TOUCHED
def remove_bomb(self, cell: Point2D): def remove_bomb(self, cell: Point2D):
@ -129,10 +129,12 @@ class Board:
def get_matrice(self) -> np.array: def get_matrice(self) -> np.array:
""" """
:return: the boats and bombs represented as a matrice :return: les bateaux et les bombes représentés sur une même matrice
""" """
return self.boats * self.bombs # Remove the position that have been bombed # En multipliant la matrice des bombes par la matrice des bateaux,
# tous les bateaux avec une bombe dessus seront mis à 0 puisqu'une bombe placée vaut "False".
return self.boats * self.bombs
def get_score(self) -> int: def get_score(self) -> int:
""" """
@ -145,6 +147,7 @@ class Board:
return boat_total - boat_left return boat_total - boat_left
def to_json(self) -> dict: def to_json(self) -> dict:
# converti en json les données
return { return {
"boats": self.boats.tolist(), "boats": self.boats.tolist(),
"bombs": self.bombs.tolist() "bombs": self.bombs.tolist()
@ -152,12 +155,14 @@ class Board:
@classmethod @classmethod
def from_json(cls, json_: dict) -> "Board": def from_json(cls, json_: dict) -> "Board":
# charge à partir de json les données
return Board( return Board(
boats=np.array(json_["boats"], dtype=np.ushort), boats=np.array(json_["boats"], dtype=np.ushort),
bombs=np.array(json_["bombs"], dtype=np.bool_) bombs=np.array(json_["bombs"], dtype=np.bool_)
) )
def __copy__(self): def __copy__(self):
# fait une copie de la grille
return self.__class__( return self.__class__(
boats=self.boats.copy(), boats=self.boats.copy(),
bombs=self.bombs.copy(), bombs=self.bombs.copy(),

View file

@ -5,8 +5,8 @@ from source.core.enums import Orientation
class Boat: class Boat:
""" """
Represent a boat. Représente un bateau.
It can be added to a board. Il peut être ajouté à une grille.
""" """
__slots__ = ("orientation", "length") __slots__ = ("orientation", "length")
@ -20,7 +20,8 @@ class Boat:
def get_matrice(self, value: int = 1) -> np.array: def get_matrice(self, value: int = 1) -> np.array:
""" """
:return: the boat represented as a matrice Représente le bateau sous la forme d'une matrice
:return: le bateau sous la forme d'une matrice
""" """
return np.full( return np.full(
(1, self.length) if self.orientation == Orientation.HORIZONTAL else (1, self.length) if self.orientation == Orientation.HORIZONTAL else
@ -30,6 +31,7 @@ class Boat:
) )
def to_json(self) -> dict: def to_json(self) -> dict:
# converti le bateau en json
return { return {
"length": self.length, "length": self.length,
"orientation": self.orientation.to_json(), "orientation": self.orientation.to_json(),
@ -37,6 +39,7 @@ class Boat:
@classmethod @classmethod
def from_json(cls, json_: dict) -> "Boat": def from_json(cls, json_: dict) -> "Boat":
# charge le bateau à partir de json
return Boat( return Boat(
length=json_["length"], length=json_["length"],
orientation=Orientation.from_json(json_["orientation"]), orientation=Orientation.from_json(json_["orientation"]),

View file

@ -3,19 +3,19 @@ from enum import Enum
class BombState(Enum): class BombState(Enum):
""" """
This class represent the state of a bomb after being place on the board. Cette classe représente les états d'une bombe après avoir été placé sur la grille.
""" """
NOTHING = 0 # the bomb missed NOTHING = 0 # la bombe a manqué
TOUCHED = 1 # the bomb touched a boat TOUCHED = 1 # la bombe a touché un bateau
SUNKEN = 2 # the bomb touched the last part of a boat SUNKEN = 2 # la bombe a coulé un bateau
WON = 3 # the bomb sunk the last boat WON = 3 # la bombe a coulé le dernier bateau
ERROR = -1 # the bomb could not be placed ERROR = -1 # la bombe n'a pas été placé
@property @property
def success(self): def success(self):
""" """
:return: Vrai si la valeur correspond à une case qui a été touché :return: Vrai si une case a été touché
""" """
return self in [self.TOUCHED, self.SUNKEN, self.WON] return self in [self.TOUCHED, self.SUNKEN, self.WON]

View file

@ -3,7 +3,7 @@ from enum import Enum
class Orientation(Enum): class Orientation(Enum):
""" """
Represent the orientation of a boat. Les possibles orientations pour un bateau
""" """
HORIZONTAL = "H" HORIZONTAL = "H"

View file

@ -3,5 +3,9 @@ from source.type import Point2D
class InvalidBoatPosition(Exception): class InvalidBoatPosition(Exception):
"""
Erreur utilisée lorsque le bateau n'a pas pu être placé
"""
def __init__(self, boat: Boat, position: Point2D): def __init__(self, boat: Boat, position: Point2D):
super().__init__(f"The boat {boat} can't be placed at {position}.") super().__init__(f"The boat {boat} can't be placed at {position}.")

View file

@ -2,5 +2,9 @@ from source.type import Point2D
class InvalidBombPosition(Exception): class InvalidBombPosition(Exception):
"""
Erreur utilisée lorsque la bombe n'a pas pu être placé
"""
def __init__(self, position: Point2D): def __init__(self, position: Point2D):
super().__init__(f"The bomb can't be placed at {position}.") super().__init__(f"The bomb can't be placed at {position}.")

View file

@ -2,6 +2,10 @@ from source.type import Point2D
class PositionAlreadyShot(Exception): class PositionAlreadyShot(Exception):
"""
Erreur utilisée lorsque la bombe vise une case déjà touchée
"""
def __init__(self, position: Point2D): def __init__(self, position: Point2D):
super().__init__(f"The position {position} have already been shot.") super().__init__(f"The position {position} have already been shot.")