L3-Bataille-Navale/source/core/Board.py

169 lines
6 KiB
Python

import numpy as np
from source.core import Boat
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
class Board:
"""
Représente la planche de jeu.
Des bateaux et des bombes peuvent y être placé.
"""
__slots__ = ("width", "height", "boats", "bombs")
def __init__(
self,
width: int = None,
height: int = None,
boats: np.array = None,
bombs: np.array = None) -> None:
if (width is None or height is None) and (boats is None or bombs is None):
raise ValueError(f"{self.__class__}: width and height or boats and bombs should be set.")
# associate the boats and the bombs to array
self.boats: np.array = np.zeros((height, width), dtype=np.ushort) if boats is None else boats
self.bombs: np.array = np.ones((height, width), dtype=np.bool_) if bombs is None else bombs
# récupère la hauteur et la largeur
self.height, self.width = self.boats.shape
def __repr__(self) -> str:
return f"<{self.__class__.__name__} width={self.width} height={self.height}>"
def __str__(self) -> str:
return str(self.get_matrice())
def add_boat(self, boat: Boat, position: Point2D) -> None:
"""
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
"""
# 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)
# 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()
# 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)
# récupère la nouvelle somme de la grille matricielle
board_matrice_sum_new: int = board_matrice.sum()
# 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)
# sinon remplace l'ancienne matrice par la nouvelle
self.boats = board_matrice
def bomb(self, position: Point2D) -> BombState:
"""
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.
"""
# 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)
# si une bombe a déjà été placé ici
if not self.bombs[y, x]: raise PositionAlreadyShot(position)
# récupère l'ancienne somme de la matrice de la grille
board_mat_old_sum = self.get_matrice().sum()
# place la bombe dessus (False équivaut à placer une bombe)
self.bombs[y, x] = False
# 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()
# 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
# 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
# si aucun bateau n'a été touché, ignore
if boat_touched == 0: return BombState.NOTHING
# 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
# si le bateau a été touché partiellement
return BombState.TOUCHED
def remove_bomb(self, cell: Point2D):
"""
Retire une bombe de la matrice
:param cell: cellule de la bombe
"""
x, y = cell
self.bombs[y, x] = True
def clear_bombs(self):
"""
Retire toutes les bombes de la planche
"""
self.bombs = np.ones(self.bombs.shape)
def get_matrice(self) -> np.array:
"""
:return: les bateaux et les bombes représentés sur une même matrice
"""
# 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:
"""
:return: le score du joueur. (Nombre de bateau cassé)
"""
boat_total: int = np.count_nonzero(self.boats)
boat_left: int = np.count_nonzero(self.get_matrice())
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()
}
@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(),
)