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):
"""
Unité de test pour tester l'implémentation du jeu.
"""
def test_boats(self):
"""
Test pour le placement des bateaux
"""
board = Board(width=5, height=5)
board.add_boat(Boat(5, Orientation.HORIZONTAL), (0, 0))
@ -46,6 +53,10 @@ class TestCore(unittest.TestCase):
)
def test_bombs(self):
"""
Test pour le placement des bombes
"""
board = Board(width=5, height=5)
board.add_boat(Boat(5, Orientation.HORIZONTAL), (0, 0))
board.add_boat(Boat(4, Orientation.VERTICAL), (1, 1))

View file

@ -8,13 +8,15 @@ from typing import Optional
import numpy as np
from source.core.enums import BombState
from source.network.packet import PacketChat, PacketUsername, PacketQuit, PacketAskSave, PacketBoatPlaced, \
PacketLoadOldSave, PacketResponseSave, PacketHaveSaveBeenFound, PacketBombPlaced, PacketBombState, PacketSettings, \
PacketBoatsData
from source.network.packet import *
from source.network.packet.abc import Packet
class TestNetwork(unittest.TestCase):
"""
Unité de test pour le réseau
"""
PORT: int = 54200
def __init__(self, *args, **kwargs):
@ -39,11 +41,11 @@ class TestNetwork(unittest.TestCase):
thread_client.join()
def __del__(self):
# ferme les connexions lorsque l'objet est supprimé
self.co_client.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
# ports encore et encore
# tous les tests de packet sont réunis dans la même fonction pour éviter de rouvrir des sockets sur le même port
def test_packet(self):
# PacketChat
for _ in range(100):
@ -161,7 +163,7 @@ class TestNetwork(unittest.TestCase):
# Packet Générique
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])
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)
# Créer un objet widget qui pourra être utilisé dans les tests
window = TestWindow()
scene = TestScene(window)
widget = TestBoxWidget(scene)
class TestPosition(unittest.TestCase):
"""
Unité de test pour les unités de positionnement
"""
def test_unit_px(self):
"""
Test des unités px (pixel)
"""
for value in range(1, 500):
self.assertEqual((value*px)(widget), value)
def test_unit_vw(self):
"""
Test des unités vw (viewport width)
"""
for value in range(1, 200):
self.assertEqual((value*vw)(widget), int(window.width * (value / 100)))
def test_unit_vh(self):
"""
Test des unités vh (viewport height)
"""
for value in range(1, 200):
self.assertEqual((value*vh)(widget), int(window.height * (value / 100)))
def test_unit_ww(self):
"""
Test des unités ww (widget width)
"""
for value in range(1, 200):
self.assertEqual((value*ww)(widget), int(widget.width * (value / 100)))
def test_unit_wh(self):
"""
Test des unités wh (widget height)
"""
for value in range(1, 200):
self.assertEqual((value * wh)(widget), int(widget.height * (value / 100)))
def test_unit_add(self):
"""
Test des additions d'unités
"""
for value_px in range(1, 100):
for value_vw in range(1, 100):
self.assertEqual(
@ -56,6 +85,10 @@ class TestPosition(unittest.TestCase):
)
def test_unit_sub(self):
"""
Test des soustractions d'unités
"""
for value_px in range(1, 100):
for value_vw in range(1, 100):
self.assertEqual(
@ -64,6 +97,10 @@ class TestPosition(unittest.TestCase):
)
def test_unit_rsub(self):
"""
Test des soustractions d'unités (inversé)
"""
for value_px in range(1, 100):
for value_vw in range(1, 100):
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):
"""
Unité de test des fonctionnalités utilitaire pour dictionnaire
"""
def test_dict_filter(self):
"""
Test du filtre de dictionnaire
"""
self.assertEqual(
dict_filter(
lambda key, value: key.startswith("valeur"),
@ -31,6 +39,10 @@ class TestDict(unittest.TestCase):
)
def test_dict_filter_prefix(self):
"""
Test du filtre de dictionnaire par prefix
"""
self.assertEqual(
dict_filter_prefix(
"valeur",
@ -56,6 +68,10 @@ class TestDict(unittest.TestCase):
)
def test_dict_add_prefix(self):
"""
Test de l'ajout de prefix dans un dictionnaire
"""
self.assertEqual(
dict_add_prefix(
"valeur",
@ -82,7 +98,15 @@ class TestDict(unittest.TestCase):
class TestMatrice(unittest.TestCase):
"""
Unité de test des fonctionnalités utilitaire pour matrice
"""
def test_copy_array_offset(self):
"""
Test de la copie d'une matrice dans une autre avec décalage
"""
src = np.array([
[1, 2, 3, 4],
[5, 6, 7, 8]

View file

@ -1,7 +1,7 @@
import numpy as np
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.type import Point2D
from source.utils import copy_array_offset
@ -9,8 +9,8 @@ from source.utils import copy_array_offset
class Board:
"""
Represent a board for the game.
Boat can be added and bomb can be placed.
Représente la planche de jeu.
Des bateaux et des bombes peuvent y être placé.
"""
__slots__ = ("width", "height", "boats", "bombs")
@ -41,76 +41,76 @@ class Board:
def add_boat(self, boat: Boat, position: Point2D) -> None:
"""
Add a boat to the board. Check before if the position is valid.
:boat: the boat to add
:position: the position where to add the boat
:raise: InvalidBoatPosition if the boat position is not valid
Ajoute un bateau à la planche. Vérifie avant si la position est valide.
:param boat: le bateau a placé
:param position: la position du bateau sur la planche
: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_sum_old: int = board_matrice.sum()
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_sum: int = boat_matrice.sum()
# add the boat to the board matrice
# ajoute la matrice du bateau à la matrice de la grille
try:
copy_array_offset(boat_matrice, board_matrice, offset=position)
except ValueError:
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()
# 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, ...)
# si la somme de l'ancienne planche et de la matrice du bateau n'est pas égal à celle de la nouvelle grille,
# 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:
raise InvalidBoatPosition(boat, position)
# otherwise accept the boat in the boats dict
# sinon remplace l'ancienne matrice par la nouvelle
self.boats = board_matrice
def bomb(self, position: Point2D) -> BombState:
"""
Hit a position on the board
:position: the position where to shoot
:raise: PositionAlreadyShot if the position have already been shot before
Place une bombe sur la grille
:position: la position de la bombe
: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
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)
# 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()
# 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
# 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_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
# get the difference between the old and new board sum.
# if the board sum changed, then the difference is the number of the boat that have been hit
# récupère la différence entre l'ancienne et la nouvelle somme de la grille
# si la somme a changé, alors un bateau a été touché.
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 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 the boat have been touched, but without sinking
# si le bateau a été touché partiellement
return BombState.TOUCHED
def remove_bomb(self, cell: Point2D):
@ -129,10 +129,12 @@ class Board:
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:
"""
@ -145,6 +147,7 @@ class Board:
return boat_total - boat_left
def to_json(self) -> dict:
# converti en json les données
return {
"boats": self.boats.tolist(),
"bombs": self.bombs.tolist()
@ -152,12 +155,14 @@ class Board:
@classmethod
def from_json(cls, json_: dict) -> "Board":
# charge à partir de json les données
return Board(
boats=np.array(json_["boats"], dtype=np.ushort),
bombs=np.array(json_["bombs"], dtype=np.bool_)
)
def __copy__(self):
# fait une copie de la grille
return self.__class__(
boats=self.boats.copy(),
bombs=self.bombs.copy(),

View file

@ -5,8 +5,8 @@ from source.core.enums import Orientation
class Boat:
"""
Represent a boat.
It can be added to a board.
Représente un bateau.
Il peut être ajouté à une grille.
"""
__slots__ = ("orientation", "length")
@ -20,7 +20,8 @@ class Boat:
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(
(1, self.length) if self.orientation == Orientation.HORIZONTAL else
@ -30,6 +31,7 @@ class Boat:
)
def to_json(self) -> dict:
# converti le bateau en json
return {
"length": self.length,
"orientation": self.orientation.to_json(),
@ -37,6 +39,7 @@ class Boat:
@classmethod
def from_json(cls, json_: dict) -> "Boat":
# charge le bateau à partir de json
return Boat(
length=json_["length"],
orientation=Orientation.from_json(json_["orientation"]),

View file

@ -3,19 +3,19 @@ from enum import 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
TOUCHED = 1 # the bomb touched a boat
SUNKEN = 2 # the bomb touched the last part of a boat
WON = 3 # the bomb sunk the last boat
NOTHING = 0 # la bombe a manqué
TOUCHED = 1 # la bombe a touché un bateau
SUNKEN = 2 # la bombe a coulé un bateau
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
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]

View file

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

View file

@ -3,5 +3,9 @@ from source.type import Point2D
class InvalidBoatPosition(Exception):
"""
Erreur utilisée lorsque le bateau n'a pas pu être placé
"""
def __init__(self, boat: Boat, position: Point2D):
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):
"""
Erreur utilisée lorsque la bombe n'a pas pu être placé
"""
def __init__(self, position: Point2D):
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):
"""
Erreur utilisée lorsque la bombe vise une case déjà touchée
"""
def __init__(self, position: Point2D):
super().__init__(f"The position {position} have already been shot.")